오브젝트 독서회 4회차 - 4장 설계 품질과 트레이드오프

Intro

이번 4회차 스터디는 강남역 5번 출구 부근 더나은플레이스에서 진행했습니다.

건물의 지하 2층에 작은 투명한 문 하나가 있었고 문 안으로 들어가면 방이 3개있는 곳이었습니다.

전문 스터디 룸은 아니었고, 아늑하고 이쁜 공간에서 소규모로 미팅 같은 것을 할 수 있는 장소였습니다.

장소가 예뻐서 사진도 찍게 되었네요. (거울에 제 모습이 있는건 지금 올리다가 알았습니다.)
디퓨저도 있었고, 조용했습니다.

기존 전문 스터디 룸들 가격이 2시간 기준 4400원, 3000원이었던 것을 감안하면 이번 공간은 5000원이 었기 때문에 약간 비쌌지만, 그만큼 환경이 깔끔해서 이용 경험이 좋았습니다.

단점은 건물까지 찾아가는 게 어렵고, 건물에서도 공간 안내가 잘 되어있지 않았고, 들어가서도 어딘지 잘 알 수 없었다는 것입니다.

이번 스터디에는 @muki4742 님과 @freebear 님께서 불참하셨습니다.
4장부터는 구체적인 소스코드 예제를 통한 책임 기반 설계의 실제 리펙토링이 들어가기 때문에 어려운 내용이었고, 2명의 불참으로 시간이 많이 비었던만큼 4장의 내용에 대해서 나눔하기 적절했습니다.


독후감 문장

97P

결국 책임이 객체지향 애플리케이션 전체의 품질을 결정하는 것이다.

@아메리카노

객체지향 설계란 올바른 객체에게 올바른 책임을 할당하면서 낮은 결합도와 높은 응집도를 가진 구조를 창조하는 활동이다. 이 정의에는 객체지향 설계에 관한 두 가지 관점이 섞여 있다. 첫 번째 관점은 객체지향 설계의 핵심이 책임이라는 것이다. 두 번째 관점은 책임을 할당하는 작업이 응집도와 결합도 같은 설계 품질과 깊이 연관돼 있다는 것이다.

@은딩

설계는 변경을 위해 존재하고 변경에는 변경에는 어떤 식으로든 비용이 발생한다. 훌륭한 설계란 합리적인 비용 안에서 변경을 수용할 수 있는 구조를 만드는 것이다.

@Vincent @은딩

객체의 상태가 아니라 객체의 초점을 맞추는 것이다.

@아메리카노

객체를 단순한 데이터의 집합으로 바라보는 시각은 객체의 내부 구현을 퍼블릭 인터페이스에 노출시키는 결과를 낳기 때문에 결과적으로 설계가 변경에 취약해진다.

@Vincent

98P

가끔씩은 좋은 설계보다는 나쁜 설계를 살펴보는 과정에서 통찰을 얻기도 한다.

@은딩

데이터 중심의 관점에서 객체는 자신이 포함하고 있는 데이터를 조작하는 데 필요한 오퍼레이션을 정의한다. 책임 중심의 관점에서 객체는 다른 객체가 요청할 수 있는 오퍼레이션을 위해 필요한 상태를 보관한다.

@Vincent

데이터 중심의 관점은 객체의 상태에 초점을 맞추고 책임 중심의 관점은 객체의 행동에 초점을 맞춘다.

@아메리카노

객체의 상태는 구현에 속한다. 구현은 불안정하기 때문에 변하기 쉽다.

@Vincent

99P

그에 비해 객체의 책임은 인터페이스에 속한다.

@Vincent

책임 중심의 설계가 '책임이 무엇인가’를 묻는 것으로 시작한다면 데이터 중심의 설계는 객체가 내부에 저장해야 하는 '데이터가 무엇인가’를 묻는 것으로 시작한다.

@Vincent

106P

ReservationAgeny는 데이터 클래스들을 조합해서 영화 예매 절차를 구현하는 클래스다.

@Vincent

109P

객체를 설계하기 위한 가장 기본적인 아이디어는 변경의 정도에 따라 구현과 인터페이스를 분리하고 외부에서는 인터페이스에만 의존하도록 관계를 조절하는 것이다.

@은딩 @아메리카노

객체지향 설계의 가장 중요한 원리는 불안정한 구현 세부사항을 안정적인 인터페이스 뒤로 캡슐화하는 것이다.

@아메리카노

훌륭한 프로그래밍 기술을 적용해서 캡슐화를 향상시킬 수는 있겠지만 객체지향 프로그래밍을 통해 전반적으로 얻을 수 있는 장점은 오직 설계 과정 동안 캡슐화를 목표로 인식할 때만 달성될 수 있다[Wirfs-Brock89].

@아메리카노

설계가 필요한 이유는 요구사항이 변경되기 때문이고, 캡슐화가 중요한 이유는 불안정한 부분과 안정적인 부분을 분리해서 변경의 영향을 통제할 수 있기 때문이다.

@은딩

정리하면 캡슐화란 변경 가능성이 높은 부분을 객체 내부로 숨기는 추상화 기법이다. 객체 내부에 무엇을 캡슐화해야 하는가? 변경될 수 있는 어떤 것이라도 캡슐화해야 한다.

@Vincent

유지보수성이 목표다. 여기서 유지보수성이란 두려움 없이, 주저함 없이, 저항감 없이 코드를 변경할 수 있는 능력을 말한다.

@Vincent @은딩 @아메리카노

110P

일반적으로 좋은 설계란 높은 응집도와 낮은 결합도를 가진 모듈로 구성된 설계를 의미한다.

@Vincent

좋은 설계란 오늘의 기능을 수행하면서 내일의 변경을 수용할 수 있는 설계다.

@Vincent @은딩 @아메리카노

112P

이것은 "인터페이스에 대해 프로그래밍하라[GOF94]"라는 격언으로도 잘 알려져 있다.

@Vincent @아메리카노

기능적인 측면에서만 놓고 보면 이번 장에서 구현한 데이터 중심의 설계는 2장에서 구현한 책임 중심의 설게와 완전히 동일하다. 하지만 설계 관점에서는 완전히 다르다.

@Vincent

114P

설계할 때 협력에 관해 고민하지 않으면 캡슐화를 위반하는 과도한 접근자와 수정자를 가지게 되는 경향이 있다.

@은딩

앨런 홀럽(Allen Holub)은 이처럼 접근자와 수정자에 과도하게 의존하는 설계 방식을 추측에 의한 설계 전략(design-by-guessing strategy)[Holub04]이라고 부른다. 이 전략은 객체가 사용될 협력을 고려하지 않고 객체가 다양한 상황에서 사용될 수 있을 것이라는 막연한 추측을 기반으로 설계를 진행한다. 따라서 프로그래머는 내부 상태를 드러내는 메서드를 최대한 많이 추가해야 한다는 압박에 시달릴 수 밖에 없으며 결과적으로 대부분의 내부 구현이 퍼블릭 인터페이스에 그대로 노출될 수밖에 없는 것이다.

@Vincent

116P

데이터 중심의 설계는 전체 시스템을 하나의 거대한 의존성 덩어리로 만들어 버리기 때문에 어떤 변경이라도 일단 발생하고 나면 시스템 전체가 요동칠 수밖에 없다.

@아메리카노

117P

속성의 가시성을 private으로 설정했다고 해도 접근자와 수정자를 통해 속성을 외부로 제공하고 있다면 캡슐화를 위반하는 것이다.

@Vincent

119P

코드 중복은 악의 근원이다.

@Vincent

객체는 단순한 데이터 제공자가 아니다.

@Vincent

120P

다시 말해 새로운 데이터 타입을 만들 수 있는 것이다.

@Vincent

126P

내부 구현의 변경이 외부로 퍼져나가는 **파급 효과(ripple effect)**는 캡슐화가 부족하다는 명백한 증거다. 따라서 변경 후의 설계는 자기 자신을 스스로 처리한다는 점에서는 이전의 설계보다 분명히 개선됐지만 여전히 내부의 구현을 캡슐화하는 데는 실패한 것이다.

@Vincent @아메리카노

127P

calculateAmountDiscountedFee, calculatePercentDiscountedFee, calculateNoneDiscountedFee라는 세 개의 메서드는 할인 정책에는 금액 할인 정책, 비율 할인 정책, 미적용의 세 가지가 존재한다는 사실을 만천하에 드러내고 있다.

@Vincent

128P

다시 한번 강조하지만 캡슐화란 변할 수 있는 어떤 것이라도 감추는 것이다.

@아메리카노

132P

객체지향 애플리케이션을 구현한다는 것은 협력하는 객체들의 공동체를 구축한다는 것을 의미한다.

@은딩 @아메리카노


이번 장에서는 데이터 주도 설계에서 책임 주도 설계로 넘어가는 실질적인 소스코드 예제가 나왔습니다. 하지만 아직 추상화가 완벽하지 않다고 하면서 4챕터가 마무리됩니다.

이번 챕터는 실제적으로 변화하는 과정을 담고 있기 때문에 객체지향적이지 않은 것과 객체지향 적인 것과 차이의 중간과정을 볼 수 있는 챕터라고 생각하여 아주 중요하게 생각합니다.

챕터를 거듭해가면서 점점 더 고급스럽고 원칙에 입각한 설계가 나올 것이라고 기대가 됩니다.

6개의 좋아요

이번 챕터에서는 본문에 언급한대로 실질적인 데이터 주도 설계에서 책임 주도 설계의 과정이 나옵니다.

이번 챕터에 나온 이론적인 내용은 사실 1, 2, 3장에서 강조해서 소개했던 내용을 중심으로 풀어서 나왔기 때문에 중복되는 구절들이 많았습니다.

그렇기 때문에 이번 챕터의 핵심은 변화하는 소스코드에 있었고, 4장까지는 제가 이미 과거에 선행학습을 했었기 때문에 4장까지만 읽고도 이렇게 변화된 저를 경험할 수 있었던 만큼, 사전에 읽어오는 것이 중요했다고 생각해서 꼭 독서회 전에 읽어올 것을 독서회 리더로서 강조했던 챕터이기도 했습니다.

구성원 분들 중에 4장이 그저 1, 2, 3장 내용과 겹친다라고만 생각하고 소스코드를 가치를 낮게 보고 넘어가신 분이 혹시 계시다면, 꼭 소스코드의 변화를 오랫동안 들여다보시면서 그 과정을 지켜보고 이해했으면 좋겠습니다.


제가 이번에 가장 중요하게 생각했던 문장과 느낀점은 아래와 같습니다.

106P

ReservationAgency는 데이터 클래스들을 조합해서 영화 예매 절차를 구현하는 클래스다.

이 문장은 저자분께서 데이터 주도 설계를 예시로 보여주시면서 언급되는 문장입니다.

위 문장에서 데이터 클래스들을 조합 이라는 것에 포인트를 두고 싶은데요. 4장에서 예제 코드를 통해 소개하고 있듯, 데이터 주도 설계는 객체의 책임을 고려하지 않고, 어떤 프로세스를 처리할 때 어떤 데이터가 있으면 좋을까? 라고 먼저 초점을 맞추는데서 시작하기 때문입니다.

데이터에 초점을 맞추기 때문에 여러 데이터 클래스들을 미리미리 만들어두고 그것들을 하나의 클래스에 의존을 걸어 처리하기 때문입니다. 그러면 C#을 예제로 들 경우 많은 namespace와 class type이 하나의 클래스 파일에 의존이 걸리게 됩니다.

바로 이 부분에서 하나를 바꾸면 다 바꿔야하는 것이 안좋은 포인트라는 것입니다.

물론 Class 마다 역할이 다 있기 때문에 최종적으로는 Aggregation해서 Type들이 집중된 클래스가 생길 수는 있습니다. 책임 주도 설계를 할 때도 이 부분은 트레이드오프를 통해 자연스럽게 나오는 부분입니다.

다만 처음부터 그래서는 안 된다는 것입니다. 처음에 어떤 Domain을 머리 속으로 프로세스를 그려보며 소스코드로 Boilerplate를 구성해 갈 때는 어떤 행위가 필요할까 생각해보고, 어떤 책임들이 필요하며, 그 책임들을 담당할 객체들이 필요할까를 고민하고, 여기까지 비로소 고민이 마쳐졌을 때 class로 코딩하여 추상화를 하는 것입니다.

class를 만든다는 그 자체가 이미 개발자가 추상화하는 행위입니다. 아무 고민도 없이 막 만들어서는 안됩니다. 그렇기에 class의 이름에는 그 객체의 책임이 담겨있는 것이기에 네이밍이 중요하고 가장 어려운 부분일 것입니다. 그렇기에 함축적인 것보다는 할 수 있다면 길게 늘여서 쓰는 것입니다.

도메인의 성숙도와 디테일에 따라 다르고, class의 많다 적다의 기준을 명확히 할 수는 없겠지만, 생각을 충분하게 하지 않은 Boilerplate는 class의 개수가 적을 것이고, 그만큼 객체들이 1개당 1개의 책임이 아니라 여러개의 책임을 지니고 있다는 것일 것이고 이것은 SRP를 위반하는 것입니다.

따라서 최대한 원시타입으로 작성은 하되, OCP를 위반하지는 않도록 적절하게 추상화해야할 것입니다.


다른 하나의 문장은 이것입니다.

114P

앨런 홀럽(Allen Holub)은 이처럼 접근자와 수정자에 과도하게 의존하는 설계 방식을 추측에 의한 설계 전략(design-by-guessing strategy) [Holub04]이라고 부른다. 이 전략은 객체가 사용될 협력을 고려하지 않고 객체가 다양한 상황에서 사용될 수 있을 것이라는 막연한 추측을 기반으로 설계를 진행한다. 따라서 프로그래머는 내부 상태를 드러내는 메서드를 최대한 많이 추가해야 한다는 압박에 시달릴 수 밖에 없으며 결과적으로 대부분의 내부 구현이 퍼블릭 인터페이스에 그대로 노출될 수밖에 없는 것이다.

이 문장에서 저격하고 싶은 포인트는 객체가 사용될 협력을 고려하지 않고 객체가 다양한 상황에서 사용될 수 있을 것이라는 막연한 추측을 기반으로 설계라는 부분입니다.

이 문장을 언급할 때 저는 독서회 구성원들에게 Helper 접미사를 지닌 클래스에 대해서 언급했습니다. 혹은 Common이라는 단어가 들어간 클래스가 있는지도요.

저도 경력을 쌓으면서 선배들의 코드들을 많이 봤지만, Helper라는 접미사가 붙어서 이곳저곳에서 쓰이고 있는 클래스를 봤습니다.
Helper, Common 이라는 class가 있다면 장점이 있습니다.

  1. 사용이 편리하다.
  2. 클래스 이름을 고려할 필요가 없다. (메서드 이름만 고려)

저는 이 2번의 이유가 책임 주도 설계의 적이라고 생각합니다. 물론 개발 편리를 위해 어느 정도는 사용할 수 있습니다. BCL에도 Helper 클래스를 이용해서 구현된 static 객체들도 존재하고 BCL뿐 아니라 제가 알지 못하는 수많은 여러 선배들의 Open Source에도 존재할 것입니다.
Helper를 사용하지 말자는 뜻보다는, 매우 엄격하게 Helper의 기준을 정의하고 사용하는 것이 좋다라는 의견을 주장하고 싶었습니다.

많은 회사들에서 최초에 프로젝트를 개발했던 개발자가 그 프로젝트를 회사 근속동안 계속해서 유지보수하는 경우는 솔직히 거의 없습니다. 솔루션 업계든 SI 회사든 마찬가지죠. Domain이 지속적으로 성장하다가 안정기에 접어들면 그 고급인력은 다른 프로젝트에 투입되어 Domain을 성숙시키는데 사용하는 것이 회사에서 개발자 비용을 효율적으로 사용하는 것이기 때문입니다.

그렇기 때문에 최대한 스파게티 코드가 될 여지를 제거하면서 개발하는 것이 내 다음 사람, 내 후배, 내 동료가 유지보수할 수 있는 시간을 줄일 수 있을 것이라는 뜻입니다. 같은 맥락에서 저는 Singleton 패턴도 지양하는 이유가 이것입니다. 아래는 과거에 제가 작성했던 Singleton 패턴에 관한 의견입니다.

static 객체는 CLR이 유일한 인스턴스를 보장 해주는 인스턴스 형태입니다. 싱글톤 역시 인스턴스는 static형태로 가지고 있습니다. 프로세스 내에서 아무데서나 막 접근해도 유일한 인스턴스이기 때문에 개발 시 간편하게 사용할 수 있으나, static 객체에 여러 맴버를 추가해놓고 쉽게 프로그래밍을 한다면, 스파게티 코드 및 static 객체에 대한 의존성이 엄청나게 생겨서 프로그램 유지보수에 좋지 않은 영향 을 끼칩니다. 싱글톤은 나아가, 하나의 클래스기 때문에 static 객체에 대하여 자신만의 추가 기능을 구현하거나, 클래스기 때문에 상속 을 할 수 있다는 장점이 있습니다. 하지만 이렇게 추가 기능을 여러가지를 구현하면 역시 의존도가 높아지기 때문에 프로그래밍 구조를 깨뜨릴 수 있게 될 겁니다.

위 의견과 현재도 크게 변하지 않았습니다.

물론 나쁜 방법은 없고, 현재에 적합한 선택만 있다고 평소에 주장하지만, 그 현재에 적합했던 선택이 시간이 점점 누적되면서 돌이켜 봤을 때 안 좋은 선택만으로 쌓이면 그것은 하나의 방향을 만들어내게 됩니다. 그것을 기술부채라고 부르는 것일 것입니다.

따라서 기술부채를 줄이기 위한 노력은 나로부터 시작되어 팀 모두가 함께 참여해야하는 것이고, 그것을 회사에서 선배 개발자들이 문화로서 주도해야한다는 점입니다.

따라서 잘하든 못하든 지금부터라도 이런 인지를 가지고서, 아무리 이 책이 이상적인 내용을 주장하고는 있다지만 포기하지 말고 이상을 지향하는, 훈련하는 자세가 필요하다고 생각합니다.

그냥 돈 버는 회사에서 왜 그렇게까지 고민하고 어렵게 살려고 하느냐 라고 물어본다면, 문화의 힘이 강력하다고 생각하기 때문입니다. 혼자서는 바꾸기 어려워도 여러 사람이 모이면 바꿀 수 있습니다. 그리고 문화는 좋은 문화던 안좋은 문화던 전파되기 마련이므로, 기왕이면 좋은 문화를 전파하고 싶다는 생각을 갖고 있습니다.


다음 챕터에도 도움이 되는 내용들이 많을 것이라 기대가 되는 독서회 였습니다.

6개의 좋아요

정성이 식지 않으십니다.

덕분에 항상 잘 보고 있습니다. 감사합니다.

2개의 좋아요

응원해주셔서 정말 감사드립니다!!

3개의 좋아요

이번 챕터에서는 몸이 좋지 않아 참여를 못 하였습니다.
대신에 댓글로 독후감을 남기도록 하겠습니다.

데이터 주도 설계란 무엇인지, 데이터 주도 설계를 했을 때 코드가 얼마나 좋지 않은 것인지 알려주는 챕터였습니다. 그러면서 챕터2에서 나오는 책임 주도 설계를 했을 때의 이점을 다시 한 번 깨닫게 해주는 챕터였습니다.

97P

설계는 변경을 위해 존재하고 변경에는 어떤 식으로든 비용이 발생한다.

98P

책임 중심의 관점은 객체의 행동에 초점을 맞춘다.

훌륭한 객체지향 설계는 데이터가 아니라 책임에 초점을 맞춰야 한다.

상태변경은 인터페이스의 변경을 초래하며 이 인터페이스에 의존하는 모든 객체에서 변경의 영향이 퍼지게 된다. 따라서 데이터에 초점을 맞추는 설계는 변경에 취약할 수 밖에 없다.

109P

변경될 가능성이 높은 부분을 구현이라고 부르고 상대적으로 안정적인 부분을 인터페이스라고 부른다는 사실을 기억하라.

설계가 필요한 이유는 요구사항이 변경되기 때문

변경의 관점에서 설계의 품질을 판단하기 위해 캡슐화를 기준으로 삼을 수 있다.

캡슐화란 변경 가능성이 높은 부분을 객체 내부로 숨기는 추상화 기법

117P 캡슐화를 지켜라

객체는 스스로의 상태를 책임져야 하며 외부에서는 인터페이스에 정의된 메서드를 통해서만 상태에 접근할 수 있어야 한다.

131P

데이터 중심의 설계는 너무 이른 시기에 데이터에 대해 고민하기 때문에 캡슐화에 실패하게 된다. 객체 내부의 구현이 객체의 인터페이스를 어지럽히고 객체의 응집도와 결합도에 나쁜 영향을 미치기 때문에 변경에 취약한 코드를 낳게 된다.

132P

객체의 구현이 이미 결정된 상태에서 다른 객체와의 협력 방법을 고민하기 때문에 이미 구현된 객체의 인터페이스를 억지로 끼워맞출 수밖에 없다.

챕터4를 읽는 동안 사실 챕터2에서 책임 주도 설계에 맞춰진 코드로 설명한 것을 봤을 땐 와닿지 않았고, 체득하려니 안 되서 어느 순간 저에게 편한 방식, 그동안 해온 대로 데이터 중심 설계를 하여 프로젝트를 하고 있는 제 자신을 발견하게 되었습니다.

데이터와 상태에 초점을 맞춰서, 구현이 결정된 상태에서 협력을 위해 만들어진 인터페이스는, 구현부와 1대1일 뿐, 다른 객체가 추가될 때 다른 구현부에서는 사용을 할 수가 없었습니다.

지금부터라도 다시 원래대로 돌아가려던 굳은 습관을 버리고, 주말 동안 책을 읽으며 어떻게 뜯어 고쳐야 하는가 많이 고민했습니다.

거기서 더 나아가 디자인패턴 중 추상 팩토리 패턴과 퍼사드 패턴을 공부하게 되었고, 고민에 고민을 거듭한 끝에 오늘 회사에 출근하여 해당 부분을 뜯어 고쳐보았습니다.

처음 고칠 때는 이게 맞나 싶어서 챗지피티의 도움도 받아가며 완성하고 보니까, 이 인터페이스를 사용하게 될 객체, 이 프로젝트에서는 객체가 크게 POS라고 볼 수 있는데, 어떤 POS 객체가 추가가 되어도 인터페이스는 변경 없이 사용할 수 있게 되었습니다.

나중되면 아 이거 이렇게 짤 걸 하고 후회하는 날이 오긴 하겠지만, 현재로썬 오늘의 트레이드오프의 산물에 만족합니다.

언제나 그때 당시에 읽을 때는 와닿지 않아도 시간이 지나면 도움이 되는 것 같습니다.

2개의 좋아요

장소 정말 러블리하네요~

저도 다음 기회에 꼭 참서해보고 싶습니다. :smile:

고생 많으셨어요!! 다음 챕터도 기대하겠습니다.

1개의 좋아요