Intro
이번 5회차 스터디는 강남역 7번 출구 부근 강남311에서 진행했습니다.
오피스텔 안을 사용하는 것인데, 역시 전문 스터디 룸이 아니라서 그런지 커스텀하게 이쁘게 잘 꾸며져있었고 안락했고 저렴했습니다.
다만 원룸 오피스텔이라서 일반 주민들이 거주하기 때문에 복도에서 소음을 주의해야 한다는 점은 주의했어야 했고, 오피스텔이라서 찾아가기 편했습니다.
잘 꾸며져 있어서 퇴실할 때 방을 사진 찍고 나오려고 했는데 깜빡하고 그냥 나왔네요. 링크에서 사진 확인하시면 될 것 같습니다.
이번 독서회는 1회차 이후 오랜만에 5인 전원이 참석한 독서회였어서 시간을 알차게 사용했습니다.
독후감 문장
133P
데이터 중심 설계로 인해 발생하는 문제점을 해결할 수 있는 가장 기본적인 방법은 데이터가 아닌 책임에 초점을 맞추는 것이다.
책임에 초점을 맞춰서 설계할 때 직면하는 가장 큰 어려움은 어떤 객체에게 어떤 책임을 할당할지를 결정하기가 쉽지 않다는 것이다. 책임 할당 과정은 일종의 트레이드오프 활동이다. 동일한 문제를 해결할 수 있는 다양한 책임 할당 방법이 존재하며, 어떤 방법이 최선인지는 상황과 문맥에 따라 달라진다. 따라서 올바른 책임을 할당하기 위해서는 다양한 관점에서 설계를 평가할 수 있어야 한다.
134P
객체에게 중요한 것은 데이터가 아니라 외부에 제공하는 행동이다. 클라이언트의 관점에서 객체가 수행하는 행동이란 곧 객체의 책임을 의미한다. 객체는 협력에 참여하기 위해 존재하며 협력 안에서 수행하는 책임이 객체의 존재가치를 증명한다.
데이터는 객체가 책임을 수행하는 데 필요한 재료를 제공할 뿐이다.
너무 이른 시기에 데이터에 초점을 맞추면 객체의 캡슐화가 약화되기 때문에 낮은 응집도와 높은 결합도를 가진 객체들로 넘쳐나게 된다. 그 결과로 얻게 되는 것은 변경에 취약한 설계다.
가장 기본적인 해결 방법은 객체를 설계하기 위한 질문의 순서를 바꾸는 것이다.
반면 책임 중심의 설계에서는 "이 객체가 수행해야 하는 책임은 무엇인가"를 겨정한 후에 "이 책임을 수행하는 데 필요한 데이터는 무엇인가"를 결정한다.
135P
협력을 시작하는 주체는 메시지 전송자이기 때문에 적합한 책임이란 메시지 수신자가 아니라 메시지 전송자에게 적합한 책임을 의미한다.
다시 말해서 메시지를 전송하는 클라이언트의 의도에 적합한 책임을 할당해야 한다는 것이다.
객체가 메시지를 선택하는 것이 아니라 메시지가 객체를 선택하게 해야 한다[Metz12].
"이 클래스가 필요하다는 점은 알겠는데 이 클래스는 무엇을 해야 하지?"라고 질문하지 않고 "메시지를 전송해야 하는데 누구에게 전송해야 하지?"라고 질문하는 것.
136P
메시지를 먼저 결정하기 때문에 메시지 송신자는 메시지 수신자에 대한 어떠한 가정도 할 수 없다. 메시지 전송자의 관점에서 메시지 수신자가 깔끔하게 캡슐화되는 것이다.
책임 주도 설계의 핵심은 책임을 결정한 후에 책임을 수행할 객체를 결정하는 것이다.
137P
GRASP 패턴은 이런 지식을 서로 공유하고 쉽게 토의할 수 있도록 이름을 붙여 놓은 것이다.
설계를 시작하기 전에 도메인에 대한 개략적인 모습을 그려 보는 것이 유용하다.
따라서 어떤 책임을 할당해야 할 때 가장 먼저 고민해야 하는 유력한 후보는 바로 도메인 개념이다.
138P
설계를 시작하는 단계에서는 개념들의 의미와 관계가 정확하거나 완벽할 필요가 없다. 단지 우리에게는 출발점이 필요할 뿐이다.
중요한 것은 설계를 시작하는 것이지 도메인 개념들을 완벽하게 정리하는 것이 아니다.
필요한 것은 도메인을 그대로 투영한 모델이 아니라 구현에 도움이 되는 모델이다. 다시 말해서 실용적이면서도 유용한 모델이 답이다.
139P
따라서 객체에게 책임을 할당하는 첫 번째 원칙은 책임을 수행할 정보를 알고 있는 객체에게 책임을 할당하는 것이다.
책임을 수행하는 객체가 정보를 ‘알고’ 있다고 해서 그 정보를 '저장’하고 있을 필요는 없다.
어떤 방식이건 정보 전문가가 데이터를 반드시 저장하고 있을 필요는 없다는 사실을 이해하는 것이 중요하다.
140P
만약 스스로 처리할 수 없는 작업이 있다면 외부에 도움을 요청해야 한다. 이 요청이 외부로 전송해야 하는 새로운 메시지가 되고, 최종적으로 이 메시지가 새로운 객체의 책임으로 할당된다. 이 같은 연쇄적인 메시지 전송과 수신을 통해 협력 공동체가 구성되는 것이다.
143P
다시 말해 두 협력 패턴 중에서 높은 응집도와 낮은 결합도를 얻을 수 있는 설계가 있다면 그 설계를 선택해야 한다는 것이다.
144P
LOW COUPLING 패턴과 HIGH COHESION 패턴은 설계를 진행하면서 책임과 협력의 품질을 검토하는 데 사용할 수 있는 중요한 평가 기준이다.
145P
이미 결합돼 있는 객체에게 생성 책임을 할당하는 것은 설계의 전체적인 결합도에 영향을 미치지 않는다.
그리고 협력과 책임이 제대로 동작하는지 확인할 수 있는 유일한 방법은 코드를 작성하고 실행해 보는 것뿐이다.
올바르게 설계하고 있는지 궁금한가? 코드를 작성하라.
147P
여기서 중요한 것은 Screening이 Movie의 내부 구현에 대한 어떤 지식도 없이 전송할 메시지를 결정했다는 것이다. 이처럼 Movie의 구현을 구려하지 않고 필요한 메시지를 결정하면 Movie의 내부 구현을 깔끔하게 캡슐화할 수 있다.
Screening은 Movie와 협력하기 위해 calculateMovieFee 메시지를 전송한다. Movie는 이 메시지에 응답하기 위해 calculateMovieFee 메서드를 구현해야 한다.
151P
가장 큰 문제점은 변경에 취약한 클래스를 포함하고 있다는 것이다.
152P
따라서 낮은 응집도가 초래하는 문제를 해결하기 위해서는 변경의 이유에 따라 클래스를 분리해야 한다
지금까지 살펴본 것처럼 일반적으로 설계를 개선하는 작업은 변경의 이유가 하나 이상인 클래스를 찾는 것으로부터 시작하는 것이 좋다.
코드를 통해 변경의 이유를 파악할 수 있는 첫 번째 방법은 인스턴스 변수가 초기화되는 시점을 살펴보는 것이다.
클래스의 속성이 서로 다른 시점에 초기화되거나 일부만 초기화된다는 것은 응집도가 낮다는 증거다. 따라서 함께 초기화되는 속성을 기준으로 코드를 분리해야 한다
@Vincent @muki4742 @아메리카노 @콘크리트장인
모든 메서드가 객체의 모든 속성을 사용한다면 클래스의 응집도는 높다고 볼 수 있다.
154P
클래스를 분리하면 앞에서 언급했던 문제점들이 모두 해결된다.
하지만 안타깝게도 클래스를 분리한 후에 새로운 문제가 나타났다.
155P
클래스를 분리한 후에 설계의 관점에서 전체적인 결합도가 높아진 것이다.
156P
두 클래스가 할인 여부를 판단하기 위해 사용하는 방법이 서로 다르다는 사실은 Movie 입장에서는 그다지 중요하지 않다.
역할을 대체할 클래스들 사이에서 구현을 공유해야 할 필요가 있다면 추상 클래스를 사용하면 된다. 구현을 공유할 필요 없이 역할을 대체하는 객체들의 책임만 정의하고 싶다면 인터페이스를 사용하면 된다.
158P
프로그램을 if ~ else 또는 switch ~ case 등의 조건 논리를 사용해서 설계한다면 새로운 변화가 일어난 경우 조건 논리를 수정해야 한다. 이것은 프로그램을 수정하기 어렵고 변경에 취약하게 만든다. POLYMORPHISM 패턴은 객체의 타입을 검사해서 타입에 따라 여러 대안들을 수행하는 조건적인 논리를 사용하지 말라고 경고한다. 대신 다형성을 이용해 새로운 변화를 다루기 쉽게 확장하라고 권고한다.
159P
"설계에서 변하는 것이 무엇인지 고려하고 변하는 개념을 캡슐화하라[GOF94]"라는 객체지향의 오랜 격언은 PROTECTED VARIATIONS 패턴의 본질을 잘 설명해준다. 우리가 캡슐화해야 하는 것은 변경이다. 변경이 될 가능성이 높은가? 그렇다면 캡슐화하라.
하나의 클래스가 여러 타입의 행동을 구현하고 있는 것처럼 보인다면 클래스를 분해하고 POLYMORPHISM 패턴에 따라 책임을 분산시켜라. 예측 가능한 변경으로 인해 여러 클래스들이 불안정해진다면 PROTECTED VARIATIONS 패턴에 따라 안정적인 인터페이스 뒤로 변경을 캡슐화하라.
162P
클래스는 작고 오직 한 가지 일만 수행한다. 책임은 적절하게 분배돼 있다. 이것이 책임을 중심으로 협력을 설계할 때 얻을 수 있는 혜택이다.
163P
설계를 주도하는 것은 변경이다.
대부분의 경우에 전자가 더 좋은 방법이지만 유사한 변경이 반복적으로 발생하고 있다면 복잡성이 상승하더라도 유연성을 추가하는 두 번째 방법이 더 좋다.
165P
이 도메인 모델은 도메인에 포함된 개념과 관계뿐만 아니라 도메인이 요구하는 유연성도 정확하게 반영한다.
166P
아무것도 없는 상태에서 책임과 협력에 관해 고민하기 보다는 일단 실행되는 코드를 얻고 난 후에 코드 상에 명확하게 드러나는 책임들을 올바른 위치로 이동시키는 것이다.
주로 객체지향 설계에 대한 경험이 부족한 개발자들과 페어 프로그래밍을 할 때나 설계의 실마리가 풀리지 않을 때 이런 방법을 사용하는데 생각보다 훌륭한 설계를 얻게 되는 경우가 종종 있다.
이처럼 이해하기 쉽고 수정하기 쉬운 소프트웨어로 개선하기 위해 겉으로 보이는 동작은 바꾸지 않은 채 내부 구조를 변경하는 것을 리팩터링(Refactoring) 이라고 부른다.[Fowler99a]
168P
긴 메서드는 다양한 측면에서 코드의 유지 보수에 부정적인 영향을 미친다.
한마디로 말해서 긴 메서드는 응집도가 낮기 때문에 이해하기도 어렵고 재사용하기도 어려우며 변경하기도 어렵다. 마이클 페더스(Michael Feathers)는 이런 메서드를 몬스터 메서드(monster method)[Feathers04]라고 부른다.
주석을 추가하는 대신 메서드를 작게 분해해서 각 메서드의 응집도를 높여라.
169P
뽑아내는 것이 코드를 더욱 명확하게 하면 새로 만든 메서드의 이름이 원래 코드의 길이보다 길어져도 뽑아낸다.[Fowler99a]
171P
이렇게 조그마한 부분에서 개선된 명확성이 모여 변경하기 쉬운 코드가 만들어진다.
174P
여기서 하고 싶은 말은 책임 주도 설계 방법에 익숙하지 않다면 일단 데이터 중심으로 구현한 후 이를 리팩터링하더라도 유사한 결과를 얻을 수 있다는 것이다. 처음부터 책임 주도 설계 방법을 따르는 것보다 동작하는 코드를 작성한 후에 리팩터링하는 것이 더 훌륭한 결과물을 낳을 수도 있다. 캡슐화, 결합도, 응집도를 이해하고 훌륭한 객체지향 원칙을 적용하기 위해 노력한다면 책임 주도 설계 방법을 단계적으로 따르지 않더라도 유연하고 깔끔한 코드를 얻을 수 있을 것이다.
@Vincent @freebear @muki4742 @아메리카노 @콘크리트장인
이번 챕터에서는 책임 주도 설계에 대해서 이론적으로 자세한 기준을 공부할 수 있었습니다.
모두가 객체지향에 대해 조금 더 이해할 수 있었고, 객체지향의 책임 주도 설계라는 것이 일상 생활에서도 비유될 수 있는 개념으로 확장도 가능했습니다.
기업에서 JD를 만든다던지, 기술의 민주화를 구성한다 같은 개념들이었습니다.
GRASP(General Responsibility Assignment Software Pattern) 이라는 새로운 단어도 등장했지만 다들 단어에 집중하고 현혹되지 않고 그 맥락을 잘 파악하기 위해서, 결국 그 패턴이 어떤 것을 의미하는지 구성원 모두가 이해할 수 있었던 것 같아서 뜻 깊었습니다.
이번 챕터는 다들 이론이 매우 중요하다고 생각해서 문장을 요약하지 못하고 문단 자체를 통째로 지정했기 때문에 타이핑하고 정리하는데 애먹었지만 의미있었다고 생각합니다.
6장에는 드디어 책임 그 자체라고 할 수 있는 인터페이스가 등장합니다. 다같이 다음 챕터를 기대하며 독서회를 마쳤습니다.