이 책은 이론에서 시작하지 않는다. 소극장 하나를 세우고, 초대장과 티켓과 현금이 오가는 아주 작은 거래를 코드로 옮기는 데서 출발한다. 조영호가 첫 장에서 던지는 질문은 하나다 — 똑같이 동작하는 두 코드가 있을 때, 무엇이 더 나은 설계인가. 답은 기능이 아니라 변경에 있다. 요구가 바뀔 때 흔들리는 코드와 견디는 코드를 가르는 것은 객체 사이의 의존성이다.

무대 뒤를 들여다보는 소극장

이벤트 당첨자는 초대장을, 나머지는 현금을 들고 온다. 입장하려면 티켓이 필요하다. 처음 떠오르는 대로 짜면 Theater가 모든 걸 통제한다. 관람객(Audience)의 가방을 열어 초대장이 있는지 확인하고, 없으면 가방에서 현금을 꺼내 매표소(TicketOffice)에 넣고 티켓을 꺼내 관람객에게 건넨다.

동작은 한다. 문제는 이 Theater가 관람객의 가방 내부와 매표소의 금고 내부를 속속들이 알고 있다는 것이다. 관람객이 현금이 아니라 신용카드로 결제하려 하면? Theater를 고쳐야 한다. 가방에 초대장 대신 모바일 QR을 넣으면? 또 Theater를 고쳐야 한다. 나와 상관없어야 할 변경이 나를 흔든다.

판단 기준: 어떤 클래스를 고쳐야 하는지가 “누구의 변경이냐”와 어긋난다면 의존성이 잘못 놓인 것이다. 함정: 처음 떠오르는 절차적 흐름은 대개 한 객체가 나머지의 내부를 훤히 아는 형태로 수렴한다 — 동작하니까 방치되기 쉽다.

자율적인 존재로 되돌리기

해법의 방향은 분명하다. 가방을 뒤지는 일은 가방을 가진 관람객이, 금고를 여는 일은 금고를 가진 판매원이 하도록 되돌린다. Theater는 “표를 팔아라”라고 요청만 하고, 내부 처리는 각자에게 맡긴다. 아래 스텝이 이 장의 전부다 — 절차적 원본에서 시작해 책임을 한 단계씩 제자리로 돌려보낸다.

Refactoring Step 절차적 원본 — Theater가 모든 내부를 뒤진다
1/3
public class Theater {    private TicketSeller ticketSeller;    public void enter(Audience audience) {        if (audience.getBag().hasInvitation()) {            Ticket ticket = ticketSeller.getTicketOffice().getTicket();            audience.getBag().setTicket(ticket);        } else {            Ticket ticket = ticketSeller.getTicketOffice().getTicket();            audience.getBag().minusAmount(ticket.getFee());            ticketSeller.getTicketOffice().plusAmount(ticket.getFee());            audience.getBag().setTicket(ticket);        }    }}// Theater가 남의 내부를 직접 연다 — getBag(), getTicketOffice()가 결합의 증거다.

무엇이 달라졌나. 마지막 상태에서 TicketSeller는 더 이상 audience.getBag()을 호출하지 않는다. 가방의 존재조차 모른다. 관람객이 카드로 결제하도록 바꾸고 싶으면 Bag(혹은 Audience)만 고치면 되고, TheaterTicketSeller는 그대로다. 캡슐화가 의존성을 끊었고, 끊긴 의존성이 변경의 파급을 막았다.

판단 기준: 한 객체가 다른 객체의 getX().getY()로 내부의 내부까지 파고든다면(디미터 법칙 위반), 그 사슬만큼 결합이 깊다는 뜻이다. 함정: 자율화가 지나치면 객체 수가 늘고 흐름을 눈으로 좇기 어려워진다 — 절차적 코드가 읽기 쉬워 보이는 것은 착각이 아니라 사실이며, 그 대가로 변경에 취약할 뿐이다.

설계가 좋다는 것은 무엇인가

두 코드는 같은 결과를 낸다. 그런데 하나는 요구가 바뀔 때 한 곳만 고치면 되고, 다른 하나는 무관해야 할 곳까지 번진다. 조영호의 결론은 담백하다 — 훌륭한 설계란 오늘의 기능을 만족시키면서 내일의 변경을 수용할 수 있는 코드다. 그리고 그 열쇠는 객체를 자율적인 존재로 대하는 것, 남의 내부를 들여다보지 않고 메시지로만 협력하게 하는 것이다.

  • 절차적 설계는 한 객체(Theater)에 데이터와 로직이 몰리고, 나머지는 수동적인 데이터 덩어리가 된다. 변경이 중앙으로 집중돼 취약하다.
  • 객체지향 설계는 데이터를 가진 객체에게 그 데이터를 다루는 책임까지 준다. 캡슐화가 자연히 따라오고, 의존성이 줄어든다.
  • 좋고 나쁨의 기준은 “동작하느냐”가 아니라 “변경에 어떻게 반응하느냐”다.

다음장으로 2장