대범하게

[클린코드] 21일차/22일차/23일차 - 11장 시스템 본문

알아두면쓸데있는신기한잡학사전/독서일지

[클린코드] 21일차/22일차/23일차 - 11장 시스템

대범하게 2023. 11. 16. 01:21
반응형

[클린코드] 21일차 - 생성과 사용 로직 분리 (부제: 조금은 불친절한 시스템장 톺아보기)

클린코드 21일차 (p. 189~191(10장) / 192~197(11장) )

클린코드 22일차 (p. 198~205(11장) )

클린코드 23일차 (p. 206~215(11장) )

11장 시스템

1. 도시를 세운다면 ?

- 도시를 세운다면 혼자서 직접 관리는 불가능하다. 이미 세워진 도시라도 한 사람의 힘으로는 무리다.

- 도시는 각 분야를 관리하는 팀이 있기에 잘 돌아간다.  (수도 관리 팀, 전력 관리 팀, 교통 관리 팀 ..)

 

- 도시가 돌아가는 또 다른 이유는 적절한 추상화와 모듈화 때문이다.

- 큰 그림을 이해하지 못할지라도 개인과 개인이 관리하는 '구성요소'는 효율적으로 돌아간다.

 

- 소프트웨어 팀도 도시처럼 구성한다. 

- 깨끗한 코드를 구현하면 낮은 추상화 수준에서 관심사를 분리하기 쉬워진다. 

* 높은 추상화 수준 == 시스템 수준 / 낮은 추상화 수준 == 코드 

 

- 이 장에서는 높은 추상화 수준, 즉 시스템 수준에서도 깨끗함을 유지하는 방법을 살펴본다.

 

2. 시스템 제작과 시스템 사용을 분리하라

- 제작(construction)과 사용(use)은 아주 다르다는 것을 명심해야한다.

(*제작도 맞는 말이긴 하지만 생성이라고 용어를 사용하는게 더 직관적인 것 같다..)

- 소프트웨어 시스템은 준비과정(객체 생성, 의존성 연결)과 런타임 로직을 분리해야 한다.

하지만 내가 궁금한건 '왜' 생성과 사용을 분리해야하는가 이다...

위에서 언급한게 무엇인가 바로 적절한 추상화와 모듈화이다.

생성 로직과 사용 로직을 분리하면 모듈성이 높아진다.

public Service getService(){
  if (service == null)
    service = new MyServiceImpl(...); // 모든 상황에 적합한 기본값일까?
   return service;
}

 

하지만, 위 코드는 생성 로직과 사용 로직이 섞여있다. 즉, 모듈성이 낮다.

// 생성 로직
if (service == null)
    service = new MyServiceImpl(...);
// 사용 로직
return service;

 

장점

- 실제로 getService가 호출되기 전가지 service 객체를 생성하지 않는다. (Lazy Initialization or Lazy Evaluation)

- 객체를 생성하지 않으므로 불필요한 부하 X / 애플리케이션 시작 시간 그만큼 빨라짐.

- 어떤 경우도 null 포인터를 반환하지 않는다.

 

단점 

- getService 메서드가 MyServiceImpl (new MyServiceImpl) 과 생성자 인수에 명시적으로 의존한다.

- 책임이 두 개이다. 

- 무엇보다도 MyServiceImpl이 모든 상황에 적합한 객체인지 모른다. 다른 객체가 들어올 일이 없나? 다른 객체가 들어오면 로직을 수정해야 한다.

 

 

어떻게 생성과 사용을 분리해야 하는가 ?

1. Main 분리

 

1) Main에서 Application을 호출하기 전에 Builder에 생성 요청을 한다.

 

1.1) 생성 요청을 받은 Builder는 Configured Object를 생성해서 Main에 리턴한다.

 

2) Main은 Application에 Builder에서 넘겨준 Configured Object를 넘겨주어 로직에서 사용하게 한다.

 

장점

- Application은 사용할 객체가 생성되는 과정을 전혀 모른다.

- 생성과 사용이 분리됐다.

 

2. 팩토리

객체가 생성되는 시점을 애플리케이션이 결정해야하는 경우엔 팩토리 형식을 사용할 수 있다.

 

장점

- Application은 FactoryImpl을 받아 필요할 때 Configured Object를 생성한다.

- Application은 Configured Object가 언제 생성되는지 알 수가 없다.

- 생성과 사용이 분리됐다.

 

3. 의존성 주입 (Dependency Injection)

의존성 주입(Dependency Injection)은 생성과 사용을 강력하게 분리하는 강력한 메카니즘이다. 

*제어 역전 IOC 기법을 의존성 관리에 적용한 메커니즘이다.

MyService myService = (MyService)(jndiContext.lookup(“NameOfMyService”));

 

위 코드를 호출하는 쪽에서는 실제로 lookup 메서드가 무엇을(어떤 구현체를) 리턴하는지에 대해 관여하지 않으면서 의존성을

DI 컨테이너가 필요한 객체의 인스턴스를 만든 후 생성자 인수나 설정자 메서드를 사용해 의존성을 설정한다.

스프링 프레임워크는 가장 널리 알려진 자바 DI 컨테이너를 제공한다.

AspectJ 관점

마지막으로, 관심사를 관점으로 분리하는 가장 강력한 도구는 AspectJ 언어다. AspectJ는 언어 차원에서 관점을 모듈화 구성으로 지원하는 자바 언어 확장이다. AspectJ는 관점을 분리하는 강력하고 풍부한 도구 집합을 제공하긴 하지만, 새 도구를 사용하고 새 언어 문법과 사용법을 익혀야 한다는 단점이 있다.

 

테스트 주도 시스템 아키텍처 구축

관점으로(혹은 유사한 개념으로) 관심사를 분리하는 방식은 그 위력이 막강하다. 애플리케이션 도메인 논리를 POJO로 작성할 수 있다면, 즉 코드 수준에서 아키텍처 관심사를 분리할 수 있다면, 진정한 테스트 주도 아키텍처 구축이 가능해진다. 그때그때 새로운 기술을 채택해 단순한 아키텍처를 복잡한 아키텍처로 키워갈 수도 있다. BDUF(Big Design Up Front: 구현을 시작하기 전에 앞으로 벌어질 모든 사항을 설계하는 기법)를 추구할 필요가 없다.

 

건축가는 BDUF 방식을 취하지만 소프트웨어는 다르다. 나름 형체(physics)가 있지만 소프트웨어 구조가 관점을 효과적으로 분리한다면, 극적인 변화가 경제적으로 가능하다.

 

다시 말해, '아주 단순하면서도' 멋지게 분리된 아키텍처로 소프트웨어 프로젝트를 진행해 결과물을 재빨리 출시한 후, 기반 구조를 추가하며 조금씩 확징해나가도 괜찮다는 말이다.

 

그렇다고 '아무 방향 없이' 프로젝트에 뛰어들어도 좋다는 소리는 아니다. 일반적인 범위 내로 생각하되, 변하는 환경에 대처해 진로를 변경할 능력도 반드시 유지해야한다는 소리다.

의사 결정을 최적화하라

모듈을 나누고 관심사를 분리하면 지엽적인 관리와 결정이 가능해진다.

때로는 가능한 마지막까지 결정을 미루는 방법이 최선일 수 있다. 최대한 정보를 모아 최선의 결정을 내려라.

미뤘다하더라도, 관심사를 잘 분리한 POJO 시스템은 기민하게 적용할 수 있다.

 

명백한 가치가 있을 때 표준을 현명하게 사용하라

표준을 사용하면 아이디어와 컴포넌트를 재사용하기 쉽고, 적절한 경험을 가진 사람을 구하기 쉬우며, 좋은 아이디어를 캡슐화하기 쉽고, 컴포넌트를 엮기 쉽다. 하지만 때로는 표준을 만드는 시간이 너무 오래 걸려 업계가 기다리지 못한다. 어떤 표준은 원래 표준을 제정한 목적을 잊어버리기도 한다.

 

간단한 프로젝트에도 표준에 집착하지 말자.

 

시스템은 도메인 특화 언어가 필요하다

소프트웨어 분야에서도 DSL이 조명 받기 시작했다. DSL은 간단한 스크립트 언어나 표준 언어로 구현한 API를 가리킨다.

DSL로 짠 코드는 도메인 전문가가 작성한 구조적인 산문처럼 읽힌다.

좋은 DSL은 도메인 개념과 그 개념을 구현한 코드 사이에 존재하는 '의사소통 간극'을 줄여준다.

효과적으로 사용한다면 DSL은 추상화 수준을 코드 관용구나 디자인 패턴 이상으로 끌어올린다.

 

결론

시스템 역시 깨끗해야 한다. 깨끗하지 못한 아키텍처는 도메인 논리를 흐리며 기민성을 떨어뜨린다. 도메인 논리가 흐려지면 제품 품질이 떨어진다. 버그가 숨어들기 쉬워지고, 스토리를 구현하기 어려워지는 탓이다. 기민성이 떨어지면 생산성이 낮아져 TDD가 제공하는 장점이 사라진다.

 

모든 추상화 단계에서 의도는 명확히 표현해야 한다. 그러려면 POJO를 작성하고 관점 혹은 관점과 유사한 메커니즘을 사용해 각 구현 관심사를 분리해야 한다.

 

시스템을 설계하든 개별 모듈을 설계하든, 실제로 돌아가는 가장 단순한 수단을 사용해야 한다는 사실을 명심하자.

 

Comments