본문 바로가기
독서에서 한걸음

Clean Code .Part11

by oncerun 2022. 8. 2.
반응형

이번 장은 시스템 수준에서 깨끗함을 유지하는 방법이다. 

 

소프트웨어 시스템은 준비 과정과 런타임 로직을 분리해야 한다.

 

애플리케이션에서 시작 단계라는 관심사를 분리해야 한다고 말한다. 시작 단계가 무슨 단계일까?

 

- 애플리케이션의 객체 생성 단계 및 조립 단계 

- 조립 이후 런타임 단계

 

이러한 단계로 나누어지지 않을까 싶다. 하지만 대다수 애플리케이션은 준비 과정 코드와 런타임 로직을 마구 뒤섞는다고 한다. 책에서 보여준 예시를 확인해보자.

 

public Service getService() {
 if (service == null) 
   service = new MyServiceImpl(...);
 return service;
}

 

이를 초기화 지연 혹은 계산 지연이라는 기법이라고 한다. 아마 getService()를 호출하기 전까지 객체를 생성하지 않기 때문에 성능적으로 이점을 얻을 수 있고 null 포인터를 확실하게 막을 수 있는 코드이다. 

 하지만 여기서 문제는 생성되는 객체가 고정되어 있고, 런타임 로직에서 반드시 객체를 사용하여야 한다.

이는 테스트에서도 문제가 발생한다. MyServiceImpl 객체가 무거운 객체라면 테스트 전용 객체를 사용하여야 하고 일반 런타임 로직에 객체 생성 로직을 섞어놓은 탓에 모든 실행 경로(if문)도 테스트해야 한다.

또한 MyServiceImpl이 모든 상황에 적합한 객체일까? 이 코드가 변경에 자유로운지 한번 생각해보아야 한다.

모든 맥락을 처리할 수 있는 객체가 존재한다면 설계를 의심해보아야 한다.

즉 해당 메서드는 런타임과 객체 생성 두 가지를 책임지고 있다.

 

 체계적이고 탄탄한 시스템을 위해서 저자는 모듈성을 높여야 한다고 하며, 이를 높이기 위한 방법들을 소개한다. 

 

1. Main 분리

 대걔 Main 메서드는 모든 애플리케이션의 첫 실행 단계이다.  main에 생성과 관련한 코드를 모두 main이나 main을 호출하는 모듈로 옮기고, 나머지는 모든 객체가 생성되었고 모든 의존성이 연결되었다고 가정한다.

main 메서드에서 시스템에 필요한 객체를 생성한 후 애플리케이션에 넘기면 애플리케이션은 그저 객체를 사용할 뿐이다. 

애플리케이션은 객체가 어떤 객체를 사용해 초기화되는지 알 수 없다. 단지 사용하기 때문이다.

생성 단계와 실행 단계의 관심사가 분리되었다.

 

2. 팩토리

지정된 객체로 시작하고 런타임 도중 객체가 변경될 가능성이 없다면 main에 생성 단계를 모을 수 있겠지만 객체가 생성되는 시점을 결정해야 할 일도 있다.

여기서 추상 팩토리 패턴을 사용하여 해결하는 방법을 기술한다.

여기선  왜 저자는 추상 팩토리 패턴을 사용했는지. 그로 인해 애플리케이션이 객체를 생성되는 시점을 결정할 수 있는지 확인하면 된다. 왜 추상 팩토리 패턴을 사용했을까? 

저자는 설정 논리와 일반 실행 논리의 관심사 분리를 하고 싶어 한다. 따라서 애플리케이션은 설정 논리에 대한 의존성을 낮추어야 한다. 또한 애플리케이션이 객체의 생성 시점을 결정해야 한다. 

그럼 애플리케이션에서 객체 생성 요청을 할 수 있어야 한다. 다만 실제 객체의 생성 방법은 몰라야 한다. 

main(실행 메서드)에서 객체의 생성 방법을 팩토리 패턴을 통해 만들어 놓고 애플리케이션은 인터페이스에 의존한다.

이를 통해 객체 생성 시점을 통제할 수 있고, 인터페이스 의존을 통해 의존성을 낮추어 확장에 유리하다. 또한 필요하다면 해당 애플리케이션에서만 사용하는 생성자 인수를 넘길 수도 있다.

 

 

3. 의존성 주입

사용(조립)과 제작(실행)을 분리하는 강력한 메커니즘 중 하나가 Dependecy Injection, DI이다. 

수많은 DI 설명을 읽었지만 저자가 설명하는 DI는 다음과 같다.

 

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

제어 역전에서는 한 객체가 맡은 보조 책임을 새로운 객체에게 떠넘긴다.

의존성 관리 맥락에서는 객체는 의존성 자체를 인스턴스로 만드는 책임은 지지 않고, 대신 이런 책임을 다른 '전담' 메커니즘에 넘겨야만 한다. 그렇게 함으로써 제어를 역전한다.

 

예시로 JNDI는 의존성 주입을 부분적으로 구현한 기능이라고 한다. 

 

JNDI는 컴퓨터 네트워크의 사용자와 네트워크 자원에 대한 정보를 저장하고 조직하는 응용 소프트웨어

에서

 제공하는 데이터 및 객체를 발견(discover)하고 참고(lookup) 하기 위한 자바 API다.

 

JNDI에서 호출하는 객체는 실제로 반환되는 객체의 유형을 제어하지 않는다. 대신 호출하는 객체는 의존성을 능동적으로 해결한다.

이 말이 무엇일까? 반환되는 객체가 여러 의존성을 가지고 있을 때 이를 자동으로 주입해 반환해 준다는 말일까?

(토비의 스프링이 절실하다.)

진정한 의존성 주입은 클래스에서 의존성을 해결하려 하지 않고 주입 방법으로 설정자 메서드나, 생성자 인수를 제공한다. 

스프링 프레임워크를 사용했다면 자바 DI 컨테이너를 경험해 보았을 것이다. 우리는 실제 의존성을 어노테이션으로 생성자, 생성자 메서드 혹은 XML을 통해 의존성을 정의한 적이 있을 것이다.

 

지금까지 저자는 어떻게 시스템의 시작 단계의 관심사를 분리하여야 하는지 해당 방법에 대해 설명했다. 시작 단계의 관심사를 분리하여야 하는 이유와 그 방법을 설명했다.

 

 다음은 확장에 대하여 다룬다. 기능의 확장이 아닌 시스템의 확장의 관점이다.

 

소프트웨어는 관심사를 적절히 분리해 관리한다면 소프트웨어의 아키텍처는 점진적으로 발전할 수 있다고 설명한다.

이후 역사로 사라진 EJB에 대해 설명한다. EJB를 사용하기 위해선 비즈니스 논리가 EJB 애플리케이션 컨테이너에 강하게 결합된다. 모든 것이 컨테이너에서 파생되어야 했으며 컨테이너의 요구사항을 전부 작성해야 했다. 

결국 객체 지향 프로그래밍이라는 개념조차 뿌리가 흔들리게 되었다. 

다만 EJB2 아키텍처는 일부 영역에서는 관심사를 거의 완벽하게 분리한다.

여기서 횡단 관심사, AOP에 대한 이야기가 나온다. 우리가 사용하는 동적 프록시, CGLIB,  AspectJ 등을 예시로 든다. 또한 코드 수준에서 아키텍처 관심사를 분리할 수 있다면 테스트 주도 아키텍처 구축이 가능하다고 말한다.

 최선의 시스템 구조는 각기 POJO 객체로 구현되는 모듈화된 관심사 영역으로 구성된다.  이렇게 서로 다른 영역은 해당 영역 코드에 최소한의 영향을 미치는 관점이나 유사한 도구를 사용해 통합한다. 

 

큰 틀에서의 관심사를 나누고 각 도메인 영역을 최소한의 영향을 미치는 관점으로 엮어 점진적인 발전을 도모하라고 조언한다. 

 

깨끗하지 못한 아키텍쳐는 도메인 논리를 흐리고 이는 제품의 품질의 하락으로 이어진다. 버그가 많아지고 스토리 구현이 어려워지고 확장을 받아들일 수 없어진다. 

 모든 추상화 단계에서 의도는 명확히 표현하여야 하고 그러긴 위해서 POJO를 작성하고 관점(Aspect를 의미하는 것으로 보인다.)  혹은 관점과 유사한 메커니즘을 사용해 각 구현 관심사를 분리해야 한다.

 

* POJO에 대해 좀 더 알아봐야 겠다. 책에 따르면 POJO로 애플리케이션 도메인 논리를 작성하는 것이 코드 수준에서 도아 키 텍쳐 관심사를 작성하는 것이라고 한다. ( 뭔가 비즈니스를 플레인 한 자바 객체로 표현한다는 것 같다.)

 

 

 

 

 

 

 

 

반응형

'독서에서 한걸음' 카테고리의 다른 글

Clean Code .Part13  (0) 2022.08.05
Clean Code .Part12  (1) 2022.08.04
Clean Code .Part10  (0) 2022.07.30
Clean Code .Part9  (0) 2022.07.30
Clean Code .Part7  (0) 2022.04.21

댓글