도시를 세운다면?

도시가 돌아가는 이유는 수도 관리 팀 등 여러 인프라를 관리하는 팀이 있기 때문이면서, 적절한 추상화와 모듈화 때문이다. 소프트웨어 팀도 도시처럼 구성한다. 시스템은 비슷한 수준으로 관심사를 분리하거나 추상화를 이뤄내지 못한다. 시스템 수준에서도 깨끗함을 유지하는 방법은 무엇일까.

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

**제작(construction)**은 **사용(use)**과 아주 다르다. 호텔의 공사 시기에 있던 기중기와 승강기가 사라지고 호텔에 근무하고 체류하는 사람들로 채워 지듯이 말이다. 소프트웨어 시스템은 객체를 제작하고 의존성을 연결하는 준비과정과 런타임 로직을 분리해야 한다.

시작단계는 모든 애플리케이션이 풀어야 할 관심사다. 관심사 분리는 우리 분야에서 가장 오래되고 가장 중요한 설계 기법이다.

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

위는 초기화 지연 혹은 계산 지연이라는 기법이다. 실제로 필요할 떄까지 객체를 생성하지 않으므로 불필요한 부하가 걸리지 않고 Null 포인터를 반환하지 않는다. 하지만 첫째 MyServiceImpl 의존성을 해결하지 않으면 컴파일이 되지 않고, 단위 테스트에서 테스트 전용 객체가 필요할것이다. 또한 일반 런타임 로직에다 객체 생성 로직을 섞어 놓은탓에 모든 경로를 감안한 테스트를 해야한다. 단일 책임 원칙 위반이다.

체계적이고 탄탄한 시스템을 위해서라면 흔히 쓰이는 손쉬운 기법으로 모듈성을 깨서는 절대로 안 된다. 객체를 생성하거나 의존성을 연결할때, 설정 논리와 일반 실행 논리를 분리해야 모듈성이 높아진다.

Main 분리

시스템 생성과 시스템 사용을 분리하는 한 가지 방법은 main 분리이다. 제어 흐름은 main 함수에서 시스템에 필요한 객체를 생성한 후 이를 애플리케이션에 넘긴다. 애플리케이션은 그저 객체를 사용할 뿐이다. main과 애플리케이션 사이에 표시된 의존성 화살표의 방향에 주목하자. 모든 화살표가 main 쪽에서 애플리케이션 쪽을 향한다. 즉 애플리케이션은 객체가 생성되는 과정을 전혀 모른다는 뜻이다.

팩토리

때로는 객체를 생성하는 책임을 다른 객체에게 넘기는 것이 좋다. 이를 팩토리라고 부른다. 주문 처리 시스템에서 애플리케이션은 LineItem 인스턴스를 생성해 Order에 추가한다. 이때는 AbstractFactory 패턴을 사용하면 좋다. 그러면 생성시점은 애플리케이션이 결정하지만 생성코드는 애플리케이션이 모른다.

의존성 주입

의존성 주입은 제어 역전의 한 형태다. 제어 역전은 프레임워크가 프로그램의 제어 흐름을 전적으로 가져가는 것을 말한다.