오브젝트 1장 객체, 설계 요약
서론 #
- 실무가 이론보다 먼저다.
- 실무를 하면서 관찰한 결과를 바탕으로 이론이 정립된다.
- 소프트웨어 분야는 아직 걸음마 단계에 머물러 있기 때문에 이론보다 실무가 더 앞서있음.
- 소프트웨어 유지보수의 경우 그 격차가 더 심하다.
- 실무에서는 다양한 규모의 소프트웨어를 유지보수하고 있지만, 소프트웨어 유지보수에 관련된 효과적인 이론은 발표되지 않았다.
- 소프트웨어 설계와 유지보수에 중점을 두려면 이론이 아닌 실무에 초점을 맞추는 것이 효과적이다.
- 추상적인 개념과 이론은 훌륭한 코드를 작성하는데 필요한 도구일 뿐이다.
01. 티켓 판매 애플리케이션 구현 #
https://github.com/soso01/object/tree/main/chapter1/1-1
02. 무엇이 문제인가? #
- 로버트 마틴의 소프트웨어 모듈이 가져야 하는 세 가지 기능
- 실행 중에 제대로 동작하는 것
- 변경이 용이 할 것
- 이해하기 쉬울 것
- 1-1 챕터의 코드는 제대로 동작하지만, 변경 용이성과 읽는 사람과의 의사소통이라는 목적을 만족시키지 못함.
예상을 빗나가는 코드 #
- 관람객과 판매원이 극장의 통제를 받는 수동적인 존재이다.
- 티켓을 꺼내 관람객의 가방에 집어넣고, 관람객에게 받은 돈을 매표소에 적립하는 행동을 극장이 수행한다.
- 이해 가능한 코드란 그 동작이 우리의 예상에서 크게 벗어나지 않는 코드다.
- 위의 예시는 일반 상식과 다르게 동작하기 때문에 코드를 읽는 사람과 제대로 소통하지 못함.
- 극장에 모든 세부적인 내용이 한번에 들어가기 때문에 판매원, 관람객의 세부사항을 극장에서 알고 있어야 한다.
변경에 취약한 코드 #
- 의존성은 변경에 대한 영향을 암시한다.
- 의존성이라는 말 속에는 어떤 객체가 변경될 때 그 객체에게 의존하는 다른 객체도 함께 변경될 수 있다는 사실이 내포됨
- 객체 사이의 의존성이 과한 경우 결합도가 높다고 말한다.
- 두 객체 사이의 결합도가 높으면 높을수록 함께 변경될 확률도 높아지기 때문에 변경하기 어려워진다.
- 설계의 목표는 객체 사이의 결합도를 낮춰 변경이 용이한 설계를 만드는 것이어야 한다.
03. 설계 개선하기 #
https://github.com/soso01/object/tree/main/chapter1/1-3
- 코드를 이해하기 어려운 이유는 극장에서 관람객의 가방과 판매원의 매표소에 직접 접근하기 때문이다.
- 관람객과 판매원이 자신의 일을 스스로 처리해야 한다는 우리의 직관을 벗어난다.
- 의도를 정확하게 의사소통하지 못하기 때문에 코드가 이해하기 어려워진 것이다.
- 극장이 관람객과 판매원의 너무 세세한 부분까지 알지 못하도록 정보를 차단하자.
- 관람객과 판매원을 자율적인 존재로 만든다.
자율성을 높이자 #
- 개념적이나 물리적으로 객체 내부의 세부적인 사항을 감추는 것을 캡슐화라 한다.
- 캡슐화의 목적은 변경하기 쉬운 객체를 만드는 것이다.
- 캡슐화를 통해 객체 내부로의 접근을 제한하면 객체와 객체 사이의 결합도를 낮출 수 있기 때문에 설계를 좀 더 쉽게 변경할 수 있게 된다.
- 객체를 인터페이스와 구현으로 나누고 인터페이스만을 공개하는 것은 객체 사이의 결합도를 낮추고 변경하기 쉬운 코드를 작성하기 위해 따라야 하는 가장 기본적인 설계 원칙이다.
수정 사항 #
- 수정된 관람객과 판매원은 자신이 가지고 있는 소지품을 스스로 관리한다.
- 이는 우리의 예상과 일치하게 작동하는 코드다.
- 읽는 사람과의 의사소통이라는 관점에서 확실히 개선되었다.
- 관람객과 판매원의 내부 구현을 변경하더라도 극장의 코드를 함께 변경할 필요가 없어졌다.
- 객체의 자율성을 높이는 방향으로 설계를 개선하여, 이해하기 쉽고 유연한 설계를 얻을 수 있었다.
캡슐화와 응집도 #
- 핵심은 객체 내부의 상태를 캡슐화하고 객체 간에 오직 메세지를 통해서만 상호작용하도록 만드는 것이다.
- 밀접하게 연관된 작업만을 수행하고 연관성 없는 작업은 다른 객체에게 위임하는 객체를 가리켜 응집도가 높다고 말한다.
- 자신의데이터를 스스로 처리하는 자율적인 객체를 만들면 결합도를 낮출 수 있을뿐더러 응집도를 높일 수 있다.
- 객체의 응집도를 높이기 위해서는 객체 스스로 자신의 데이터를 책임져야 한다.
- 자신이 소유하고 있지 않은 데이터를 이용해 작업을 처리하는 객체에게 어떻게 연관성 높은 작업들을 할당할 수 있겠는가?
- 외부의 간섭을 최대한 배제하고 메세지를 통해서만 협력하는 자율적인 객체들의 공동체를 만드는 것이 훌륭한 객체지향 설계를 얻을 수 있는 지름길이다.
절차지향과 객체지향 #
-
프로세스와 데이터를 별도의 모듈에 위치시키는 방식을 절차적 프로그래밍이라고 부른다.
-
절차적 프로그래밍의 세상은 우리의 예상을 쉽게 벗어나기 때문에 코드를 읽는 사람과 원활하게 의사소통하지 못한다.
- 절차적 프로그래밍의 세계에서 관람객과 판매원은 수동적인 존재
-
절차적 프로그래밍의 세상에서는 데이터의 변경으로 인한 영향을 지역적으로 고립시키기 어렵다.
- 따라서 변경하기 어려운 코드를 양산하는 경향이 있다.
-
변경하기 쉬운 설계는 한 번에 하나의 클래스만 변경할 수 있는 설계다.
- 절차적 프로그래밍은 프로세스가 필요한 모든 데이터에 의존해야 한다는 근본적인 문제점 때문에 변경에 취약할 수 밖에 없다.
-
데이터와 프로세스가 동일한 모듈 내부에 위치하도록 하는 프로그래밍 방식을 객체지향 프로그래밍이라고 부른다.
- 자신의 데이터를 스스로 처리하도록 극장의 프로세스를 판매원과 관람객으로 이동
-
훌륭한 객체지향 설계의 핵심은 캡슐화를 이용해 의존성을 적절히 관리함으로써 객체 사이의 결합도를 낮추는 것이다.
책임의 이동 #
- 두 방식 사이에 근본적인 차이를 만드는 것은 책임의 이동이다.
- 여기서 책임은 기능을 가리키는 객체지향 세계의 용어로 생각해도 무방함.
- 절차지향 방식에서는 책임이 극장에 집중되어 있었음.
- 그에 반해 객체지향 설계에서는 제어 흐름이 각 객체에 적절하게 분산된다.
- 극장에 몰려있던 책임이 각 개별 객체로 이동한 것.
- 객체지향 설계에서는 독재자가 존재하지 않고 각 객체에 책임이 적절하게 분배된다.
- 따라서 각 객체는 자신을 스스로 책임진다.
- 객체지향 애플리케이션은 스스로 책임을 수행하는 자율적인 객체들의 공동체를 구성함으로써 완성된다.
- 사실 객체지향 설계의 핵심은 적절한 객체에 적절한 책임을 할당하는 것이다.
- 다른 객체와의 협력이라는 문맥 안에서 특정한 역할을 수행하는 데 필요한 적절한 책임을 수행해야 한다.
- 객체가 어떤 데이터를 가지느냐 보다는 객체에 어떤 책임을 할당할 것이냐에 초점을 맞추자
- 설계를 어렵게 만드는 것은 의존성이다.
- 불필요한 의존성을 제거함으로써 객체 사이의 결합도를 낮추자.
더 개선하기 #
- 관람객이 소지하고 있는 가방과, 판매원의 판매소 객체에 자율권을 가지도록 수정함.
- 자율권을 주기 위해 기존에 없었던 의존성이 생겨날 수 있다.
- 결합도와 자율성 모두를 만족시키는 방법이 없다면 트레이드 오프를 해야 한다.
- 이 예제로 알 수 있는 것 두가지
- 어떤 기능을 설계하는 방법은 한 가지 이상이다.
- 동일한 기능을 한 가지 이상의 방법으로 설계할 수 있기 때문에 결국 설계는 트레이드오프의 산물이다.
그래, 거짓말이다 #
- 직관에 따르는 코드는 이해하기 더 쉽다.
- 하지만 이후에 수정한 가방과 판매소는 실세계에서 자율적인 존재가 아니므로 어색하게 느껴진다.
- 코드에서 무생물 역시 스스로 행동하고 자기 자신을 책임지는 자율적인 존재로 취급한 것이다.
- 비록 현실에서는 수동적인 존재라고 하더라도 일단 객체지향의 세계에 들어오면 모든 것이 능동적이고 자율적인 존재로 바뀐다. - 의인화
- 훌륭한 객체지향 설계란 소프트웨어를 구성하는 모든 객체들이 자율적으로 행동하는 설계를 가리킨다.
- 그 대상이 비록 생명이 없는 수동적인 존재라고 생각하더라도 객체지향의 세계로 넘어오는 순간 그들은 생명과 지능을 가진 싱싱한 존재로 다시 태어난다.
04. 객체지향 설계 #
설계가 왜 필요한가 #
- 설계란 코드를 배치하는 것이다.
- 어떤 사람들은 설계가 코드를 작성하는 것보다 높은 차원의 창조적인 행위라고 생각한다.
- 하지만 설계와 구현을 떨어뜨려서 이야기하는 것은 불가능하다.
- 설계는 코드를 작성하는 매 순간 코드를 어떻게 배치할 것인지를 결정하는 과정에서 나온다.
- 설계는 코드 작성의 일부이며 코드를 작성하지 않고서는 검증할 수 없다.
- 좋은 설계란 무엇인가?
- 우리는 오늘 완성해야 하는 기능을 구현하는 코드를 짜야 하는 동시에 내일 쉽게 변경할 수 있는 코드를 짜야한다.
- 좋은 설계란 오늘 요구하는 기능을 온전히 수행하면서 내일의 변경을 매끄럽게 수용할 수 있는 설계다.
- 변경을 수용할 수 있는 설계가 중요한 또 다른 이유는 코드를 변경할 때 버그가 추가될 가능성이 높기 때문이다.
- 요구사항 변경은 필연적으로 코드 수정을 초래하고, 코드 수정은 버그가 발생할 가능성을 높인다.
- 버그의 가장 큰 문제점은 코드를 수정하려는 의지를 꺾는다.
객체지향 설계 #
- 우리가 진정으로 원하는 것은 변경에 유연하게 대응할 수 있는 코드다.
- 객체지향 프로그래밍은 의존성을 효율적으로 통제할 수 있는 다양한 방법을 제공함으로써 요구사항 변경에 좀 더 수월하게 대응할 수 있는 가능성을 높여준다.
- 변경 가능한 코드란 이해하기 쉬운 코드다.
- 객체지향 패러다임은 여러분이 세상을 바라보는 방식대로 코드를 작성할 수 있게 돕는다.
- 단순히 데이터와 프로세스를 객체라는 덩어리 안으로 밀어 넣었다고 해서 변경하기 쉬운 설계를 얻을 수 있는 것은 아니다.
- 객체지향의 세계에서 애플리케이션은 객체들로 구성되며 애플리케이션의 기능은 객체들 간의 상호작용을 통해 구현된다.
- 객체들 사이의 상호작용은 객체 사이에 주고 받는 메시지로 표현된다.
- 훌륭한 객체지향 설계란 협력하는 객체 사이의 의존성을 적절하게 관리하는 설계다.
- 객체가 실행되는 주변 환경에 강하게 결합될수록 변경하기 어려워진다.
- 객체 간의 의존성은 애플리케이션을 수정하기 어렵게 만드는 주범이다.
- 데이터와 프로세스를 하나의 덩어리로 모드는 것은 훌륭한 객체지향 설계로 가는 첫걸음일 뿐이다.
- 진정한 객체지향 설계로 나아가는 길은 협력하는 객체들 사이의 의존성을 적절하게 조절함으로써 변경에 용이한 설계를 만드는 것이다.