본문 바로가기
공부 기록

[클린코드] 11장. 시스템

by 매트(Mat) 2024. 5. 1.

11장. 시스템

복잡성은 죽음이다. 개발자에게서 생기를 앗아가며, 제품을 계획하고 제작하고 테스트하기 어렵게 만든다.
- 레이 오지, 마이크로소프트 CTO

도시를 세운다면?

  • 혼자서 도시를 세울 수 있을까? 불가능하다.
  • 혼자서 도시를 관리할 수 있을까? 불가능하다.

도시를 잘 관리할 수 있는 이유는 수도 관리팀, 전력 관리팀, 건축물 관리팀 등 각 분야를 관리하는 팀이 있기 때문이다. 도시가 잘 돌아가는 또 다른 이유는 적절한 추상화와 모듈화이다. 따라서 큰 그림을 이해하지 못할지라도 개인과 개인이 관리하는 '구성요소'는 효율적으로 돌아간다.

소프트웨어 팀도 도시처럼 구성한다. 깨끗한 코드를 구현하면 낮은 추상화 수준에서 관심사를 분리하기 쉬워진다.

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

소프트웨어 시스템은 (애플리케이션 객체를 제작하고 의존성을 서로 '연결'하는) 준비 과정과 (준비 과정 이후에 이어지는) 런타임 로직을 분리해야 한다.

시작 단계는 모든 애플리케이션이 풀어야 할 관심사다. 관심사 분리는 이 분야에서 가장 오래되고 가장 중요한 설계 기법 중 하나다. 안좋은 코드는 준비 과정 코드를 주먹구구식으로 구현할 뿐만 아니라 런타임 로직과 마구 뒤섞는 코드를 말한다.

public Service getService() {
  if (service == null) {
    service = new MyServiceImpl(...);
  }
  return service;
}
  • 위 코드는 어떤가?
  • 먼저 실제로 사용하기 전까지는 객체를 생성하지 않으므로 부하가 걸리지 않는다.
  • NullPointerException을 발생시키지 않는다.

하지만 getService 메서드는 MyServiceImpl과 생성자 인수에 명시적으로 의존한다. 런타임 로직에서 MyServiceImpl 객체를 전혀 사용하지 않더라도 의존성을 해결하지 않으면 컴파일이 안된다.

또한 테스트 코드를 작성하기도 어려워진다. 여러 책임을 가지는 getService 메서드는 단위 테스트 작성이 까다로워진다. (Mock 객체를 만든다던지, 한 메서드에 여러 테스트를 진행해야 한다던지 등등..)

그렇다면 시스템 생성과 시스템 사용 분리는 어떻게 하는 것이 좋을까?

  • Main 분리: 생성과 관련된 코드는 모두 main이나 main이 호출하는 모듈로 옮기고, 나머지 시스템은 모두 객체가 생성되었고 모든 의존성이 연결되었다고 가정한다. 즉, 개발자는 main에서 생성한 객체를 사용하면 된다.
  • 팩토리: 때로는 객체가 생성되는 시점을 애플리케이션이 결정할 필요도 생긴다. 추상화 팩토리 클래스를 만들어서 그 클래스에 의존하게 된다.
  • 의존성 주입: 의존성 주입(DI)은 제어의 역전 기법을 의존성 관리에 적용한 메커니즘이다. 스프링을 공부했다면 DI는 아주 익숙할 것이다. 또한 DI를 사용하면 성능에도 이점이 있다.

확장

소프트웨어 시스템은 물리적인 시스템과 다르다. 관심사를 적절히 분리해 관리한다면 소프트웨어 아키텍처는 점진적으로 발전할 수 있다.

관심사를 분리하지 못한 대표적인 아키텍처가 EJB이다. 관심사를 분리하지 못했기 때문에 사용하기 정말 어려웠고 복잡했다고 한다. EJB 내에 코드들은 재사용하기가 사실상 불가능하고, OOP의 장점이 있는데도 이를 활용하지 못하게 된다.

자바 프록시

  • 자바 프록시는 단순한 상황에 적합하다.
  • 개별 개체나 클래스에서 메서드 호출을 감싸는 경우가 좋은 예다.
  • JDK에서 제공하는 동적 프록시는 인터페이스만 제공한다.
  • 클래스 프록시를 사용하려면 CGLIB, ASM, Javassist 등과 같은 바이트 코드 조작 라이브러리가 필요하다.

순수 자바 AOP 프레임워크

대부분의 프록시 코드는 비슷하여 도구로 자동화할 수 있다. 대표적으로 스프링의 AOP가 내부적으로 프록시를 사용하고 있다.

앞서 살펴보았던 EJB는 엔터프라이즈 프레임워크에 의존하고 있고, 어떤 의존도 하지 않고 순수하게 도메인에 초점을 맞춘 것이 POJO이고, 이 POJO를 사용하는 프레임워크가 바로 스프링 프레임워크이다. 따라서 상대적으로 테스트하기도 편하고 각종 코드를 개선하기도 편하다.

따라서 순수 자바를 사용했기 때문에 사실상 스프링과 독립적이고, EJB가 가지고 있던 높은 결합도의 문제를 해결할 수 있었다. 스프링의 내부 코드를 보면 정말 많은 것들이 역할과 구현으로 분리되어 있기 때문에 우리가 편하게 사용할 수 있는 것이다.

AspectJ 관점

관심사를 관점으로 분리하는 가장 강력한 도구가 AspectJ 언어이다. AspectJ는 언어 차원에서 관점을 모듈화 구성으로 지원하는 자바 언어 확장이다.

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

관점으로 관심사를 분리하는 방식은 그 위력이 막강하다. 애플리케이션 도메인 논리를 POJO로 작성할 수 있다면, 즉 코드 수준에서 아키텍처 관심사를 분리할 수 있다면 진정한 테스트 주도 아키텍처 구축이 가능해진다.

의사 결정을 최적화하라

모듈을 나누고 관심사를 분리하면 지엽적인 관리와 결정이 가능해진다. 때때로 가능한 마지막 순간까지 결정을 미루는 방법이 최선일 수 있다. 최대한 정보를 모아 최선의 결정을 내리기 위해서다.

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

표준을 사용하면 아이디어와 컴포넌트를 재사용하기 쉽고, 적절한 경험을 가진 사람을 구하기 쉬우며, 좋은 아이디어를 캡슐화하기 쉽고, 컴포넌트를 엮기 쉽다.

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

SW 분야에서 최근(현재는 최근이 아니다..) DSL(Domain-Specific-Language)이 새롭게 조명받기 시작했다. DSL은 간단한 스크립트 언어나 표준 언어로 구현한 API를 가리킨다. DSL로 짠 코드는 도메인 전문가가 작성한 구조적인 산문처럼 읽힌다.

결론

시스템 역시 깨끗해야 한다. 깨끗하지 못한 아키텍처는 도메인 논리를 흐리며 기민성을 떨어뜨린다. 이는 우리가 프로젝트를 설계, 개발, 테스트 모두에 치명적인 것이다. 결국 시스템을 설계하든 개별 모듈을 설계하든, 실제로 돌아가는 가장 단순한 수단을 사용해야 한다는 사실을 명심하자.

References

'공부 기록' 카테고리의 다른 글

[클린코드] 12장. 창발성  (1) 2024.05.02
[클리코드] 10장. 클래스  (0) 2024.04.26
[클린코드] 9장. 단위 테스트  (0) 2024.04.23
[클린코드] 8장. 경계  (0) 2024.04.22
[클린코드] 7장. 오류 처리  (1) 2024.04.19

댓글