객체지향 설계
개요
이 팩은 객체지향 스타일로 작성된 코드베이스를 위한 설계 규칙이지, 보편적인 조언이 아닙니다. 프로젝트 자체의 작성 규칙이 이 팩보다 우선합니다. 변경 중인 모듈의 관용구를 따르세요. 이 스킬은 작업 규모를 결정하고 적합한 스킬을 명명합니다.
답변하기 전에 이 색인이 가리키는 스킬을 로드하세요. 해당 스킬의 규칙과 예제는 그곳에 있습니다.
작업에 필요한 설계 규모
| 작업 | 사용 | 건너뛰기 |
|---|---|---|
| 한 번 사용하고 버리는 답변 | 아무것도 하지 않음 | 모든 것 |
| 존재하는 클래스의 한 메서드 | 메서드 이름과 계약 | 재설계 |
| 기존 흐름 내부의 기능 | 해당 흐름의 관용구 | 새로운 경계 |
| 새 클래스 또는 모듈 | 설계 루프 | 모든 것 |
| 새로운 컨텍스트 | 루프, 그다음 경계 | 모든 것 |
| 완전히 새로운 시스템 | 루프, 컨텍스트, 그다음 구조 | 모든 것 |
설계 전
설계 전에 게이트를 명시하세요. 소프트웨어가 따라야 할 하나의 규칙과, 이 규칙이 허용하는 예제 하나, 거부하는 예제 하나를 제시하세요. 예를 들어 "슬롯은 최대 하나의 약속만 가질 수 있다."라는 규칙은 다른 슬롯에 두 번째 환자를 예약하는 것은 허용하지만, 같은 슬롯에 두 번째 환자를 예약하는 것은 거부합니다. 규칙을 적을 수 있으면 시작하세요. 적을 수 없으면, 부족한 것은 설계가 아니며 이 팩이 제공할 수 있는 것도 아닙니다.
설계 루프
- 도메인의 언어로 소프트웨어가 무엇을 해야 하는지 말하고(도메인 언어 사용), 각 동작에 필요한 것을 아는 객체를 찾으세요(책임 할당).
- 일관성 경계를 찾으세요. 하나의 트랜잭션에서 함께 참이어야 하는 것은 무엇인가요?(집합체 설계).
- 각 규칙이 어디에 위치할지 결정하세요. 엔티티, 값 객체 또는 도메인 서비스 중 어디인가요?(도메인 로직 배치).
- 각 객체가 응답하는 메시지를 설계하고(상태와 동작 캡슐화), 불가능한 상태를 구성할 수 없도록 만드세요(불가능한 상태 만들기).
- 도메인이 데이터베이스나 프레임워크를 알지 못하도록 의존성을 역전시키세요(의존성 역전, 시스템 계층화).
- 도메인의 언어로 이름을 짓고, 코드가 말할 수 없는 것만 주석으로 남기세요(클래스와 메서드 이름 짓기, 코드가 말할 수 없는 것에 주석 달기).
- 아래의 위험 신호와 결과를 대조하세요(객체 설계 검토).
규칙의 엄격도
이 팩의 모든 핵심 규칙은 다음 세 가지 레이블 중 하나를 가지며, 이 레이블이 규칙이 얼마나 엄격하게 적용되는지를 나타냅니다.
- 규칙. 위반하면 결함입니다. 어떤 프레임워크도 이를 면제하지 않습니다.
- 기본값. 명시된 조건이 성립하지 않는 한 이렇게 하세요. 조건은 독립적인 문장이며, 절대로 뒷부분에 붙는 절이 아닙니다.
- 판단. 두 가지 답변이 모두 방어 가능합니다. 스킬은 어떤 것이 균형을 기울이는지 알려줍니다.
증상과 스킬
증상은 이를 치료하는 스킬을 가리키며, 이 표는 스킬이 추가됨에 따라 커집니다.
| 증상 | 스킬 |
|---|---|
| 이 작업을 어떤 클래스가 수행해야 하나요? | 책임 할당 |
| 이 클래스가 너무 많은 일을 합니다 | 책임 할당 |
| 변경 사항이 여러 클래스로 퍼져나갑니다 | 결합도 진단 |
| 이 두 부분은 얼마나 단단히 결합되어 있나요? | 결합도 진단 |
| 이것은 객체인가요 아니면 단순한 데이터인가요? | 객체 또는 데이터 결정 |
| 우리의 도메인 모델이 빈약합니다 | 객체 또는 데이터 결정 |
| 다른 코드가 이 객체를 대신해 결정합니다 | 상태와 동작 캡슐화 |
| Setter가 이 객체가 규칙을 위반하도록 만듭니다 | 상태와 동작 캡슐화 |
| 이 객체가 반쯤 만들어진 상태로 존재할 수 있습니다 | 유효한 객체 구성 |
| 이 생성자가 너무 많은 인수를 받습니다 | 유효한 객체 구성 |
| 새로운 경우마다 작동 중인 코드를 수정해야 합니다 | 변하는 것 캡슐화 |
| 이것이 얼마나 유연해야 하나요? | 변하는 것 캡슐화 |
| 이것은 값인가요 엔티티인가요? | 값과 엔티티 모델링 |
| 원시 문자열과 숫자가 도메인 규칙을 전달합니다 | 값과 엔티티 모델링 |
| 이것은 저 클래스를 확장해야 하나요, 아니면 포함해야 하나요? | 상속보다 합성 선택 |
| 기본 클래스를 변경하면 하위 클래스가 깨집니다 | 상속보다 합성 선택 |
| 하위 클래스가 부모의 약속을 위반합니다 | 대체 가능성 존중 |
| 코드가 어떤 하위 타입인지 확인합니다 | 대체 가능성 존중 |
| 동일한 타입에 대한 switch 문이 계속 나타납니다 | 조건문을 다형성으로 대체 |
| 하위 클래스인가요 패턴 매칭인가요? | 조건문을 다형성으로 대체 |
| 코드가 객체 체인을 통해 도달합니다 | 객체가 아는 것 제한 |
| 이 클래스는 호출만 전달합니다 | 객체가 아는 것 제한 |
| 이 클래스나 메서드를 어떻게 불러야 하나요? | 클래스와 메서드 이름 짓기 |
| Manager, Helper, Util 같은 이름 | 클래스와 메서드 이름 짓기 |
| 호출자가 절대 사용하지 않는 인터페이스 메서드에 의존합니다 | 인터페이스 설계 |
| 인터페이스인가요 추상 기본 클래스인가요? | 인터페이스 설계 |
| 도메인 코드가 데이터베이스나 프레임워크에 의존합니다 | 의존성 역전 |
| 객체는 어디에서 생성되고 연결되나요? | 의존성 역전 |
| 호출자가 다른 클래스가 데이터를 저장하거나 형식화하는 방식을 압니다 | 구현 숨기기 |
| 클래스가 내부 오류나 단계를 누설합니다 | 구현 숨기기 |
| 플래그나 선택적 필드가 불가능한 조합을 허용합니다 | 불가능한 상태 만들기 |
| 타입이 도메인이 절대 가지지 않는 값을 허용합니다 | 불가능한 상태 만들기 |
| 코드의 단어가 직원들의 단어와 다릅니다 | 도메인 언어 사용 |
| 하나의 단어가 두 팀에게 다른 의미를 갖습니다 | 도메인 언어 사용 |
| 어떤 객체들이 함께 변경되어야 하나요? | 집합체 설계 |
| 저장 충돌이 발생하거나 한 번 로드할 때 너무 많은 데이터를 가져옵니다 | 집합체 설계 |
| 어떤 상태 변경이 허용되나요? | 생명주기 모델링 |
| 취소되거나 종료된 객체가 다시 변경됩니다 | 생명주기 모델링 |
| 도메인 객체를 어떻게 저장하고 찾아야 하나요? | 리포지토리 설계 |
| 리포지토리가 화면마다 find 메서드를 가지고 있습니다 | 리포지토리 설계 |
| 유스케이스가 동일한 비즈니스 검사를 반복합니다 | 도메인 로직 배치 |
| 도메인 서비스, 엔티티 메서드, 아니면 애플리케이션 서비스? | 도메인 로직 배치 |
| 변경 사항이 저장된 후 다른 부분이 반응해야 합니다 | 도메인 이벤트 발행 |
| 롤백된 변경 사항에 대한 메시지가 나갔습니다 | 도메인 이벤트 발행 |
| 한 팀의 모델이 끝나고 다른 팀의 모델이 시작되는 지점은 어디인가요? | 컨텍스트 경계 설정 |
| 어느 부분이 핵심이고, 어느 부분을 구매할 수 있나요? | 컨텍스트 경계 설정 |
| 계층은 어떤 방향으로 의존해야 하나요? | 시스템 계층화 |
| 화면이나 작업이 프레임워크 없이 테스트될 수 없습니다 | 시스템 계층화 |
| 모듈이 순환적으로 서로 의존합니다 | 모듈 구성 |
| 모듈형 모놀리스인가요, 아니면 별도의 서비스인가요? | 모듈 구성 |
| API 변경이 이를 호출하는 앱을 중단시킵니다 | 시스템 경계 넘기 |
| 이 API나 메시지의 버전을 어떻게 관리해야 하나요? | 시스템 경계 넘기 |
| 매핑 도구가 우리의 도메인 클래스를 왜곡합니다 | 영속성 제어 |
| 리스트가 행당 하나의 쿼리를 실행합니다 | 영속성 제어 |
| 화면은 모델과 다른 형태의 데이터가 필요합니다 | 읽기와 쓰기 분리 |
| 읽기 모델, 프로젝션, 또는 이벤트 소싱? | 읽기와 쓰기 분리 |
| 이 화면에 MVC, MVP, MVVM 중 무엇인가요? | 프레젠테이션과 도메인 분리 |
| 화면이 도메인 객체를 직접 편집합니다 | 프레젠테이션과 도메인 분리 |
| 어떤 디자인 패턴이 이 문제에 적합한가요? | 패턴 선택 |
| 문제보다 먼저 패턴이 추가되었습니다 | 패턴 선택 |
| 이 패턴 클래스들은 함수일 수 있습니다 | 언어가 제공하는 패턴 |
| Visitor인가요, 아니면 매칭이 있는 닫힌 집합인가요? | 언어가 제공하는 패턴 |
| 메서드가 아무것도 찾지 못했을 때 null을 반환합니다 | 실패 동작 설계 |
| 거부가 호출자가 무시하는 코드로 돌아옵니다 | 실패 동작 설계 |
| 스레드가 객체를 공유하여 변경 사항을 잃거나 섞습니다 | 공유 상태 관리 |
| 이 상태에 대한 잠금인가요, 아니면 단일 소유자인가요? | 공유 상태 관리 |
| 다른 시스템에 대한 호출이 중간에 멈추거나 실패합니다 | 작업 신뢰성 확보 |
| 재시도가 작업을 두 번 수행했습니다 | 작업 신뢰성 확보 |
| 기록으로 사건을 설명할 수 없습니다 | 기록할 내용 설계 |
| 무엇을 로깅, 카운팅 또는 감사해야 하나요? | 기록할 내용 설계 |
| 리뷰에서 신 클래스나 용암 흐름을 발견했습니다 | 안티패턴 인식 |
| 어떤 안티패턴을 먼저 수정해야 하나요? | 안티패턴 인식 |
| 동작이 변경되지 않았는데 테스트가 실패합니다 | 객체지향 코드 테스트 |
| 이 협력자에 대해 Mock, Stub, Fake 중 어떤 것을 사용하나요? | 객체지향 코드 테스트 |
| 테스트가 시간이나 난수에 의존합니다 | 테스트 용이성 설계 |
| 테스트만 접근할 수 있도록 코드가 공개됩니다 | 테스트 용이성 설계 |
| 이 코드를 안전하게 재구성하려면 어떻게 해야 하나요? | 객체 설계로의 리팩토링 |
| 리팩토링이 너무 커져서 끝낼 수 없습니다 | 객체 설계로의 리팩토링 |
| 테스트가 없는 코드를 변경해야 합니다 | 레거시 코드 작업 |
| 오래된 시스템을 조각조각 교체하세요 | 레거시 코드 작업 |
| 이 리뷰에서 무엇을 확인해야 하나요? | 객체 설계 검토 |
| 어떤 리뷰 결과가 병합을 차단하나요? | 객체 설계 검토 |
| 아무도 코드가 이렇게 만들어진 이유를 기억하지 못합니다 | 설계 결정 문서화 |
| 무엇을 그리고, 어떤 수준으로 그려야 하나요? | 설계 결정 문서화 |
| 주석이 코드를 반복하거나 모순됩니다 | 코드가 말할 수 없는 것에 주석 달기 |
| 이 코드가 왜 이상해 보이는지 어디에서 설명하나요? | 코드가 말할 수 없는 것에 주석 달기 |
| 프레임워크 훅이나 마커가 비즈니스 규칙을 보유합니다 | 프레임워크 내 작업 |
| 프레임워크를 얼마나 따라가야 하나요? | 프레임워크 내 작업 |
| 계산이 입력을 저장, 전송 또는 변경하기도 합니다 | 함수형 기법 적용 |
| 어떤 함수형 기법이 객체 코드에 적합한가요? | 함수형 기법 적용 |
언어별 스킬
언어별 스킬은 하나의 언어에 대한 핵심 규칙을 유지하며 결코 이를 대체하지 않습니다. 기본 언어 스킬은 프레임워크가 없는 코드를 위한 것입니다.
oo-java,object-oriented-design-java에서: 레코드, 봉인 타입,equals와hashCode, 확인된 예외,Optional, 확장에 닫힌 클래스.oo-kotlin,object-oriented-design-kotlin에서: 데이터 클래스, 봉인 타입, 널 가능 타입,by를 사용한 위임, 확장 함수.oo-typescript,object-oriented-design-typescript에서: 브랜드 식별자,#private상태, 추상 클래스, 데코레이터, 판별 유니언, 구성 루트.oo-javascript,object-oriented-design-javascript에서: c
…
[번역을 위해 잘림]