책과 강연/클린 코드

클린 코드 11장: 시스템

데비시 2024. 1. 1. 00:16
복잡성은 죽음이다. 개발자에게서 생기를 앗아가며, 제품을 계획하고 제작하고 테스트하기 어렵게 만든다. 
- 레오 오지, 마이크로소프트 최고 기술 책임자(CTO)

 

저자는 소프트웨어를 도시에 비유한다.

도시는 수도 관리 팀, 전력 관리 팀 등 다양한 팀으로 구성되어 있다. 이런 도시에는 큰 그림을 그리는 사람과 작은 사항에 세부적으로 집중하는 사람들도 있다. 

이렇듯 도시는 적절한 추상화와 모듈화를 가지고 있으며, 큰 그림을 이해하지 못하더라도 추상화가 잘 되어 있기때문에 개인이 관리하는 "구성요소"는 효율적으로 돌아간다.

이번 장에서는 도시처럼 소프트웨어 시스템을 높은 추상화 수준으로 깨끗하게 관리하는 법을 살펴본다.

 

※ 저자가 처음으로 시스템이란 용어에 대해서 설명하지 않는다. 내용 및 단어 선택으로 봤을 때, 우리가 흔히 말하는 개념(= 소프트웨어를 구성하는 컴퓨터 환경 및 애플리케이션들) 또는 아키텍처라 생각하면 될 것 같다.

 

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

시작 단계 분리 (Main 분리)

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

 

시작 단계는 모든 애플리케이션이 풀어야 할 관심사다.

불행히도, 대부분의 애플리케이션은 런타임 로직과 준비 과정 코드를 분리하지 않는다.

아래 그림처럼 시작 단계와 런타임 단계를 분리할 수 있다.

 

 

의존성 주입

사용과 제작을 분리하는 강력한 메커니즘이 의존성 주입이다.

의존성 주입은 제어 역전 기법을 의존성 관리에 적용한 메커니즘이다. 한 객체가 맡은 보조 책임을 새로운 객체에게 전적으로 떠넘긴다. 새로운 객체는 넘겨받은 책임만 맡으므로 단일 책임 원칙을 지키게 된다.

 

클래스는 완전히 수동적으로 의존성을 해결하려 시도하지 않는다. 대신에 의존성을 주입하는 설정자(Setter) 메서드나 생성장 인수를 제공한다. 그래서, DI 컨테이너는 필요한 객체의 인스턴스를 만든 후 생성자 인수나 설정자 메서드를 사용해 의존성을 설정한다.

 

확장

도시도 점점 커진다. 전기, 상수도, 인터넷이 없었다가 새로 생기고 밀도가 더욱 높아지기도 한다. 여기에는 고통이 같이 따라간다. 처음에는 4차선도로로 충분했지만, 도시가 성장하여 6차선 도로가 필요해질 수도 있다. 하지만, 성장할 지 모른다는 기대로 처음부터 6차선 도로의 비용을 부담할 수는 없다.

 

이 예시처럼 "처음부터 올바르게" 는 미신이다. 

대신에 주어진 사용자 스토리에 맞춰 시스템을 구현하고 점진적으로 시스템을 확장해야한다.

가장 먼저, 관심사를 적절하게 분리해야한다. 

 

※ 스프링에 대한 지식의 수준이 낮아서, 예시를 잘못 이해했을 수도 있다. 꼼꼼하게 여러번 읽었으니 아닐테지만, 만약을 위해서 표시해둔다.

책에서는 비즈니스 논리가 컨테이너에 강하게 결합되어 있는 예시를 보여준다.

컨테이너를 테스트하기 위해서 컨테이너를 흉내내야했으며, 프레임워크 밖에서는 재사용하기 힘든 문제점이 있었다.

 

AOP

위 예시에서 영속성과 같은 관심사는 애플리케이션의 자연스러운 객체 경계를 넘나드는 경향이 있다.

원론적으로, 모듈화되고 캡슐화된 방식으로 영속성 방식을 구상할 수 있다. 하지만, 현실적으로 영속성 방식을 구현한 코드가 온갖 객체로 흩어진다. (이렇게 시스템 전반에 걸쳐 나타는는 공통된 관심사를 횡단 관심사라고 표현한다.)

영속성 프레임워크 또한 모듈화할 수 있고, 도메인 논리도 모듈화할 수 있다. 문제는 이 두 영역이 세밀한 단위로 겹친다는 것이다.

 

이런 횡당 관심사를 다루기위한 프로그래밍 패러다임이 AOP이다. AOP는 관점이라는 모듈 구성 개념을 가지는데, "특정 관심사를 지원하려면 시스템에서 특정 지점들이 동작하는 방식을 일관성 있게 바꿔야한다" 라고 명시한다.

 

영속성을 예시로 들면, 프로그래머는 영속적으로 저장할 객체와 속성을 영속성 프레임워크에 위임한다. 그러면 AOP 프레임워크는 대상 코드에 영향을 미치지 않는 상태로 동작 방식을 변경한다.

 

자바 프록시

이런 문제를 자바 프록시를 통해서 해결할 수 있다.

프록시를 이용하여 인터페이스를 감싸고, 비즈니스 로직을 POJO(Plain Old Java Object)로 구현하여 연결한다. 

코드의 양과 크기가 상당히 많아지지만 순수한 자바 코드이기 때문에, 위에서 말한 프레임워크 의존성(컨테이너 의존성)을 해결하고 테스트를 하기 편해진다.

 

AOP 프레임워크

다행히도, 프록시는 단순 반복 코드가 많기 때문에 이를 쉽게 자동화한 프레임워크들이 나왔으며, 설정 파일이나 API를 이용하여 애플리케이션 기반 구조를 구현한다. (스프링의 경우 XML 문서를 작성한다)

실제로 우리는 Bank의 getAccount를 호출한다 생각하지만, DAO 객체의 프록시되었으며 DECORATER 객체 집합의 가장 외곽과 통신한다.

또한, 애플리케이션에서 DI 컨테이너에게 XML에 명시된 시스템 최상위 객체를 요청할 때, 거의 순수한 자바코드를 사용하므로, 스프링(프레임워크)과 독립적으로 구현된다.

 

스프링은 이런 프록시 구조를 어노테이션과 연결하여 횡단 관심사를 선언적으로 지원하도록 수정하였다. (현재의 구조인 거 같다. 정확히는 모르겠다.)

 

결론

코드 수준에서 아키텍처의 관심사를 분리할 수 있다면, 진정한 테스트 주도 아키텍처 구축이 가능해진다.

결과물을 빠르게 출시한 후, 기반 구조를 추가하며 조금식 확장해나갈 수 있다는 것이다.

 

또한 좋은 시스템을 위한 저자의 추가적인 조언이 있다.

- 의사 결정을 최적화해라 (가장 적합한 사람에게 책임을 맡기거나 가능한한 마지막 순간까지 결정을 미루어라)

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

- 도메인 특화 언어가 필요하다.

- 모든 추상화 단계에서 의도는 명확히 표현해야한다.(관심사를 분리해야한다.)

 

깨끗하지 못한 아키텍처는 도메인 논리를 흐리며 기민성을 떨어뜨리고 버그가 숨어들기 쉬워진다. TDD의 장점이 사라진다. 

그래서 시스템은 깨끗해야한다.

 

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