객체지향 생활 체조를 하는 이유?

학습 동기

프리코스 때 부터 우테코 측에서 강조하던 내용들이 있었다. 인덴트를 줄이고 else를 쓰지말고 한 메서드가 한 기능을 하고 등등. 지금은 어느정도 습관으로 남게되어 오히려 else를 쓰고 인덴트가 늘어나면 마음이 불편해졌다. 오늘은 객체 지향 생활체조의 의도가 무엇인지 하나하나 살펴보려고 한다.

객체지향 생활 체조

규칙 1: 한 메서드에 오직 한 단계의 들여쓰기만 한다.
규칙 2: else 예약어를 쓰지 않는다.
규칙 3: 모든 원시값과 문자열을 포장한다.
규칙 4: 한 줄에 점을 하나만 찍는다.
규칙 5: 줄여쓰지 않는다(축약 금지).
규칙 6: 모든 엔티티를 작게 유지한다.
규칙 7: 2개 이상의 인스턴스 변수를 가진 클래스를 쓰지 않는다.
규칙 8: 일급 콜렉션을 쓴다.
규칙 9: 게터/세터/프로퍼티를 쓰지 않는다.

규칙 1: 한 메서드에 오직 한 단계의 들여쓰기만 한다.

class Board {
   ...
   String board() {
      StringBuffer buf = new StringBuffer();
      for (int i = 0; i < 10; i++) {
         for (int j = 0; j < 10; j++)
            buf.append(data[i][j]);
         buf.append("\n");
      }
      return buf.toString();
   }
}
 
class Board {
   ...
   String board() {
      StringBuffer buf = new StringBuffer();
      collectRows(buf);
      return buf.toString();
   }
 
   void collectRows(StringBuffer buf) {
      for (int i = 0; i < 10; i++)
         collectRow(buf, i);
   }
 
   void collectRow(StringBuffer buf, int row) {
      for (int i = 0; i < 10; i++)
         buf.append(data[row][i]);
      buf.append("\n");
   }
}

위 코드처럼 덩치가 큰 메서드들은 응집력이 떨어진다. 한 메서드는 한가지 기능만 하도록 설계해야 재사용성이 높아진다. 메서드에서 인덴트 수준이 깊어졌다면 과연 해당 메서드가 한가지 기능을 하는지 의심해봐야한다.

규칙 2: else 예약어를 쓰지 않는다.

public static void endMe() {
   if (status == DONE) {
      doSomething();
   } else {
      // 다른 코드
   }
}

else 대신에 early return 또는 다형성을 통해 설계하여 개선할 수 있다. 코드의 의도를 분명하게 나타내기 위해서 else를 제거하는 훈련을 하자.

규칙 3: 모든 원시값과 문자열을 포장한다.

원시형 타입으로는 컴파일러가 의미적으로 맞는 프로그램 작성을 안내할 수 없다. 컴파일러와 프로그래머에게 그 값이 어떤 값이며, 왜 쓰고 있는지 정보를 전달하기 위해 포장하자.

규칙 4: 한 줄에 점을 하나만 찍는다.

class Board {
   ...
 
   class Piece {
      ...
      String representation;
   }
   class Location {
      ...
      Piece current;
   }
 
   String boardRepresentation() {
      StringBuffer buf = new StringBuffer();
      for (Location l : squares())
         buf.append(l.current.representation.substring(0, 1));
      return buf.toString();
   }
}
 
class Board {
   ...
 
   class Piece {
      ...
      private String representation;
      String character() {
         return representation.substring(0, 1);
      }
 
      void addTo(StringBuffer buf) {
         buf.append(character());
      }
   }
   class Location {
      ...
      private Piece current;
 
      void addTo(StringBuffer buf) {
         current.addTo(buf);
      }
   }
 
   String boardRepresentation() {
      StringBuffer buf = new StringBuffer();
      for (Location l : squares())
         l.addTo(buf);
      return buf.toString();
   }
}

하나의 동작에 대해 어떤 객체가 맡고 있는지 구분하기 어려울 때가 있다. 여러개의 점이 있는 코드를 보면 여러 동작이 동시에 일어나고 있을 경우가 높다. 캡슐화를 통해 메세지를 전달하고 각 객체는 하나의 단계에서만 작동할 수 있도록 코드를 간결하게 만들자.

규칙 5: 줄여쓰지 않는다(축약 금지).

간결해보이지미만 오히려 문맥을 파악하는데 방해하는 요소다. 이름을 줄이려고 노력하지 말고, 혹시 해당 클래스나 메서드가 너무 많은 일을 하고 있는게 아닌지 고민해봐라.

규칙 6: 모든 엔티티를 작게 유지한다.

클래스나 패키지가 너무 많은 메서드와 파일을 담고 있으면 안된다. 패키지는 하나의 목적을 담고 있고, 클래스는 하나의 책임을 다할 수 있도록 작게 유지하자.

규칙 7: 2개 이상의 인스턴스 변수를 가진 클래스를 쓰지 않는다.

class Name {
   Surname family;
   GivenNames given;
}
 
class Surname {
   String family;
}
 
class GivenNames {
   List<String> names;
}

속성의 집합에서 객체를 협력 객체의 계층구조로 분해하면 더 직접적으로 효율적인 객체 모델에 이른다.
때때로 여러 관련 인스턴스 변수가 실은 일급 컬렉션 안에서 연관된 삶을 살고있다.

규칙 8: 일급 콜렉션을 쓴다.

컬렉션을 포함한 클래스는 반드시 다른 멤버 변수가 없어야 한다. 컬렉션 그 자체로 포장돼 있으므로...

규칙 9: 게터/세터/프로퍼티를 쓰지 않는다.

"말을 해, 묻지 말고" - 메세지를 던져서 필요한 기능을 수행하도록 하자...

결론

최종 목적은 코드나 아이디어의 중복이 없게 코드를 만드는 것이다. 코드의 복잡다단함에 대한 단순하고 우아한 추상화를 간결하게 표출하는 코드를 만드는게 요지다.

내가 내린 결론

객체 지향을 추구하는 것은 좋다. 하지만 사람마다 실력차가 존재하기 때문에 이 규칙을 모든 상황에서 지킬 수 있다고 생각하지는 않는다. 마감 기한은 정해져 있기 때문에 항상 객체 지향적인 코드를 작성하는 것은 불가능하다. 하지만 어제와 비슷한 코드를 짜는 것은 좋지 않다. 9가지 규칙을 기억하고 최대한 따르려고 노력하자, 리팩터링을 통해서 어제의 코드보다 객체 지향적이고 깨끗한 코드를 작성할 수 있도록 노력하자!

Reference

https://developerfarm.wordpress.com/2012/02/03/object_calisthenics_summary/

좋은 웹페이지 즐겨찾기