코드 없는 개발이야기 #9
최근 회사를 옮기면서 그동안 정리해둔 글들을 다시 훑어보게 됐다.
예전에 적어두고 그냥 지나쳤던 글들 사이에서
유난히 눈에 띄는 글이 하나 있었다.
객체지향과 설계에 대해 나름 날카롭게 고민하던 시기의 글이었다.
돌아보면, 객체지향과 도메인 주도 개발에 대해 생각하고 실험해 온 시간이
어느새 9년 정도 된 것 같다.
그동안 일하는 환경도 바뀌고, 역할도 바뀌고, 관점도 조금씩 달라졌지만
설계에 대한 고민만큼은 계속 이어져 왔다.
그래서 이참에 그때 적어두었던 내용들을 다시 정리해 두려고 한다.
소프트웨어를 만든다는 건 결국 변화와 싸우는 일이다.
요구사항은 움직이고, 기능은 늘어나고, 기술은 어느 날 갑자기 새로운 패러다임을 들고 찾아온다.
이 끊임없는 변화 속에서 우리가 붙잡을 수 있는 것은 무엇일까.
나는 이렇게 생각한다.
좋은 설계란 변화의 흔들림을 최소화하는 구조를 만드는 일이라고.
변화를 두려워하지 않는 코드,
변화의 방향이 바뀌어도 의연하게 버틸 수 있는 구조,
그것이 우리가 설계라는 단어에서 기대하는 모습이다.
객체지향에서 가장 먼저 묻게 되는 질문은 단순하다.
“이 객체는 왜 존재하는가?”
이 질문에 답하지 못하는 순간, 코드는 목적을 잃고 방황하기 시작한다.
따로 존재하던 개념이 서로 뒤엉키고,
중복이 생기며,
시간이 지날수록 손을 대기 어려운 형태로 굳어진다.
절차지향 코드 속 반복되는 패턴,
여러 함수에 흩어진 비슷한 문장들,
그곳에는 항상 추상화될 기회가 숨어 있다.
추상화는 보통 위에서 내려오는 것이 아니라
아래에서부터 조용히 발견된다.
Top-Down 설계보다 Bottom-Up 리팩토링이 더 현실적인 이유다.
코드를 직접 만지며, 그 안의 의미를 발견하고 정리해가는 과정—
그 과정 속에서 진짜 객체지향이 모습을 드러낸다.
좋은 이름은 코드를 설명하지 않는다.
의도를 드러낸다.
메서드를 설계할 때 기준은 늘 “사용자”여야 한다.
호출하는 사람은 어떤 목적을 가지고 있을까?
이 행동을 어떤 맥락에서 쓰게 될까?
이 관점이 흐려지는 순간,
코드는 개발자마다 다르게 해석되기 시작하고
리팩토링을 할수록 혼란은 더 커진다.
코드는 기술이 아니라 언어다.
따라서 이름은 문장이어야 하고,
문장은 독자의 관점에서 쓰여야 한다.
어떤 구조든 결국 변화를 나누기 위한 도구다.
Controller와 Repository는 데이터 중심의 단순한 흐름을 다루기에 절차지향이 더 명확하다.
반면 Domain은 다르다.
도메인은 언제나 가장 자주 바뀌고,
가장 복잡하고,
가장 많은 의도를 품고 있다.
그래서 이 영역은 객체지향적이어야 한다.
개념을 명확히 하고,
책임을 나누고,
변화를 견딜 수 있는 형태로 구조화해야 한다.
Hexagonal도, Layered도 결국 목적은 같다.
서로 다른 변화의 종류를 분리해,
우리가 감당해야 할 인지적 부담을 줄이기 위해서다.
많은 개념들이 수직의 레이어 구조로만 설명되지만
실제 시스템은 훨씬 다차원적이다.
도메인 간의 수평축, 기능의 흐름, 비즈니스 규칙의 변화 같은
여러 축이 서로 교차하며 하나의 구조를 이룬다.
이 복잡함 속에서 우리가 붙잡을 수 있는 개념이 있다.
바로 계약(Contract)과 다형성(Polymorphism)이다.
다형성의 핵심은 ‘서로 바꿔 끼울 수 있음’이 아니라
“같은 계약을 지키는 다른 행동들”이다.
LSP가 이야기하는 것도 결국 신뢰다.
약속을 지키는 객체끼리는 서로를 대체할 수 있고,
그 대체 가능성이 시스템의 유연성을 만든다.
Value Object는 단순한 데이터 묶음이 아니다.
도메인의 언어를 만드는 작은 단위다.
VO가 가지는 힘은 세 가지다.
의미를 이름으로 드러낸다.
타입 이름만으로도 맥락이 보인다.
타입이 불필요한 유연성을 제한한다.
오용되는 것을 막고, 구조를 단단하게 만든다.
행위를 품는다.
데이터가 아니라 ‘행동을 하는 값’이 된다.
행위를 갖지 않은 VO는 DTO에 불과하다.
모델링의 섬세함은 VO에서 드러난다.
도메인 서비스는 종종 오해받지만, 역할은 단순하다.
도메인 객체들이 혼자 맡기 어려워하는 책임을 대신 처리하는 것.
하지만 이때 지켜야 할 경계가 있다.
도메인 서비스가 또 다른 도메인 서비스를 호출하면 구조가 흐려진다.
Repository가 이곳으로 들어오면 의도가 뒤섞인다.
애그리게이트와 도메인 서비스는 함께 하나의 ‘집합’을 이뤄야 한다.
도메인 서비스는 만능 도구가 아니라,
경계가 어긋나지 않도록 균형을 맞추는 조심스러운 역할이어야 한다.
추상화는 불필요한 것을 지우는 과정이 아니라
의도와 맥락을 더 선명하게 드러내는 과정이다.
리팩토링도 마찬가지다.
코드를 새로 쓰는 것이 아니라,
코드 속에 숨어 있던 개념을 발견하고 재배치하는 일이다.
그리고 이 과정은 생각보다 기술보다
대화와 커뮤니케이션 비용이 훨씬 크다.
좋은 설계는 결국 좋은 대화에서 나온다.
둘 중 하나를 선택하는 문제가 아니다.
서로 다른 역할을 가진 도구일 뿐이다.
단순 데이터 흐름 → 절차지향이 더 자연스럽다.
자주 바뀌는 핵심 도메인 → 객체지향이 진짜 힘을 발휘한다.
절차지향 코드는 통합 테스트로도 충분하고,
객체지향 코드는 단위 테스트가 뒷받침되어야 한다.
설계는 ‘철학’이 아니라
‘필요에 맞는 선택’이다.
좋은 설계는 코드에서만 나오지 않는다.
사람 사이의 상호작용에서 나온다.
누구나 질문할 수 있고, 가설을 던질 수 있는 팀.
의견이 부딪히더라도 안전한 분위기.
그런 팀이 좋은 소프트웨어를 만든다.
객체지향도, 추상화도, 리팩토링도
모두 대화에서 시작된다.
SRP(Single Responsibility Principle)를 제대로 이해하려면
먼저 변화를 바라봐야 한다.
변화하지 않는 것은 묶고,
변하는 것은 분리해야 한다.
그 기준이 곧 SRP다.
그리고 기억해야 한다.
RDD의 책임은 “행동을 맡는 주체로서의 역할”
SRP의 책임은 “변화의 이유를 분리하는 것”
이 둘은 같은 단어를 쓰지만 전혀 다른 개념이다.
객체지향은 언어의 문법도, 패턴 카탈로그도 아니다.
변화를 관리하고, 의미를 드러내며, 사람과 코드가 함께 이해할 수 있는 구조를 만드는 사고 방식이다.
좋은 코드는 결국 좋은 대화에서 태어난다.
그리고 좋은 팀은 그 대화를 멈추지 않는다.
나는 이렇게 믿는다.
객체지향이란, 사람과 코드가 함께 이해할 수 있는 세계를 만드는 일이다.