도서 스터디 후기 “객체지향의 사실과 오해”
도서 스터디 후기 “객체지향의 사실과 오해”
『객체지향의 사실과 오해』 스터디를 마치고
2025년 4월 24일부터 6월 26일까지, 약 두 달에 걸쳐 총 8회에 걸쳐 조영호 님의 『객체지향의 사실과 오해』를 함께 읽는 스터디를 진행했다. 매 회차는 정해진 분량을 읽어오고, 그 장에서 눈에 띄는 개념을 공유한 뒤 "이걸 지금 우리 실무에 어떻게 적용해볼 수 있을까"를 행동 계획으로 남기는 방식으로 진행됐다.
이 책을 붙잡은 이유는 개인적인 의문에서 시작됐다. 여러 개발자를 만나며 "객체지향 패러다임을 이용해 컴포넌트 설계를 한다"는 말을 했을 때, "프론트엔드 개발자가 객체지향을 쓸 일이 있냐"는 반응을 자주 들었다.
그 말을 들을 때마다 나는 객체지향이 패러다임이라면 사용하는 언어가 무엇이든 상관없어야 하는 게 아닌가 생각했고, 어쩌면 내가 아직 객체지향을 제대로 몰라서 이 질문에 답하지 못하는 게 아닐까 하는 생각을 자주 했다.
이 책은 그 의문에 상당히 명확한 답을 주었다. 아래는 8회의 세션에서 다룬 내용을 책이 전개하는 개념의 흐름을 따라 다시 정리한 것이다.
클래스 상속이 아니라, 역할과 책임과 협력
1장 "협력하는 객체의 공동체"는 스터디 전체를 통틀어 가장 인상 깊었던 장이었다.
객체지향의 중심은 클래스나 상속이 아니라 객체의 역할, 책임, 협력이라는 것.
이 관점은 항상 변화를 요구받는 소프트웨어 개발에서 유연하게 대응할 수 있게 해주는 핵심 개념이라고 느꼈다. 상속 구조를 얼마나 잘 짰는지가 아니라, 객체들이 서로 어떤 역할을 맡고 어떤 책임을 지며 어떻게 협력하는지가 설계의 본질이라는 것이다.
이 관점 하나로 "프론트엔드에도 객체지향이 필요한가"라는 오래된 질문에 대한 답이 명확해졌다. 언어나 상속 여부와 무관하게, 협력하는 단위를 역할과 책임으로 나눌 수 있다면 그것이 곧 객체지향적 사고다.
상태보다 행동이 먼저다
2장 "이상한 나라의 객체"를 학습하며 얻은 인사이트는 실무에 가장 직접적으로 적용해본 개념이었다.
핵심은 "상태 ⇒ 행동"이 아니라 "행동 ⇒ 상태"라는 순서다. 그동안 객체지향적으로 설계한다고 하면서도 늘 비슷한 문제에 부딪혔던 이유가 바로 이 순서를 거꾸로 가져갔기 때문이라는 걸 깨달았다.
항상 상태를 먼저 만들고 그다음에 행동을 붙였던 것이다. 반대로 행동을 먼저 정의하면, 그 행동에 대한 책임이 각 객체에 주어지고 그 책임에 따라 필요한 상태만 자연스럽게 결정된다. 그러면 의미 없는 상태가 애초에 생기지 않는다.
이 관점을 태스크 타이머 컴포넌트에 대입해봤다.
상태를 먼저 생각하면
- "타이머 객체(시작 시간/현재 시간/총 시간)"
- "태스크 객체(총 시간/태스크 이름/태스크 개수)"
처럼 데이터 구조부터 나열하게 된다.
반대로 행동을 먼저 생각하면
- "태스크 이름을 입력한다", "타이머를 시작한다", "타이머를 끝낸다"
- "태스크를 저장한다(태스크 이름, 총 시간)"
는 행동이 먼저 나오고, 그 행동들을 수행하기 위해 필요한 최소한의 상태만 남는다.
- 타이머 객체(현재 시간)
- 태스크 객체(태스크 이름, 총 시간)
타입과 추상화: 책임이 타입을 결정한다
3장은 "행동에 따라 객체를 분류한다"는 문장으로 요약할 수 있다.
여기서 캡슐화는 결국 "감추는 것"이고, 책에서 예로 든 지하철 노선도 설계가 그 감각을 잘 보여준다.
노선도는 실제 지리 정보를 있는 그대로 드러내지 않고 필요한 정보만 추상화해서 보여주는데, 객체도 마찬가지로 내부를 감추고 외부에는 행동만 노출한다.
행동에 따라 객체를 분류하는 절차는 이렇게 이어진다. 외부에서 제공해야 할 행동을 먼저 생각하고, 그 행동에 대한 책임을 먼저 설계한다. 이것이 책임 주도 설계다.
실무 예시로 결제 컴포넌트를 그려봤다.
여기서도 "은행을 선택할 수 있다", "전화번호를 입력할 수 있다", "다음으로 넘어갈 수 있다"는 외부에 제공하는 행동이 먼저 정의되고, 그 행동들이 컴포넌트의 타입을 결정한다.
결국 객체가 어떤 타입에 속하느냐는 그 객체가 수행하는 행동으로 결정된다는 것이 3장의 결론이었다.
역할, 책임, 협력
4장은 도출한 도서관 책 대출 시스템의 행동 목록을 가지고 실제로 역할, 책임, 협력을 그려보는 것으로 시작했다
협력은 객체와 객체 간의 상호작용이고, 이 상호작용에는 반드시 책임이 따른다.
책임은 두 가지로 나뉜다.
- 하는 것(다른 객체의 행동을 시작·제어·조절하는 것)
- 아는 것(정보, 관련 객체, 해야 하는 행동에 대해 아는 것)이다.
역할은 단순성, 유연성, 재사용성을 뒷받침하는 핵심 개념인데, 여기서 중요한 건 객체가 항상 구체적인 주체가 아니라 하나의 역할을 추상화한다는 점이다.
협력을 추상화하면 추상적인 역할에서 추상적인 협력으로 이어지고, 이 추상적인 협력자는 언제든 다른 객체로 대체 가능하다는 대체 가능성을 얻는다.
아르바이트생이라는 역할은 어떤 인간이든 채울 수 있는 것처럼, 역할이란 결국 최소한의 조건, 즉 책임을 만족하기만 하면 누구든(어떤 객체든) 대신할 수 있는 자리다.
이 장에서는 객체지향 설계 기법도 함께 짚었다.
책임 주도 설계, 그리고 디자인 패턴(이미 만들어져 있는 객체지향적 설계도), TDD가 그것이다. 특히 TDD에 대한 것이 흥미로웠는데, TDD는 단순히 테스트를 작성하는 기법이 아니라 책임 주도 설계를 위한 기법이며, "TDD가 어렵다면 객체지향에 대한 이해가 부족한 것"이라는 문장이 인상적이었다.
TDD가 강조하는 "테스터블한 코드"란 결국 하나의 책임만 가진 컴포넌트를 만드는 것이고, 테스트가 어렵다는 건 여러 책임이 뒤섞여 있다는 신호라는 것이다.
그렇다면 테스터블한 컴포넌트는 곧 하나의 책임을 추상화한 객체이고, 테스트는 데이터를 주입했을 때 그 객체가 자신의 책임을 제대로 수행하는지를 확인하는 행위가 된다. 결국 "테스터블하다"는 것은 "책임 주도적으로 설계되어 있다"는 말과 다르지 않을 수 있다는 생각으로 이어졌다.
메시지가 객체를 만든다
5장 "메시지를 따라라"는 두 세션에 걸쳐 다뤘다.
먼저 메시지는 객체들의 의사소통 수단으로, "어떻게"가 아니라 "무엇"이 실행되기를 바라는지를 명시한다.
- 메서드는 그 메시지에 따라 수신자가 실제로 수행하는 행동이다.
- 다형성은 동일한 메시지에 대해 객체마다 다르게 반응하는 것을 말한다.
송신자 입장에서는 수신자가 누구인지 구별할 필요가 없다. 요청을 수행할 책임은 어떤 수신자든 동일하게 지고 있기 때문이다. 이걸 요리사에 비유하면 이해가 쉬웠다.
고객은 어떤 요리사가 응대하든 신경 쓰지 않고 주문(메시지)만 보내면 된다. 요리사는 대체 가능하고(대체 가능성), 요리사를 교체해도 고객은 알아채지 못하며(협력의 유연성), 요리사를 여러 명으로 늘릴 수도 있고(확장의 유연성), 고객이라는 역할 자체도 다른 상황에 재사용할 수 있다(재사용성).
이어지는 논의에서는 데이터 주도 설계와 책임 주도 설계를 대비시켰다.
책임 주도 설계에서는 메시지가 수신자의 책임을 결정한다.
이걸 what/who 사이클로 표현할 수 있는데, 먼저
- "무엇을 해야 하는가(what) → 메시지"를 정하고,
- 메시지가 정해진 다음에야 "누가 그 메시지를 수신할 것인가(who)"를 선택한다.
순서가 뒤바뀌면 안 된다는 게 핵심이다. 객체 자체에 먼저 초점을 맞추면 불필요한 메시지가 생길 수 있다는 경고도 함께 남겼다.
인터페이스, 그리고 구현과의 분리
메시지가 결정되면 그 메시지를 수신할 수 있는 공용 인터페이스가 결정되고, 인터페이스는 다시 어떤 메서드를 실행할지를 결정한다.
즉 "메시지 → 책임 결정 → 인터페이스 결정"의 흐름이다.
자동차 핸들에 비유하면 쉬운데, 운전자는 핸들(인터페이스)만 조작할 뿐 그 뒤에서 실제로 바퀴가 어떻게 꺾이는지(내부 구현)는 알 필요가 없다.
인터페이스는 수신할 수 있는 메시지 목록으로 구성된다.
여기서 인터페이스와 구현의 분리, 즉 구현 세부 사항의 캡슐화가 나온다.
이 개념을 도서관 대출 시스템에 대입하면서 재미있는 일이 있었다.
처음엔 "도서관리인"이 인터페이스라고 생각했는데, 다시 보니 도서관리인은 인터페이스라기보다는 인터페이스를 다루는 핸들러에 가까웠다. 오히려 검색, 대출, 반납이라는 기능을 추상화한 것 자체가 인터페이스였다.
인터페이스는 특정 객체가 아니라, 그 객체가 제공하는 행동의 집합이라는 점을 다시 확인한 지점이었다.
AS-IS
TO-BE
객체지도 그리기: 기능설계와 구조설계
6장은 "객체 지도 그리기"를 다뤘다. 여기서 두 가지 설계 접근법이 대비된다.
- 기능설계는 사람들에게 직접 길을 묻는 접근법으로, 기능이 구조에 우선하고 초점은 "사용자를 위해 무엇을 할 수 있는가"에 맞춰진다.
전통적인 기능 분해가 여기 속하는데, 자주 변경되는 기능을 중심으로 설계하고 구조가 그 기능을 따라가게 만들면, 시스템 기능이 점점 더 작은 기능으로 쪼개지면서 서로 밀접하게 얽힌 덩어리가 되어 변경에 취약해진다.
- 구조설계는 지도를 보고 길을 찾는 접근법으로, 기능이 구조를 따르고 초점은 "제품의 형태가 어떠해야 하는가"에 맞춰진다.
정리하면 구조는 사용자와 이해관계자들이 도메인에 관해 생각하는 개념과 개념들 간의 관계로 표현되고, 기능은 사용자의 목표를 만족시키기 위해 책임을 수행하는 행위다.
구조를 다루는 기법이 도메인 모델링이다.
도메인이란 사용자가 프로그램을 사용하는 대상 분야(예: 결제, 주문, 배송)를 말하고,
도메인 모델은 소프트웨어 개발과 관련된 이해관계자들이 그 도메인을 어떻게 생각하는지를 담은 관점으로, 사용자 모델(사용자), 디자인 모델(디자이너), 시스템 이미지(개발자)를 포괄해 추상화한 소프트웨어 모델이다.
여기서 중요한 개념이 표현적 차이다. 소프트웨어 객체와 현실 객체 사이의 의미적 거리를 뜻하는데, 이 거리를 최대한 줄이는 방법이 은유다.
"도서 반입"이라는 현실의 개념을 registerBook이라는 이름으로 옮기는 식이다. 표현적 차이를 줄이는 게 중요한 이유는, 도메인 개념이 코드에 그대로 박혀 있으면 소프트웨어를 이해하기 쉽고 수정하기도 쉬워지기 때문이다.
도메인 모델링만으로는 부족하고, 비즈니스의 개념과 정책을 반영하는 안정적인 구조 위에 소프트웨어의 기능을 서술하는 기법이 필요한데 그것이 유스케이스 모델링이다.
유스케이스는 사용자의 목표를 달성하기 위해 사용자와 시스템 사이에서 이뤄지는 상호작용의 흐름을 텍스트로 설계한 것이다.
예를 들어 "중도 해지 이자액을 계산한다"는 유스케이스가 있다면, 그 뒤에는 정기예금·이자·계좌·이자율이라는 도메인 모델이 자리한다.
도메인 모델은 팀원들이 함께 보고 의사소통하는 멘탈 모델 역할을 하며, 반대로 코드로부터 도메인 모델을 역으로 유추할 수도 있다.
유스케이스와 도메인 모델을 함께 갖춘 상태로 개발하면 "시스템의 책임은 무엇인가"라는 질문에는 유스케이스가, "은유한 이름과 안정적인 구조는 무엇인가"라는 질문에는 도메인 모델이 답을 준다.
이 흐름을 도서관 대출 시스템에 다시 적용해봤다. "도서를 검색하고 반납 처리한다", "도서를 검색하고 도서를 선택해 대출 기한을 입력한 후 대출을 신청한다"는 유스케이스를 뽑고, 그에 맞는 도메인 모델을 그려봤다.
이 장을 지나며 남긴 느낌이 스터디 전체에서 가장 실무적인 깨달음에 가까웠다. 평소 "도메인"이라는 단어를 다소 다르게, 얕게 이해하고 있었다는 걸 알게 됐고, 이제는 도메인이 무엇인지 명확히 보이기 시작했다.
그동안 개발하면서 생각의 리소스를 가장 많이 잡아먹었던 문제들 — 지금 다루는 서비스의 도메인은 무엇인지, 무엇을 기준으로 책임을 나눠야 하는지, 지금의 구조가 적합한지 — 가 이 개념 하나로 상당 부분 해소될 것 같다는 기대가 생겼다.
세 가지 관점 — 책임, 명세, 구현
7장에서는 하나의 설계를 바라보는 세 가지 관점을 다이어그램으로 정리했다.
책임 관점은 사용자가 도메인을 바라보는 관점이고, 명세 관점은 소프트웨어에 초점을 맞추되 객체들의 책임에 초점을 두는 관점이며, 구현 관점은 그 책임을 "어떻게" 수행할 것인지에 초점을 맞춘다.
구현해보지 않고 머릿속으로만 구상한 설계는 대부분 바뀐다는 전제 아래, 설계는 최대한 간단하게 잡고 최대한 빨리 구현해보는 편이 낫다. 테스트 주도 설계는 테스트 코드를 작성해가며 객체 간의 협력을 설계해 나가는 방식이고, 여기서도 가급적 내부 구현에 대한 어떤 가정도 하지 않고 협력과 책임만 놓고 설계하는 태도가 강조됐다.
이 장의 느낀 점은 프론트엔드 개발자로서의 고민과 바로 맞닿아 있었다.
프론트엔드는 백엔드 API에 대한 도메인 의존성이 꽤 높다. 도메인 관련 정보 대부분이 API 응답을 통해 그대로 쏟아지기 때문에, 백엔드 개발자와 도메인에 대한 이해를 함께 맞춰서 코드에 녹여내야 하는지가 여전히 어려운 문제로 남았다. 이론적으로는 API에 의존하지 않고 프론트엔드가 스스로 타입을 설계하고, API는 구현부처럼 취급해 설계에 영향을 주지 않게 만들 수도 있다.
그런데 실제 프로젝트에서는 스웨거 API 명세를 기반으로 API 호출 함수와 타입을 자동 생성하는 API 제너레이터를 사용하다 보니, 자동 생성된 백엔드 API 위주의 타입에 자연스럽게 의존하게 된다는 현실적인 한계도 짚었다.
책을 다 읽고 나니 타입이라는 것이 더 이상 타입 추론을 돕는 정도의 도구가 아니라, 추상화된 객체를 만들어내기 위한 상당히 중요한 도구로 느껴졌다. 타입을 통해 도메인을 설계에 최대한 담아낸 추상화 객체를 만들고, TDD를 이용해 빠르게 구현하면서 그 추상화 객체들 사이의 협력 관계가 명확한지를 입증할 수 있다는 생각에 이르렀다. 다만 이걸 지금 당장 실천하기에는 아직 많이 부족하다고 느꼈고, 타입스크립트와 객체지향의 관계는 앞으로도 계속 써보고 공부해야 할 주제로 남겨두었다.
마무리
8회차는 그동안 읽어온 내용을 정리해서 나누는 독서 공유 시간으로, 별도의 노션 페이지에서 진행되어 이 노트 자체에는 남은 기록이 없다. 8번의 세션을 거치며 매번 "이번 장에서 배운 걸 지금 우리 실무에 어떻게 적용할 수 있을까"라는 질문을 스스로에게 던졌던 게 이 스터디의 가장 큰 수확이었다고 생각한다.
특히 "상태보다 행동을 먼저 설계하라"는 2장의 조언과, "도메인을 명확히 이해하면 책임 분리와 구조 판단이 쉬워진다"는 6장의 깨달음은 이후 실제 코드를 짤 때도 계속 떠올리게 되는 문장들이었다.
객체지향을 언어나 상속의 문제로만 여겼던 예전의 생각은, 이 책을 통해 역할·책임·협력이라는 훨씬 근본적인 틀로 바뀌었다. 프론트엔드 개발자에게도 객체지향이 유효한가라는 처음의 질문에는, 이제 "언어가 아니라 협력하는 단위를 어떻게 나누느냐의 문제"라고 답할 수 있을 것 같다.
스터디 모임은 이 책을 마친 뒤 같은 저자의 다음 책인 『오브젝트』 스터디로 이어갔다.

