인자의 값으로 null을 절대 허용하지 마라

  • 객체를 존중했다면 조건의 존재 여부를 객체 스스로 결정하게 했을 것이다. 객체지향 생활 체조 원칙의 원시 값과 문자열을 포장한다. 원칙을 적용하는 것이 null을 허용하지 않는 방법 중의 하나가 될 수 있다.

final 이거나 abstract 이거나

  • 상속이 객체들의 관계를 너무 복잡하게 만들기 수 있다. 또한 캡슐화를 해칠 수 있기 때문에 상속을 고려하지 않는 메서드나 클래스는 final 로, 오버라이딩의 가능성을 남겨두고 싶다면 abstract로 불완전한 상태로 만드는 것이 좋다.

-er로 끝나는 이름을 사용하지 마라

  • 클래스는 객체의 팩토리다. 클래스는 객체를 만들고, 추적하고, 적절한 시점에 파괴한다. 즉, 객체의 라이프사이클을 관리한다.
  • 종종 클래스를 객체의 템플릿으로 설명하지만, 이 설명은 완전히 잘못된 설명이다. 이 설명은 수동적이고, 멍청한 코드 덩어리로 클래스를 격하시킨다.
  • 클래스는 객체의 능동적인 관리자. 즉, 객체를 꺼내거나 반환하는 역할이기 떄문에 저장소(storage)또는 웨어하우스 라고 불러야한다.
  • 클래스 이름은 무엇을 하는지가 아니라 무엇인지를 기반해 지어야된다.
  • 객체는 객체의 외부 세계와 내부 세계를 이어주는 연결장치가 아니다.
  • 객체는 내부에 캡슐화된 데이터를 다루기 위해 요청할 수 있는 절차의 집합이 아니다.
  • 객체는 캡슐화된 데이터의 대표자다. 스스로 결정을 내리고 자립적일 수 있어야된다.

메서드 이름을 신중하게 선택하라

  • 빌더(builder)는 이름을 명사로, 조정자(manipulator)는 이름을 동사로.
  • 빌더: 새로운 객체를 반환하는 메서드. 반환 타입이 void일 수 없다.
  • 조정자: 객체로 추상화한 실세계 엔티티를 수정하는 메서드. 반환 타입이 항상 void다.
  • 예외적으로 반환값이 Boolean 인 경우, 빌더에 속하지만 형용사로 지어야된다.
  • 조정자와 빌더가 혼합되어 있는 형태는 충분히 분리가 가능한 메서드다.

      class Document {
          int write(InputStream content);
      }
    

    를 아래와 같이 분리할 수 있다.

      class Document {
          OutputPipe output();
      }
      class OutputPipe {
          void write(InputStream content);
          int bytes();
          long time();
      }
    

생성자 하나를 주 생성자로 만들어라

  • 클래스를 잘 설계한다면, 클래스는 많은 수의 생성자와 적은 수의 메서드를 포함할 것이다.
  • 2~3개의 메서드와 5~10개의 생성자를 포함하는 것이 적당하다.
  • 이런 기준을 두는 핵심은 응집도가 높고, 견고한 클래스에는 적은 수의 메서드와 상대적으로 더 많은 생성자가 존재한다는 점이다.
  • 메서드가 많아지면 클래스의 초점이 흐려지고, 단일 책임 원칙(SRP)를 위반한다. 하지만 생성자가 많아지면 유연성이 향상 된다.

문서를 작성하는 대신 테스트를 만들어라

  • 읽기 쉬운 코드를 작성하기 위해서는 읽는 사람이 비즈니스 도메인, 프로그래밍 언어, 디자인 패턴, 알고리즘을 거의 이해하지 못하는 주니어 프로그래머라고 가정해야 한다.
  • 이상적인 코드는 스스로를 설명하기 때문에 어떤 추가 문서도 필요하지 않다.

생성자에 코드를 넣지마라

  • 인자에 손대지 마라
  • 잘못 구현한 예
public class Cash {
    private int dollars;
    public Cash(String dlr) {
        this.dollars = Integer.parseInt(dlr);
    }
}
  • 리팩터링한 예
public class Cash {
    private Number dollars;

    public Cash(String dlr) {
        this.dollars = new StringAsInteger(dlr);
    }
}

public class StringAsInteger extends Number {
    private String source;

    public StringAsInteger(String source) {
        this.source = source;
    }

	@Override
    public int intValue() {
        return Integer.parseInt(this.source);
    }
}
  • 위와 같이 리팩터링할 경우 실제 사용 시점까지 객체의 변환 작업은 연기한다.
  • 진정한 객체지향에서 인스턴스화란 더 작은 객체들을 조합해서 더 큰 객체를 만드는 것을 의미한다.
  • 처음에 할 일은 객체를 인스턴스화 하는 것이고, 두 번째 할 일은 객체가 우리를 위해 작업을 하게 만드는 것이다. 이 두 단계가 겹쳐서는 안된다.
  • 생성자에서 코드를 없애면 사용자가 쉽게 제어할 수 있는 투명한 객체를 만들 수 있으며, 객체를 이해하고 재사용 하기도 쉬워진다.
  • 객체는 요청을 받을 때만 행동하고, 그 전에는 어떤 일도 하지 않는다. 이 객체들은 좋은 방식으로 메우 게으르다.

5개 이하의 public 메서드만 노출해라

  • 가장 우아하고, 유지보수가 가능하고, 응집력이 높으면서, 테스트하기 용이한 객체는 작은 객체이다.
  • 클래스의 크기를 정하는 기준으로 public 메서드의 개수를 사용하기를 권한다.
  • 클래스가 작으면 메서드와 프로퍼티가 더 가까이 있을 수 있기 때문에 응집도가 높아진다.

불변 객체로 만들어라

  • 모든 클래스를 상태 변경이 불가능한 불변 클래스로 구현하면 유지보수성이 크게 향상된다.
  • 하지만 불변 객체는 지연로딩을 지원하지 않는다.
  • 식별자 가변성 문제가 없다.
  • 실패 원자성이 있다. - 완전하고 견고한 상태의 객체를 가지거나, 실패하거나 둘 중 하나만 가능하다.
  • 시간적 결합을 제거할 수 있다. - 가변 객체들이 많을 경우 연산의 순서를 일일이 기억해야 한다.
  • 사이드 이펙트를 제거할 수 있다.
  • null 참조를 없앨 수 있다.
  • 스레드 안전한 코드를 구현할 수 있다.
  • 더 작고 더 단순한 객체를 구현할 수 있다.

Public 상수를 사용하지 마라

  • 객체들은 어떤 것도 공유해서는 안된다.
  • 코드 중복이라는 문제를 해결하기 위해 public static final 프로퍼티를 생성하는 것은 결합도를 높이고 응집도가 낮아지는 문제가 있다.
  • 잘못된 예시
public class Constants {
    public static final String EOL = "\r\n";
}

public class Records {
    void write(Writer out) {
        for (Record rec : this.all) {
            out.write(rec.toString());
            out.write(Constants.EOL);
        }
} }
  • 리팩터링
public class EOLString {
    private final String origin;
    public EOLString(String origin) {
        this.origin = origin;
    }
    
	@Override
    public String toString() {
        return String.format("%s\r\r", origin);
    }
}

public class Records {
    void write(Writer out) {
        for (Record rec : this.all) {
            out.write(new EOLString(rec.toString()));
        }
} }
  • 아무리 사소해 보이는 상수라도 항상 작은 클래스를 이용해서 대체할 것을 추천한다.
  • 자바의 열거형에 대해서도 동일한 규칙이 적용된다. 열거형과 퍼블릭 상수 사이에는 아무런 차이도 없기 때문에 열거형 역시 사용해서는 안된다.

가능하면 적게 캡슐화하세요

  • 4개 또는 그 이하의 객체를 캡슐화할 것을 권장한다.
  • 상태는 객체의 식별자여야한다. 객체의 식별자는 기본적으로 세계 안에서 객체가 위치하는 좌표이다.

항상 인터페이스를 사용하세요

  • 객체들은 서로를 필요로 하기 때문에 결합된다.
  • 애플리케이션이 성장하기 시작하고 객체들의 수가 수십 개를 넘어가면서부터 객체 사이의 강한 결합도가 심각한 문제로 떠오르기 시작한다.
  • 애플리케이션 전체를 유지보수 가능하도록 만들기 위해서는 최선을 다해 객체를 분리해야한다.
  • 이를 가능하게 하는 가장 훌륭한 도구가 바로 인터페이스다.

느낀 점

  • 상당히 극단적인 내용이 많아서 꽤 충격적이었다. 이 중에서 내가 실제로 지킬 수 있는게 과연 몇 개나 있을까라는 생각이 들기도 한다.
  • 특히 ‘생성자에 코드를 넣지마라’, ‘불변 객체로 만들어라’는 과연 현실적으로 모두 적용 가능할지 의심이 된다.
  • 생성자에 코드를 넣지마라: 객체 생성전에 유효하지 않은 생성이 발생할 경우 객체가 생성이 되지 못하게 막아야 된다고 생각한다. 생성자에 코드를 넣지 않고 계산을 늦출 경우, 유효성 검증 과정도 늦어지므로 유효하지 않는 객체를 생성하게 될텐데 이게 정말 올바른 설계라고 할 수 있을지 궁금하다.
  • 불변 객체로 만들어라: 이 경우도 현실적으로 가능할지 의심이된다. 불변 객체로 구현할 경우 객체의 상태 변경을 표현하고 싶으면 매번 객체를 새로 생성해야 되는데, 이 객체의 생성은 꽤 큰 오버헤드가 발생할 것이다. 상태 변화가 잦은 객체를 표현하고 싶을 때도, 불변 객체로 구현하는 것이 맞을지 의심이 된다.

댓글남기기