엄청 오랜만에 정리해서 올리네요.
6회때는 @vincent @freebear 님께서 참석해주셨습니다. 다들 바쁘셔서 참석을 못하셨습니다.
6회차 때는 6챕터와 7챕터를 함께했습니다.
이번에는 6챕터만 올리고 7챕터는 내일 올리도록 하겠습니다.
P175
클래스라는 구현 도구에 지나치게 집착하면 경직되고 유연하지 못한 설계에 이를 확률이 높아진다.
여기서 중요한 것은 책임이 객체가 수신할 수 있는 메시지의 기반이 된다는 것이다.
클래스 사이의 정적인 관계에서 메시지 사이의 동적인 흐름으로 초점을 전환하는 것은 미숙함을 벗어나 숙련된 객체지향 설계자로 성장하기 위한 첫걸음이다. 애플리케이션은 클래스로 구성되지만 메시지를 통해 정의된다는 사실을 기억하라[Metz12].
객체가 수신하는 메시지들이 객체의 퍼블릭 인터페이스를 구성한다.
유연하고 재사용 가능한 퍼블릭 인터페이스를 만드는 데 도움이 되는 설계 원칙과 기법을 익히고 적용해야 한다.
P176
협력은 어떤 객체가 다른 객체에게 무언가를 요청할 때 시작된다[Wirfs-Brock03]. 메시지는 객체 사이의 협력을 가능하게 하는 매개체다.
P177
메시지는 오퍼레이션명(operation name) 과 인자(argument) 로 구성되며 메시지 전송은 여기에 메시지 수신자를 추가한 것이다[Wirfs-Brock90]. 따라서 메시지 전송은 메시지 수신자, 오퍼레이션명, 인자의 조합이다.
P178
이처럼 메시지를 수신했을 때 실제로 실행되는 함수 또는 프로시저를 메서드라고 부른다.
반면 객체는 메시지와 메서드라는 두 가지 서로 다른 개념을 실행 시점에 연결해야 하기 때문에 컴파일 시점과 실행 시점의 의미가 달라질 수 있다.
P179
실행 시점에 메시지와 메서드를 바인딩하는 메커니즘은 두 객체 사이의 결합도를 낮춤으로써 유연하고 확장 가능한 코드를 작성할 수 있게 만든다.
이처럼 객체가 의사소통을 위해 외부에 공개하는 메시지의 집합을 퍼블릭 인터페이스라고 부른다.
프로그래밍 언어의 관점에서 퍼블릭 인터페이스에 포함된 메시지를 오퍼레이션(operation)이라고 부른다. 오퍼레이션은 수행 가능한 어떤 행동에 대한 추상화다.
P180
UML 용어로 말하자면, 인터페이스의 각 요소는 오퍼레이션이다. 오퍼레이션은 구현이 아닌 추상화다.
오퍼레이션(또는 메서드)의 이름과 파라미터 목록을 합쳐 시그니처(signature) 라고 부른다. 오퍼레이션은 실행 코드 없이 시그니처만을 정의한 것이다.
하지만 다형성의 축복을 받기 위해서는 하나의 오퍼레이션에 대해 다양한 메서드를 구현해야만 한다.
P181
중요한 것은 객체가 수신할 수 있는 메시지가 객체의 퍼블릭 인터페이스와 그 안에 포함될 오퍼레이션을 결정한다는 것이다. 객체의 퍼블릭 인터페이스가 객체의 품질을 결정하기 때문에 결국 메시지가 객체의 품질을 결정한다고 할 수 있다.
최소한의 인터페이스는 꼭 필요한 오퍼레이션만을 인터페이스에 포함한다.
P183
이처럼 협력하는 객체의 내부 구조에 대한 결합으로 인해 발생하는 설계 문제를 해결하기 위해 제안된 원칙이 바로 디미터 법칙(Law of Demeter) 이다. 디미터 법칙을 간단하게 요약하면 객체의 내부 구조에 강하게 결합되지 않도록 협력 경로를 제한하라는 것이다. 디미터 법칙은 “낯선 자에게 말하지 말라(dont’t talk to strangers)[Larman04]” 또는 "오직 인접한 이웃하고만 말하라(only talk to your immediate neighbors)[Metz12]"로 요약할 수 있다. 자바나 C#과 같이 '도트(.)'를 이용해 메시지 전송을 표현하는 언어에서는 "오직 하나의 도트만 사용하라(use only one dot)[Metz12]"라는 말로 요약되기도 한다.
P184
위 설명이 이해하기 어렵다면 클래스 내부의 메서드가 아래 조건을 만족하는 인스턴스에만 메시지를 전송하도록 프로그래밍해야 한다라고 이해하도 무방하다[Larman 2004].
- this 객체
- 메서드의 매개변수
- this의 속성
- this의 속성인 컬렉션의 요소
- 메서드 내에서 생성된 지역 객체
@ vincent
P185
디미터 법칙을 따르면 부끄럼타는 코드(shy code) 를 작성할 수 있다[Hunt99]. 부끄럼타는 코드란 불필요한 어떤 것도 다른 객체에게 보여주지 않으며, 다른 객체의 구현에 의존하지 않는 코드를 말한다. 디미터 법ㅍ칙을 따르는 코드는 메시지 수신자의 내부 구조가 전송자에게 노출되지 않으며, 메시지 전송자는 수신자의 내부 구현에 결합되지 않는다. 따라서 클라이언트와 서버 사이에 낮은 결합도를 유지할 수 있다.
디미터 법칙은 캡슐화를 다른 관점에서 표현한 것이다. 디미터 법칙이 가치 있는 이유는 클래스를 캡슐화하기 위해 따라야하는 구체적인 지침을 제공하기 때문이다.
다음은 디미터 법칙을 위반하는 코드의 전형적인 모습을 표현한 것이다.
screening.getMovie().getDiscountConditions();
메시지 전송자가 수신자의 내부 구조에 대해 물어보고 반환받은 요소에 대해 연쇄적으로 메시지를 전송한다. 흔히 이와 같은 코드를 기차 충돌(train wreck) 이라고 부르는데 여러 대의 기차가 한 줄로 늘어서 충돌한 것처럼 보이기 때문이다.[Martin08].
P186
디미터 법칙을 따르도록 코드를 개선하면 메시지 전송자는 더 이상 메시지 수신자의 내부 구조에 관해 묻지 않게 된다. 단지 자신이 원하는 것이 무엇인지를 명시하고 단순히 수행하도록 요청한다.
screening.calculateFee(audienceCount);
디미터 법칙은 훌륭한 메시지는 객체의 상태에 관해 묻지 말고 원하는 것을 시켜야 한다는 사실을 강조한다. 묻지 말고 시켜라(Tell, Don’t Ask) 는 이런 스타일의 메시지 작성을 장려하는 원칙을 가리키는 용어다.
P187
협력을 설계하고 객체가 수신할 메시지를 결정하는 매 순간 묻지 말고 시켜라 원칙과 디미터 법칙을 머릿속에 떠올리는 것은 퍼블릭 인터페이스의 품질을 향상시킬 수 있는 좋은 습관이다.
인터페이스는 객체가 어떻게 하는지가 아니라 무엇을 하는지를 서술해야 한다.
P190
그러므로 수행 방법에 관해서는 언급하지 말고 결과와 목적만을 포함하도록 클래스와 오퍼레이션의 이름을 부여하라.
P191
객체에게 묻지 말고 시키되 구현 방법이 아닌 클라이언트의 의도를 드러내야 한다. 이것이 이해하기 쉽고 유연한 동시에 협력적인 객체를 만드는 가장 기본적인 요구사항이다.
P192
근본적으로 디미터 법칙을 위반하는 설계는 인터페이스와 구현의 분리 원칙 을 위반한다.
P196
지금까지 살펴본 것처럼 디미터 법칙과 묻지 말고 시켜라 스타일을 따르면 자연스럽게 자율적인 객체로 구성된 유연한 협력을 얻게 된다.
따라서 클라이언트의 의도가 분명하게 드러나도록 객체의 퍼블릭 인터페이스를 개선해야 한다.
P197
오퍼레이션의 이름은 협력이라는 문맥을 반영해야 한다. 오퍼레이션은 클라이언트가 객체에게 무엇을 원하는지를 표현해야 한다. 다시 말해 객체 자신이 아닌 클라이언트의 의도를 표현하는 이름을 가져야 한다.
우리는 결합도가 낮으면서도 의도를 명확히 드러내는 간결한 협력을 원한다. 디미터 법칙과 묻지 말고 시켜라 스타일, 의도를 드러내는 인터페이스가 우리를 도울 것이다.
P198
잊지 말아야 하는 사실은 설계가 트레이드오프의 산물이라는 것이다. 설계를 적절하게 트레이드오프 할 수 있는 능력이 숙련자와 초보자를 구분하는 가장 중요한 기준이라고 할 수 있다. 초보자는 원칙을 맹목적으로 추종한다. 심지어 적용하려는 원칙들이 서로 충돌하는 경우에도 원칙에 정당성을 부여하고 억지로 끼워 맞추려고 노력한다.
원칙이 현재 상황에 부적합하다고 판단된다면 과감하게 원칙을 무시하라.
P199
하나 이상의 도트(.)를 사용하는 모든 케이스가 디미터 법칙 위반인 것은 아니다. 기차 충돌처럼 보이는 코드라도 객체의 내부 구현에 대한 어떤 정보도 외부로 노출하지 않는다면 그것은 디미터 법칙을 준수한 것이다.
P202
로버트 마틴은 <클린코드>[Martin08]에서 디미터 법칙의 위반 여부는 묻는 대상이 객체인지, 자료 구조인지에 달려있다고 설명한다. 객체는 내부 구조를 숨겨야 하므로 디미터 법칙을 따르는 것이 좋지만 자료 구조라면 당연히 내부를 노출해야 하므로 디미터 법칙을 적용할 필요가 없다.
명령(Command) 과 쿼리(Query) 는 객체의 인터페이스 측면에서 프로시저와 함수를 부르는 또 다른 이름이다. 객체의 상태를 수정하는 오퍼레이션을 명령이라고 부르고 객체와 관련된 정보를 반환하는 오퍼레이션을 쿼리라고 부른다. 따라서 개념적으로 명령은 프로시저와 동일하고 쿼리는 함수와 동일하다.
P203
어떤 오퍼레이션도 명령인 동시에 쿼리여서는 안 된다. 따라서 명령과 쿼리를 분리하기 위해서는 다음의 두 가지 규칙을 준수해야 한다.
- 객체의 상태를 변경하는 명령은 반환값을 가질 수 없다.
- 객체의 정보를 반환하는 쿼리는 상태를 변경할 수 없다.
명령-쿼리 분리 원칙을 한 문장으로 표현하면 "질문이 답변을 수정해서는 안 된다"는 것이다. 명령은 상태를 변경할 수 있지만 상태를 반환해서는 안 된다. 쿼리는 객체의 상태를 반환할 수 있지만 상태를 변경해서는 안 된다.
P209
안타깝게도 대부분의 사감들은 isStatisfied 메서드가 부수효과를 가질 것이라고 예상하지 못할 것이다. 사실 isSatisfied 메서드가 처음 구현됐을 때는 그 안에서 reschedule 메서드를 호출하는 부분이 빠져 있었다. 기능을 추가하는 과정에서 누군가 Event가 RecurringSchedule의 조건에 맞지 않을 경우 Event의 상태를 수정해야 한다는 요구사항을 추가했고, 프로그래머는 별다른 생각 없이 기존에 있던 isSatisfied 메서드에 reschedule 메서드를 호출하는 코드를 추가해 버린 것이다.
명령과 쿼리를 뒤섞으면 실행 결과를 예측하기가 어려워 질 수 있다.
P210
그 결과, 코드는 예측 가능하고 이해하기 쉬우며 디버깅이 용이한 동시에 유지보수가 수월해질 것이다.
P211
쿼리는 객체의 상태를 변경하지 않기 때문에 몇 번이고 반복적으로 호출하더라도 상관이 없다.
수학에서 함수는 입력이 동일하면 결과 역시 항상 동일해야 하기 때문이다.
컴퓨터의 세계와 수학의 세계를 나누는 가장 큰 특징은 부수효과(side effect) 의 존재 유무다.
P212
부수효과를 이야기할 때 빠질 수 없는 것이 바로 참조 투명성[Meyer00] 이다. 참조 투명성이란 "어떤 표현식 e가 있을 때 e의 값으로 e가 나타나는 모든 위치를 교체하더라도 결과가 달라지지 않는 특성"을 의미한다.
수학은 참조 투명성을 엄격하게 준수하는 가장 유명한 체계다.
P214
따라서 함수형 프로그래밍에서는 참조 투명성의 장점을 극대화할 수 있으며 명령형 프로그래밍에 비해 프로그램의 실행 결과를 이해하고 예측하기가 더 쉽다.
P215
버트란드 마이어는 이런 문제를 해결하기 위해 계약에 의한 설계(Design By Contract) 개념을 제안했다. 계약에 의한 설계는 협력을 위해 클라이언트와 서버가 준수해야 하는 제약을 코드 상에 명시적으로 표현하고 강제할 수 있는 방법이다.