티스토리 뷰

이번 장에서는 협력과 메시지에 관련한 용어를 정리하고, 인터페이스 설계를 위한 원칙, 기법들에 대해서 소개한다. 도움 되는 내용들이 많이 있었다.

협력과 메시지


협력은 어떤 객체가 다른 객체에게 무언가를 요청할 때 시작된다. 메시지는 협력을 가능하게 하는 매개체이다. 객체는 메시지를 전송하고, 메시지를 수신한 객체는 이를 처리하고 응답한다.

클라이언트 - 서버 모델

두 객체 사이의 협력관계는 클라이언트 - 서버 모델로 자주 비유한다. 

  • 클라이언트 : 메시지를 전송하는 객체
  • 서버 : 메시지를 수신하는 객체

그러나 실제로 객체는 협력의 관점에서 두 가지 종류의 메시지 집합으로 구성된다. 객체가 수신하는 메시지의 집합외부의 객체에게 전송하는 메시지의 집합이다.

메시지와 메시지 전송

한 객체가 다른 객체에게 도움을 요청하는 것을 메시지 패싱 또는 메시지 전송이라고 부른다.

  • 메시지 전송자 : 메시지를 전송하는 객체
  • 메시지 수신자 : 메시지를 수신하는 객체

메시지 = 오퍼레이션명 + 인자

메시지 전송은 여기에 메시지 수신자를 추가한 것이다.

메시지와 메서드

  • 메서드 : 메시지를 수신했을 때, 실제로 실행되는 함수 또는 프로시저
  • 동일한 이름의 변수에게 동일한 메시지라도 수행하는 메서드가 달라질 수 있다.

객체는 메시지와 메서드, 이 두 개념을 실행 시점에 연결해야 하기 때문에, 컴파일 시점과 실행 시점의 의미가 다를 수 있다.

퍼블릭 인터페이스와 오퍼레이션

  • 퍼블릭 인터페이스 : 객체가 의사소통을 위해 외부에 공개하는 메시지의 집합
  • 오퍼레이션 : 퍼블릭 인터페이스에 포함된 메시지

오퍼레이션은 수행 가능한 어떤 행동에 대한 추상화이다. 오퍼레이션이라는 표현은 내부 구현 코드를 제외한 메시지와 관련된 시그니처를 의미할 때 주로 사용한다.

시그니처

  • 오퍼레이션의 이름파라미터 목록을 합쳐 시그니쳐라고 표현
  • 오퍼레이션은 실행 코드없이 시그니처만을 정의한 것
  • 메서드는 시그니처에 구현을 더한 것

오퍼레이션의 관점에서 다형성은, 동일한 오퍼레이션 호출에 대해 서로 다른 메서드들이 실행되는 것이다.

정리하면, 메시지가 객체의 퍼블릭 인터페이스와 그 안에 포함될 오퍼레이션을 결정한다. 그런데 이 퍼블릭 인터페이스가 객체의 품질을 결정하기 때문에, 결국 메시지가 객체의 품질을 결정한다고 볼 수 있다.

인터페이스와 설계 품질


좋은 인터페이스는 최소한의 인터페이스추상적인 인터페이스라는 조건을 만족해야 한다. 추상적인 인터페이스어떻게 하는지가 아니라 무엇을 하는지를 표현한다.

퍼블릭 인터페이스 품질에 영향을 미치는 원칙과 기법들

  • 디미터 법칙
  • 묻지 말고 시켜라
  • 의도를 드러내는 인터페이스
  • 명령-쿼리 분리

디미터 법칙

객체의 내부 구조에 강하게 결합되지 않도록 협력 경로를 제한하는 것.

도트(.)를 이용해 메시지 전송을 표현하는 언어에서는 오직 하나의 도트만 사용하는 것이라고 볼 수도 있다. (뒤에 나오겠지만 항상 그런 것은 아니다.) 구체적으로 협력 경로를 어디까지 제한해야 할까, 아래와 같다.

  • this 객체
  • 메서드의 매개변수
  • this의 속성
  • this의 속성인 컬렉션의 요소
  • 메서드 내에서 생성된 지역 객체

부끄럼 타는 코드(shy code)라고 해서, 불필요한 어떤 것도 다른 객체에게 보여주지 않으며 다른 객체의 구현에 의존하지 않는 코드를 말한다. 디미터 법칙을 잘 지킨다면 부끄럼 타는 코드를 잘 작성할 수 있다.

책에서 디미터 법칙을 위반하는 코드를 예시로 보여준다. 근데 그 예시를 볼 것도 없이 나는 이 부분을 읽으면서  내가 작성한 수많은 잘못된 코드들이 생각났다. 

question에 getWriter()에 getUserEmail()까지 이어진다. 이 외에도 이런 식의 코드는 아주 많다. (빨리 고쳐야지...) 이러한 코드를 여러 대의 기차가 충돌한 것 처럼 보인다고 해서 기차 충돌(train wreck)이라고 부른다고 한다. 

내부 구조를 묻는 메시지가 아니라 수신자에게 무언가를 시키는 메시지가 더 좋은 메시지이다.  getWriter 같은 메서드는 객체의 내부 구조를 묻는다. 디미터 법칙은 객체의 내부 구조를 묻지 않도록 하는 기법이라고 볼 수 있다. 

묻지 말고 시켜라

디미터 법칙과 비슷한 맥락으로, 좋은 인터페이스를 위해서는 객체의 상태를 묻지 말고 원하는 것을 시켜라고 한다. 이 원칙을 지키면, 자연스럽게 정보 전문가 패턴이 가능해진다. 훌륭한 인터페이스를 위해서는 객체의 작업이 어떻게 수행되는지 노출해서는 안된다. 즉, 객체가 어떻게 하는지가 아닌, 무엇을 하는지를 서술해야 한다.

의도를 드러내는 인터페이스

메서드의 이름을 통해 어떻게가 아니라 무엇을 하는지를 드러내야한다. 클라이언트 관점에서 협력을 바라보고, 클라이언트의 의도를 담을 수 있도록 메서드 이름을 짓자. 무엇을 하느냐에 따라 메서드의 이름을 짓는 패턴을 의도를 드러내는 선택자(Intention Revealing Selector)라고 부른다. 이를 인터페이스 레벨로 확장한 의도를 드러내는 인터페이스라는 개념도 있다.

원칙의 함정


설계는 트레이드 오프의 산물이다. 경우에 따라 다르기에 원칙을 맹신하다 보면 때로는 문제가 생긴다.

디미터 법칙은 하나의 도트(.)를 강제하는 규칙이 아니다.

앞서 디미터 법칙에서 하나의 도트만을 사용하라고 했는데, 사실 디미터 법칙은 하나의 도트(.)를 강제하는 규칙이 아니다. 예시를 보자.

IntStream.of(1, 15, 20, 3, 9).filter(x -> x > 10).distinct().count();

디미터 법칙은 결합도와 관련된 것인데, 이 결합도가 문제가 되는 것은 객체의 내부 구조가 외부로 노출되는 경우이다. 예시의 메서드들은  모두 IntStream이라는 동일한 인스턴스를 반환한다. 기차 충돌처럼 보이는 코드이지만, 객체의 내부 구현에 대한 어떤 정보도 외부로 노출하지 않는다면 디미터 법칙을 준수한 것이다. 즉, 이 코드는 디미터 법칙을 준수했다.

결합도와 응집도의 충돌

어떤 객체의 상태를 물어본 후 상태를 기반으로 결정을 내리고, 그 결정에 따라 객체의 상태를 변경하는 코드는 묻지 말고 시켜라 스타일로 변경해야 한다. 

public class Theater {
    public void enter(Audience audience) {
        if(audience.getBag().hasInvitation()) {
            ...
}

위 코드는 Audience의 내부 구조와 결합된다. 이를 해결하기 위해 모든 코드를 Audience로 옮겨야 한다. Audience에게 위임 메서드를 추가하는 것이다.

public class Audience {
    public Long buy(Ticket ticket) {
        if(bag.hasInvitation()) {
            ...
}

위임 메서드를 통해 객체의 내부 구조를 감추어서 협력에 참여한 객체들의 결합도를 낮춤과 동시에 응집도를 높일 수 있다. 하지만, 모든 상황에서 맹목적으로 위임 메서드를 추가하면 같은 퍼블릭 인터페이스 안에 어울리지 않은 오퍼레이션들이 공존하게 된다. 객체는 상관없는 책임들을 한꺼번에 떠안게 될 수도 있다.

즉, 항상 묻지말고 시켜라가 가능하지 않다. (관련 예제 코드를 찾지 못해서 설명이 부족하다. ㅠ)

명령-쿼리 분리 원칙


어떤 절차를 묶어 호출 가능하도록 이름을 부여한 기능 모듈을 루틴이라고 부른다. 루틴은 프로시저 함수로 구분된다. 명령쿼리는 객체의 인터페이스 측면에서 프로시저와 함수를 부르는 또 다른 이름이다. 

  • 명령 : 상태를 수정하는 오퍼레이션
  • 쿼리 : 정보를 반환하는 오퍼레이션

두 가지를 분리하기 위해 다음 규칙들을 준수해야 한다.

  • 객체의 상태를 변경하는 명령은 반환 값을 가질 수 없다.
  • 객체의 정보를 반환하는 쿼리는 상태를 변경할 수 없다.

getter, setter 메서드를 생각하면 이해가 쉬울 것 같다.

public int getX() {
	return this.x;
}

public void setX(int x) {
	this.x = x;
}

위 예시에서 getX메서드는 쿼리이고, setX메서드는 명령이라고 볼 수 있다.

명령-쿼리 분리와 참조 투명성

참조 투명성 : 어떤 표현식 e가 있을 때 모든 e를 e의 값으로 바꾸더라도 결과가 달라지지 않는 특성

정리 및 느낀점


6장은 실질적인 기법들을 많이 소개했다. 책을 읽으면서 내가 작성한 잘못된 코드들이 많이 생각나서 괴로웠다. 또, 좋은 코드에 대한 기준이 세워지면서 리팩터링을 어떻게 해나가야 할지 감이 오는 느낌이다. 이번 장은 특히나 도움이 많이 된 내용이었다. 빨리 코드를 수정하고 싶다.

댓글
최근에 올라온 글
최근에 달린 댓글
Total
Today
Yesterday