본문 바로가기

개발서적

객체지향의 사실과 오해 - 조영호(2)

 유튜브에서 이 책을 추천하는 영상을 봐서, 궁금했었던 책입니다. 평소에도 도대체 객체지향이 뭐지?라는 의문과 의문을 넘어선 답답함이 있었습니다. 이 책을 통해 객체지향이 무엇인지 어느정도 개념이 잡힌 것 같습니다. 책을 읽으면서 인상적인 문장들과 글들을 정리하였습니다. 생략된 부분이 많기 때문에 더 자세히 알고 싶은 분들은 아래 링크를 참조하면 좋을 것 같습니다.

 

https://product.kyobobook.co.kr/detail/S000001628109

 

객체지향의 사실과 오해 | 조영호 - 교보문고

객체지향의 사실과 오해 | 객체지향에 대한 선입견을 버려라!『객체지향의 사실과 오해』는 객체지향이란 무엇인가라는 원론적면서도 다소 위험한 질문에 답하기 위해 쓰여진 책이다. 안타깝

product.kyobobook.co.kr

 

 

 


 

 

 

5. 책임과 메시지

 자율적인 책임의 특징은 객체가 ‘어떻게(how)’ 해야 하는가가 아니라 ‘무엇(what)’을 해야 하는가를 설명한다는 것이다. 객체지향 프로그래밍에서 행동은 수행할 책임을 지닌 객체에게 전송된 메시지에 의해 시작된다. 메시지행동에 대한 요청을 표현하고, 요청을 수행하는 데 필요한 추가적인 정보를 인자를 통해 전달한다. 수신자는 메시지를 수신하는 객체를 가리킨다. 수신자가 메시지를 받아들인다는 것은 해당 행동을 수행할 책임을 받아들인다는 것을 의미한다. 객체는 메시지에 대한 응답으로 요청을 만족하기 위한 어떤 메서드를 수행할 것이다.

 

 클래스가 코드를 구현하기 위해 사용할 수 있는 중요한 추상화 도구인 것은 사실이지만 객체지향의 강력함은 클래스가 아니라 객체들이 주고받는 메시지로부터 나온다. 객체지향 애플리케이션은 클래스를 이용해 만들어지지만 메시지를 통해 정의된다. 실제로 애플리케이션을 살아있게 만드는 것은 클래스가 아니라 객체다. 그리고 이런 객체들의 윤곽을 결정하는 것이 바로 객체들이 주고받는 메시지이다.

 

 클래스는 단지 동적인 객체들의 특성과 행위를 정적인 텍스트로 표현하기 위해 사용할 수 있는 추상화 도구일 뿐이다. 중요한 것은 클래스가 아니라 객체다. 클래스를 정의하는 것이 먼저가 아니라 객체들의 속성과 행위를 식별하는 것이 먼저다. 클래스는 객체의 속성과 행위를 담는 틀일 뿐이다. 심지어는 클래스를 사용하지 않고도 객체의 속성과 행위를 표현할 수도 있다.

 

 책임-주도 설계 방법에서 역할, 책임, 협력을 식별하는 것은 애플리케이션이 수행하는 기능을 시스템의 책임으로 보는 것으로부터 시작된다. 시스템이 수행할 책임을 구현하기 위해 협력 관계를 시작할 적절한 객체를 찾아 시스템의 책임을 객체의 책임으로 할당한다. 객체가 책임을 완수하기 위해 다른 객체의 도움이 필요하다고 판단되면 도움을 요청하기 위해 어떤 메시지가 필요한지 결정한다. 메시지를 결정한 후에는 메시지를 수신하기에 적합한 객체를 선택한다. 수신자는 송신자가 메시지를 보내면서 기대한 바를 충족시켜야 한다. 즉, 수신자는 송신자가 기대한 대로 메시지를 처리할 책임이 있다. 결과적으로 메시지가 수신자의 책임을 결정한다.

 

 책임-주도 설계의 핵심은 어떤 행위가 필요한지를 먼저 결정한 후에 이 행위를 수행할 객체를 결정하는 것이다. 이 과정을 흔히 What-Who 사이클이라고 한다. What-Who 사이클은 어떤 객체가 필요한지를 생각하지 말고 어떤 메시지가 필요한지를 먼저 고민하라고 조언한다. 메시지를 결정하기 전까지는 객체에 관해 고민하지 말아야 한다. 일단 메시지가 결정된 후에야 이 메시지를 처리할 객체를 선택한다.

 

 객체 설계의 핵심은 객체를 두 개의 분리된 요소(인터페이스와 구현)로 분할해 설계하는 것이다. 그것은 바로 외부에 공개되는 인터페이스와 내부에 감춰지는 구현이다. 인터페이스와 구현의 분리 원칙이 왜 중요한가? 그것은 소프트웨어는 항상 변경되기 때문이다.

 

 

6. 객체 지도

 

 모든 소프트웨어 제품의 설계에는 두 가지 측면이 존재한다. 하나는 기능(function)측면의 설계이고, 다른 하나는 구조(structure)측면의 설계. 기능 측면의 설계는 제품이 사용자를 위해 무엇을 할 수 있는지에 초점을 맞춘다. 구조 측면의 설계는 제품의 형태가 어떠해야 하는지에 초점을 맞춘다. 설계의 가장 큰 도전은 기능과 구조라는 두 가지 측면을 함께 녹여 조화를 이루도록 만드는 것이다.

 

 미래에 대비하는 가장 좋은 방법은 변경을 예측하는 것이 아니라 변경을 수용할 수 있는 선택의 여지를 설계에 마련해 놓는 것이다. 훌륭한 설계자는 미래에 구체적으로 어떤 변경이 발생할 것인지를 예측하지 않는다. 훌륭한 설계자는 미래에 구체적으로 어떤 변경이 발생할 것인지를 예측하지 않는다.

 좋은 설계는 나중에라도 변경할 수 있는 여지를 남겨 놓는 설계다. 설계를 하는 목적은 나중에 설계하는 것을 허용하는 것이며, 설계의 일차적인 목표는 변경에 소요되는 비용을 낮추는 것이다.

 

 일반적으로 기능을 수집하고 표현하기 위한 기법을 유스케이스 모델링이라고 하고 구조를 수집하고 표현하기 위한 기법을 도메인 모델링이라고 한다. 쉽게 예상할 수 있는 것처럼 두 가지 모델링 활동의 결과물을 각각 유스케이스도메인 모델이라고 한다.

 

 도메인 모델이란 사용자가 프로그램을 사용하는 대상 영역에 관한 지식을 선택적으로 단순화하고 의식적으로 구조화한 형태다. 도메인 모델은 소프트웨어가 목적하는 영역 내의 개념과 개념 간의 관계, 다양한 규칙이나 제약 등을 주의 깊게 추상화한 것이다. 도메인 모델은 소프트웨어 개발과 관련된 이해관계자들이 도메인에 대해 생각하는 관점이다.

 

 최종 코드는 사용자가 도메인을 바라보는 관점을 반영해야 한다. 이것은 곧 애플리케이션이 도메인 모델을 기반으로 설계돼야 한다는 것을 의미한다. 도메인 모델이란 사용자들이 도메인을 바라보는 관점이며, 설계자가 시스템의 구조를 바라보는 관점인 동시에 소프트웨어 안에 구현된 코드의 모습 그 자체이기 때문이다.

 

 소프트웨어 객체는 그 대상이 현실적인지, 현실적이지 않은지에 상관없이 도메인 모델을 통해 표현되는 도메인 객체들을 은유해야 한다. 이것이 도메인 모델이 중요한 이유다. 도메인 모델을 기반으로 설계하고 구현하는 것은 사용자가 도메인을 바라보는 관점을 그대로 코드에 반영할 수 있게 한다. 결과적으로 표현적 차이는 줄어들 것이며, 상요자의 멘탈 모델이 그대로 코드에 녹아 스며들게 될 것이다.

 

 표면적 차이(소프트웨어 객체와 현실 객체 사이의 의미적 거리)가 중요한 이유는 소프트웨어를 이해하고 수정하기 쉽게 만들어주기 때문이다. 코드의 구조가 도메인의 구조를 반영하기 때문에 도메인을 이해하면 코드를 이해하기가 훨씬 수월해진다.

 

 도메인 모델을 기반을 코드를 작성하는 두 번째 이유는 도메인 모델이 제공하는 구조가 상대적으로 안정적이기 때문이다.

 사용자 모델에 포함된 개념과 규칙은 변경될 확률이 적기 때문에 사용자 모델을 기반으로 설계와 코드를 만들면 변경에 쉽게 대처할 수 있을 가능성이 커진다.

 

 비록 도메인 모델이 도메인과 관련된 중요한 개념과 관계를 보여준다고 해도 실제로 사용자에게 중요한 것은 도메인 모델이 아니라 소프트웨어의 기능이다. 소프트웨어의 존재 이유는 사용자가 원하는 목표를 달성할 수 있는 다양한 기능을 제공하는 것이다.

 

 사용자의 목표를 달성하기 위해 사용자와 시스템 간에 이뤄지는 상호작용의 흐름을 텍스트로 정리한 것을 유스케이스라고 한다. 유스케이스는 시스템의 이해관계자들 간의 계약을 행위 중심으로 파악한다.

 유스케이스는 이해관계자들 중에서 일차 액터라 불리는 행위자의 요청에 대한 시스템의 응답으로서, 다양한 조건하에 있는 시스템의 행위를 서술한다. 일차 액터는 어떤 목표를 달성하기 위해 시스템과의 상호작용을 시작한다. 시스템은 모든 이해관계자들의 요구에 응답하고 이해관계를 보호해야 한다. 특별한 요청과 관계되는 조건에 따라 서로 다른 일련의 행위 혹은 시나리오가 전개될 수 있다. 유스케이스는 이렇게 서로 다른 시나리오를 묶어 준다.

 

 유스케이스의 가치는 사용자들의 목표를 중심으로 시스템의 기능적인 요구사항들을 이야기 형식으로 묶을 수 있다는 점이다. 산발적으로 흩어져 있는 기능에 사용자 목표라는 문맥을 제공함으로써 각 기능이 유기적인 관계를 지닌 체계를 이룰 수 있게 한다. 이것은 요구사항을 기억하고 관리하는 데 필요한 다양한 정신적 과부하를 줄인다.

 

 유스케이스는 단순한 피처 목록과 다르다. 피처의 단점은 두 피처를 서로 연관이 없는 독립적인 기능으로 보이게끔 한든다는 점이다. 유스케이스는 이야기를 통해 연관된 기능들을 함께 묶어 문맥을 알 수 있게 한다.

 

 불안정한 기능을 안정적인 구조 안에 담음으로써 변경에 대한 파급효과를 최소화하는 것은 훌륭한 객체지향 설계자가 갖춰야 할 기본적인 설계 능력이다. 도메인 모델은 안정적인 구조를 개념화하기 위해, 유스케이스는 불안정한 기능을 서술하기 위해 가장 일반적으로 사용되는 도구다. 변경에 유연한 소프트웨어를 만들기 위해서는 유스케이스에 정리된 시스템의 기능을, 도메인 모델을 기반으로 한 객체들의 책임으로 분배해야 한다.

 

 

7. 함께 모으기

 

 메시지를 처리할 객체를 찾고 있다면 먼저 도메인 모델 안에 책임을 수행하기에 적절한 타입이 존재하는지 살펴보라. 적절한 타입을 발견했다면 책임을 수행할 객체를 그 타입의 인스턴스로 만들어라. 소프트웨어 객체에게 현실 객체와 유사한 이름을 붙여 놓으면 유사성을 통해 소프트웨어 객체가 수행해야 하는 책임과 상태를 좀 더 쉽게 유추할 수 있다.

 

 구현하지 않고 머릿속으로만 구상한 설계는 코드로 구현하는 단계에서 대부분 변경된다. 설계 작업은 구현을 위한 스케치를 작성하는 단계지 구현 그 자체일 수는 없다. 중요한 것은 설계가 아니라 코드다. 따라서 협력을 구성하는 단계에 너무 오랜 시간을 쏟지 말고 최대한 빨리 코드를 구현해서 설계에 이상이 없는지, 설계가 구현 가능한지를 판단해야 한다. 코드를 통한 피드백 없이는 깔끔한 설계는 얻을 수 없다.

 

 객체의 속성이 캡슐화된다는 이야기는 인터페이스에는 객체의 내부 속성에 대한 어떤 힌트도 제공돼서는 안 된다는 것을 의미한다. 이를 위한 가장 훌륭한 방법은 인터페이스를 정하는 단계에서는 객체가 어떤 속성을 가지는지, 또 그 속성이 어떤 자료 구조로 구현됐는지를 고려하지 않는 것이다. 객체에게 책임을 할당하고 인터페이스를 결정할 때는 가급적 객체 내부의 구현에 대한 어떤 가정도 하지 말아야 한다.

 

 인터페이스는 객체가 다른 객체와 직접적으로 상호작용하는 통로다. 인터페이스를 통해 실제로 상호작용해보지 않은 채 인터페이스의 모습을 정확하게 예측하는 것은 불가능에 가깝다.

 

 설계를 간단히 끝내고 최대한 빨리 구현에 돌입하라. 머릿속에 객체의 협력 구조가 번뜩인다면 그대로 코드를 구현하기 시작하라. 설계가 제대로 그려지지 않는다면 고민하지 말고 실제로 코드를 작성해가면서 협력의 전체적인 밑그림을 그려보라. 테스트-주도 설계로 코드를 구현하는 사람들이 하는 작업이 바로 이것이다. 그들은 테스트 코드를 작성해 가면서 협력을 설계한다.