리팩터링은 엔트로피와의 싸움이며 레거시 시스템이 퇴보하는 것을 막는 최전선에 놓여 있다.
암시적인 개념을 명확하게
- 도메인 전문가가 사용하는 언어에 귀 기울여라
- 어색한 부분을 조사하라
- 설명하기 힘들만큼 복잡한 작업을 수행하는 프로시저와 관련된 부분이나 새로운 요구사항 탓에 복잡성이 증가하는 부분
- 모순점에 대해 깊이 고민하라
- 서적을 참고하라
- 시도하고 또 시도하라
- 모델러/설계자는 자신의 아이디어에 집착해서는 안된다
다소 불명확한 개념을 모델링하는 법
1. 명시적인 제약조건
- 제약조건을 별도의 메서드로 분리하고 제약조건의 의미를 분명하고 명확하게 표현할 수 있게 메서드의 이름을 짓는다
- 부여된 이름을 사용해서 제약조건에 관한 토의가 가능
- 잘못된 제약조건 설계의 식별
- 제약조건을 평가하려면 해당 객체의 정의에 적합하지 않은 데이터가 필요하다
- 관련된 규칙이 여러 객체에 걸쳐 나타나며, 동일한 계층구조에 속하지 않는 객체 간에 중복 또는 상속 관계를 강요한다
- 설계와 요구사항에 관한 다양한 논의는 제약조건에 초점을 맞춰 이뤄지지만 정작 구현단계에서는 절차적인 코드에 묻혀 명시적으로 표현되지 않는다.
2. 도메인 객체로서의 프로세스
- 객체는 절차를 캡슐화해서 절차 대신 객체의 목표 의도에 관해 생각하게 해야 한다
- 어떤 프로세스를 선택할 것인가는 곧 어떤 객체를 선택할 것인가가 되고, 각 객체는 각기 다른 Strategy를 표현한다
3. 명세(Specification)
- 어떤 객체가 특정기준을 만족하는지 판단하는 술어다.
- 다른객체에 대한 제약조건을 기술하며, 제약조건은 존재할 수도 존재하지 않을 수도 있다
- 특별한 목적을 위해 술어와 유사한 명시적인 Value Object를 만들어라
유연한 설계
- 너무 과도한 추상 계층과 간접 계층이 존재하면 오히려 유연성에 방해가 된다
- 정교한 시스템을 목적으로 조립가능하고 이해하기 어렵지 않은 요소를 만들어내려면 적당한 수준의 엄밀한 설계형식과 접목하고자 노력해야 한다
1. 의도를 드러내는 인터페이스(Intention-Revealing Interface)
- 캡슐화로 클라이언트 코드는 단순해지고 상위 수준의 개념 관점에서 코드를 이해할 수 있다
- 인터페이스를 구성하는 각 요소(타입, 메서드, 인자 이름)의 이름을 토대로 설계 의도를 드러낸다
- 결과와 목적만을 표현하도록 클래스와 연산의 이름을 부여(Ubiquitous Language 기반)
- 행위에 대한 테스트를 먼저 작성
- 방법이 아닌 의도를 표현하는 추상적인 인터페이스위로 모든 까다로운 메커니즘을 캡슐화
2. 부수효과가 없는 함수(Side-Effect-Free-Function)
- 부수효과를 일으키지 않으면서 결과를 반환하는 연산을 함수라 한다
- 함수는 여러번 호출해도 무방하며 매번 동일한 값을 반환한다
- 명령과 질의를 엄격하게 분리 - 변경을 발생시키는 메서드는 도메인 데이터를 반환하지 않아야하고 가능한 단순하게 유지해야 한다
- 명령과 질의를 분리하는 대신 연산의 결과를 표현하는 새로운 Value Object를 생성해서 반환
- 상태변경을 수반하는 로직과 계산이 혼합된 연산은 리팩터링을 거쳐 두개의 연산으로 분리해야 한다
- 복잡한 계산을 처리하는 책임을 Value Object로 옮긴다
3. 단언(Assertion)
- 연산의 사후 조건과 클래스 및 Aggregate의 불변식을 명시하라
- 개발자들이 의도된 단언을 추측할 수 있게 인도하고, 쉽게 배울수 있고 모순된 코드를 작성하는 위험을 줄이는 응집도 높은 개념이 포함된 모델을 만들려고 노력하라
4. 개념적 윤곽(Conceptuel Contour)
- 이 개념이 현재 모델과 코드에 포함된 관계를 기준으로 했을때 적절한가, 또는 현재 기반을 이루는 도메인과 유사한 윤곽을 나타내는가?
- 변경되는 부분과 변경되지 않는 부분을 나누는 중심 축을 식별하고, 변경을 분리하기 위한 패턴을 명확하게 표현하는 개념적 윤곽을 찾아라
- 객체와 메서드를 와해시킬 정도로 광범위한 변경을 야기하는 요구사항이 나타났다는 것은 도메인에 관해 알고 있는 지식을 개선해야 한다는 메시지다
4. 독립형 클래스(Standalone Class)
- Module과 Aggregate 모두 지나치게 얽히고 설키는 상호의존성을 방지하는 것이 목적이다
- 명시적인 참조에 비해 암시적인 개념이 훨씬 더 많은 정신적 과부하를 초래한다.
- 현재 상황과 무관한 모든 개념을 제거하라
- 클래스가 완전히 독립적으로 바뀌고 단독으로 검토하고 이해할 수 있을 것이다
- 모든 비본질적인 의존성을 제거하는 것이 목표다
- 가장 복잡다단한 계산을 Standalone class로 도출하려고 노력하라
- Standalone Class는 극단적으로 결합도를 낮춘 것이다
5. 연산의 닫힘(Closure of Operation)
- 구현자(implementer)가 연산에 사용되는 상태를 포함하고 있다면 연산의 인자로 구현자를 사용하는 것이 효과적이므로 인자의 타입과 반환 타입을 구현자의 타입과 동일하게 정의한다. 이런 방식으로 정의된 연산은 해당 타입의 ㅇ니스턴스 집합에 닫혀 있다. 닫힌 연산은 부차적인 개념을 사용하지 않고도 고수준의 인터페이스를 제공한다.
- 일반적으로 Entity는 어떤 계산의 수행결과를 표현하는 개념이 아니다. 따라서 대부분의 경우 Value Object에서 이 패턴을 적용할 기회를 찾을 수 있다.
6. 선언적 설계
- 일반적으로 일종의 실행 가능한 명세(executable specification)로서 프로그램 전체 혹은 프로그램의 일부를 작성하는 방식
- 특성(properties)을 매우 정확하게 기술함으로써 소프트웨어를 제어하는 것
- 선언적인 프로그램의 이점을 누리려면 모든 개발자가 프레임워크의 규칙을 준수 해야 한다
- 아주 좁은 범위로 한정된 프레임워크를 사용해서 영속성과 객체관계형 매핑과 같이 매우 지루하고 오류가 발생하기 쉬운 설계 측면을 자동화한 경우에 큰 가치를 얻었다
- 규칙기반(rule base)
- 원칙적으로는 선언적이지만 대부분의 시스템은 성능 최적화를 위해 제어술어(control predicate)를 포함
- 이러한 제어코드는 부수효과를 수반하므로 더는 선언된 규칙만으로는 완전한 행위를 지시할 수 없다
7. 도메인 특화 언어
- 특정 도메인을 위해 구축된 특정 모델에 맞게 조정된 프로그래밍 언어를 사용해 클라이언트 코드 작성
- 프로그램의 표현력을 월등히 향상시킬 수 있음
- Ubiquitous Language와도 가장 높은 일관성을 유지 할 수 있음
8. 하위 도메인으로 분할하라
- 조금씩 뜯어내어 접근
- 모델의 일부 영역이 전문적인 영역이거나, 상태변경에 제약을 가하는 복잡한 규칙을 적용한다면 끄집어내어 별도의 모델이나 규칙을 선언적으로 표현해주는 간단한 프레임워크 내부로 옮긴다
- 하나의 영역에 집중
9. 가능하다면 정립된 정형화를 활용하라
- 오랜 시간 동안 정립되어 온 개념적인 체계 이용
- 수학적인 개념 - 도메인에 적절히 특화된 수학은 깔끔한 동시에 명확한 규칙과 결합할 수 있어서 사람들이 이해하기도 쉽다
모델과 디자인 패턴의 연결
- 기술저인 문제에 대한 해법뿐 아니라 개념적인 도메인에 관한 해법도 제공해야 한다는 것이 디자인 패턴을 도메인 패턴으로 적용하기 위한 유일한 요구사항이다
1. Strategy(전략, 정책 패턴)
- 여러프로세스의 선택이라는 문제를 해결
- 프로세스의 규칙과 프로세스를 제어하는 행위를 서로 분리
- 도메인 패턴으로 사용하는 관점에서는 프로세스 또는 정책적인 규칙과 같은 하나의 개념을 표현하는 능력에 중점
2. Composite
- Composite내부에 포함된 모든 구성요소를 포괄하는 추상 타입을 정의
- 계층구조상의 모든 수준에서 동일한 행위를 제공
- 크고 작은 각 부분이 전체 구조를 투명하게 반영