Clean Code
11장, 시스템
복잡성은 죽음이다.
1. 소프트웨어 = 도시
- 도시를 세울 때, 혼자서 직접 관리하기는 어렵지만 각 분야를 관리하는 팀으로 나뉨
> 작은 사항에 집중 할 수 있음
> 추상화와 모듈화: '구성요소'가 효율적으로 돌아감
- 소프트웨어에서도 낮은 추상화 수준에서 관심사를 분리해야함
- 시스템 수준에서의 깨끗함 유지
2. 시스템 제작과 시스템 사용 분리
- 제작(Construction)과 사용(Use)은 다르다.
> 소프트웨어 시스템(애플리케이션 객체를 제작하고 의존성을 서로 연결)은 준비과정과 런타임 로직을 분리해야 함
- 시작 단계: 관심사(Concern) 분리
Bad Code
//초기화 지연(Lazy Initialization) == 계산 지연(Lazy Evaluation)
public Service getService(){
if(service == null){
service = new MyServiceImple(...); // 모든 상황에서 적합한가?
}
retrun service;
}
장점
- 실제로 필요할 때까지 객체를 생성하지 않아서 불필요한 부하가 걸리지 않음
> 앱 시간 시작 빠름
- 어떤 경우에도 null 포인터를 반환하지 않음
단점
- getService 메서드가 MyServiceImple 생성자 인수에 명시적으로 의존
> 의존성을 해결하지 않으면 컴파일이 되지 않음
- 테스트 시 getService 메소드를 호출하기 전에 적절한 테스트 전용 객체(Test Double, Mock Object)를 ㄴervice 필드에 할당해야 함
- 일반 런타임 로직에 객체 생성 소릭을 섞어서 모든 실행 경로를 테스트 해야 함
- 책임이 둘 이상, SRP를 깨트림
- 모든 상황에 MyServiceImpl이 적절한 지 알 수 없음
결론
- 손쉬운 기법으로 모듈성을 깨서는 안됨
- 객체를 생성하거나 의존성을 연결할 때, 설정 논리와 일반 실행 노릴를 분리해야 함
Clean Code
1) Main 분리
- 생성과 관련한 코드는 모두 main이나 main이 호출하는 모듈로 옮김
- 나머지 시스템은 모든 객체가 생성되었고 모든 의존성이 연결되었다고 가정
- main 함수에서 시스템에 필요한 객체를 생성 한 후 애플리케이션에 넘김
- 모든 애플리케이션은 main이나 객체가 생성되는 과정을 모름
2) 팩토리
- 객체가 생성되는 시점을 애플리케이션이 결정해야 함
> ABSTRACT FACTORY 패턴 사용
- 객체를 생성하는 코드는 애플리케이션이 알 수 없음
3) 의존성 주입(Dependency Injection, DI)
- 제어의 역전(Inversion of Control, IoC) 기법을 의존성 관리에 적용한 메커니즘
> 제어의 역전: 한 객체가 맡은 보조 책임을 새로운 객체에게 전적으로 떠넘김, 새로운 객체는 넘겨받은 책임만 맡음
> 단일 책임 원칙(SRP)를 지킴
- 책임을 지기 위해 main이나 특수 컨테이너 사용
💡 또는 설정자(setter)나 메서드나 생성자 인수를 제공
> 필요한 객체의 인스턴스를 만든 후 생성자 인수나 설정자 메서드를 사용해 의존성 설정
- Spring Framwrok의 경우 자바 DI 컨테이너를 제공하여 의존성을 XML 파일에 정의
- DI 컨테이너는 필요할 때까지 객체를 생성하지 않고, 계산 지연이나 최적화에 쓸 수 있는 팩토리를 호출 or 프록시 생성하는 방법 제공
> 계산 지연 기법(최적화 기법)에서 이런 메커니즘 사용 가능
3. 확장
- 사용자 스토리에 맞춰 시스템을 조정하고 확장해야 함(애자일)
- 테스트 주도 개발(TDD), 리팩터링, 깨끗한 코드 = 시스템을 조정하고 확장하기 쉽게 만듦
- 시스템 수준: 단순한 아키텍처를 복잡한 아키텍처로 조금씩 키울 수 없음
> 관심사를 적절히 분리하면 소프트웨어 아키텍처는 점진적으로 발전할 수 있음
- 비즈니스 논리가 덩치 큰 컨테이너와 밀접하게 결합되어 있으면, 독자적인 단위 테스트가 어려움
횡단(Cross-cutting) 관심사
- 영속성과 같은 관심사는 애플리케이션의 자연스러운 객체 경계를 넘나드는 경향이 있음
▶️ 모든 객체가 전반적으로 동일한 방식을 이용
- 영속성 프레임워크, 도메인 논리 모두 모듈화 가능
> 두 영역이 세밀한 단위로 겹치는 문제 발생
관점지향 프로그래밍(Aspect-Oriented Programming, AOP)
- 횡단 관심사에 모듈성을 확보하는 방법론
- 관점(Aspect): 특정 관심사를 지원하려면 시스템에서 특정 지점들이 동작하는 방식을 일관성 있게 바꿔야 함
- 프로그래머가 영속적으로 저장할 객체와 속성을 선언한 후, 영속성 책임을 영속성 프레임워크에 위임 ➡️ AOP 프레임워크는 대상 코드에 영향을 미치지 않는 상태로 동작 방식 변경
4. 자바 프록시
- 단순한 상황에 적합 ex. 개별 객체, 클래스에서 메서드 호출을 감싸는 경우
- JDK에서 제공하는 동적 프록시 = 인터페이스만 지원
▶️ 클래스 프록시: CGLIB, ASM, Javassist 등 바이트 코드를 처리하는 라이브러리 사용 필요
ex. p203 Bank.java: 추상화를 위한 POJO(Plain Old Java Object) 구현
- 프록시를 사용하면서 깨끗한 코드를 작성하기 어려움
- 시스템 단위로 실행 지점을 명시하는 메커니즘을 제공하지 않음
- 도구로 자동화 가능
5. 순수 자바 AOP 프레임워크
- AOP, JBoos AOP 등 자바 프레임 워크는 내부적으로 프록시를 사용함
- 스프링은 비즈니스 논리를 POJO로 구현함
▶️ POJO: 순수하게 도메인에 초점, 엔터프라이즈 프레임워크에 의존하지 않음, 테스트가 간단하고 쉬움
- 설정 파일이나 API를 사용하여 필수적인 애플리케이션 기반 구조를 구현
▶️ 횡단관심사(영속성, 트랜잭션, 보안, 캐시, 장애조치) 등 포함
- 프레임워크는 사용자가 모르게 프록시나 바이트코드 라이브러리를 사용하여 구현
- DI 컨테이너의 구체적인 동작 제어
스프링 app.xml의 일부분
- 러시아의 인형과 같이 중첩되어 있는 모습
- 자료 접근자 객체는 JDBC 드라이버 자료 소스로 프록시 되어잇음
- 클라이언트는 Bank 객체에서 getAccount()를 호출한다고 생각하지만, 실제로는 Bank POJO의 기본 동작을 확장한 중첩 Decorator 객체 집합의 가장 외곽과 통신
📌 앱에서 DI 컨테이너에게 객체 요청시, 스프링 관련 자바 코드가 필요 없어, 스프링과 독립적임 -> 강한 결합 해제
- XML: 읽기 어렵지만 설정 파일에 명시된 정책이 자동으로 생성되는 프록시나 관점 논리보다 단순
▶️ 애플리케이션의 영속성 정보는 필요하다면 XML에 모두 옮겨도 괜찮음
6. AspectJ 관점
- 관심사를 분리하는 가장 강력한 도구
- AspectJ: 언어 차원에서 관점을 모듈화 구성으로 지원하는 자바 언어 확장, 새 사용법을 익혀야 하는 단점 존재
- 애너테이션 폼: 순수 자바 코드에 자바5 애너테이션을 사용하여 관점 정의
7. 테스트 주도 시스템 아키텍처 구축
- 애플리케이션 도메인 논리를 POJO로 작성하여 코드 수준에서 아키텍처 관심사 분리 -> 테스트 주도 아키텍처 구축 가능
- BDUF(Big Design Up Front: 구현 시작 전, 앞으로 벌어질 모든 상황 설계 기법)를 추구하지 않아도 됨
▶️BDUF는 변경을 쉽사리 추구하기 어려움
- 아주 단순하게 분리된 아키텍처는 결과물을 재빨리 출시한 후, 기반 구조를 추가하여 확장하여도 됨
- 결과로 내놓을 시스템의 일반적인 구조도 생각해야 함
- 최선의 시스템 구조
➡️ POJO 객체로 구현되는 모듈화된 관심사 영역으로 구성
➡️ 서로 다른 영역은 해당 영역 코드에 최소한의 영향을 미치는 관점이나 유사한 도구를 사용하여 통합
➡️ 테스트 주도 기법 적용
8. 의사 결정 최적화
- 모듈을 나누고 관심사를 분리하면 지엽적인 관리와 결정이 가능
- 가능한 마지막까지 결정을 미뤄, 최대한의 정보로 최선의 결정을 내리기
9. 명백한 가치가 있을 때 표준을 현명하게 사용
- 과장되게 표장된 표준에 집착하지 않기 ex. EJB2는 표준이라는 이유로 많이 사용되었다.
10. 시스템은 도메인 특화 언어가 필요
- DSL(Domain-Specific Language): 간단한 스크립트 언어나 표준 언어로 구현한 API, 도메인 전문가가 작성한 구조적인 산문처럼 읽힘
▶️ 좋은 DSL: 도메인 개념과 개념을 구현한 코드 사이에 존재하는 의사소통 간극을 줄임
▶️ 추상화 수준을 코드 관용구나 디자인 패턴 이상으로 끌어올림
▶️ 모든 추상화 수준과 모든 도메인을 POJO로 표현 가능
결론
- 시스템 아키턱처는 깨끗해야 함, 나쁜 아키텍처는 도메인 논리를 흐리고 기민성을 떨어뜨림 ➡️ TDD가 제공하는 장점이 사라짐
- 모든 추상화 단계에서 의도를 명확하게 표현
▶️ POJO 작성, 관점을 분리하여 관심사를 분리
어려웠던 파트로 나중에 다시 읽어야 한다.
'IT > 책' 카테고리의 다른 글
[Clean Code] 13장, 동시성 (0) | 2021.11.14 |
---|---|
[Clean Code] 12장, 창발성 (0) | 2021.11.13 |
[Clean Code] 10장, 클래스 (0) | 2021.11.07 |
[Clean Code] 9장, 단위 테스트 (0) | 2021.11.07 |
[Clean Code] 8장, 경계 (0) | 2021.11.07 |