IT/책

[Clean Code] 11장, 시스템

Terriermon 2021. 11. 7. 22:49

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