클린 코드 Chapter 11. 시스템

Jeongmin Yeo (Ethan)·2021년 3월 13일
3

Clean Code

목록 보기
11/14
post-thumbnail

클린 코드 Chapter 11. 시스템에 대해 정리합니다.

학습할 내용은 다음과 같습니다.

  • Principle 1. 시스템 제작과 사용을 분리해라.
  • Principle 2. 의사 결정을 최적화 해라
  • Principle 3. 명백한 가치가 있을 때 표준을 현명하게 사용하라
  • Principle 4. 시스템은 도메인 특화 언어가 필요하다

Reference


Intro

도시를 세운다면 온갖 세세한 사항을 혼자서 직접 관리할 수 있을까? 아마도 불가능하다.

그럼에도 도시는 잘 돌아간다. 수도 관리 팀, 전력 관리 팀, 교통 관리 팀 등 각 분야를 관리하는 팀이 있기 때문이다.

도시가 돌아가는 또 다른 이유는 적절한 추상화와 모듈화 때문이다. 그래서 큰 그림을 이해하지 못하더라도 개인이 관리하는 '구성요소'는 효율적으로 돌아간다.

흔히 소프트웨어도 도시처럼 구성한다. 그렇지만 각 팀이 제작하는 시스템은 비슷한 수준으로 관심사를 분리하거나 추상화를 이뤄내지 못한다.

꺠긋한 코드를 구현하면 낮은 추상화 수준에서 관심사를 분리하기가 쉬워진다. 이 장에서는 높은 추상화 수준 시스템 수준에서도 깨끗함을 유지하는 방법을 살펴보자.

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

우선 제작과 사용이 아주 다르다는 사실을 알아야 한다.

호텔 건물을 세우는 사람과 호텔에서 일하는 사람은 다른 것처럼 구별을 해야한다.

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

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

불행히도 대다수 애플리케이션은 시작 단계라는 관심사를 분리하지 않는다.

다음이 전형적인 예다.

public Service getService(){
  if(service == null){
    service = new MyServiceImpl(); // 이게 모든 시작에서 적합한 기본값일까? 
  }
}

이 기법은 유용하다. 초기화 지연(Lazy Initialization)이라고 불리는 기법이다.

실제로 필요할 때까지 객체를 생성하지 않으므로 불필요한 부하가 걸리지 않는다. 따라서 애플리케이션 시작시간이 빨라진다.

하지만 getService() 메소드가 생성하는 MyServiceImpl이 모든 상황에 적합한 객체는 아니다.

그렇다고 getSerivce() 메소드가 전체 맥락을 알고 있어야 할까? 과연 이 시점에서 어떤 객체를 사용할 지 알 수 있을까?

이를 위해 시스템 생성과 사용을 분리하는 방법이 있다.

Main 분리

시스템 생성과 사용을 분리하는 한 가지 방법으로, 생성과 관련한 코드는 모두 main이나 main이 호출하는 모듈로 옮기고 나머지 시스템은 모든 객체가 생성되었고 모든 의존성이 연결되었다고 가정한다.

흐름은 쉽다. main에서 시스템에 필요한 모든 객체를 생서하고 이를 어플리케이션에 넘긴다.

애플리케이션은 객체를 사용하기만 하면 된다. 즉 애플리케이션은 main이나 객체 생성과정을 모른다.

팩토리

물론 때로는 객체가 생성되는 시점을 애플리케이션이 결정해야 하기도 한다.

이 경우는 ABSTRACT FACTORY 패턴을 통해서 가능하다.

ABSTRACT FACTORY 패턴은 다음과 같이 말한다.

상세화된 서브클래스를 정의하지 않고도 서로 관련성이 있거나 독립적인 여러 객체의 군을 생성하기 위한 인터페이스를 제공한다.

예제는 다음과 같다.

// AbstractFactory
interface Factory {
  Wall makeWall();
  Room makeRoom();
  Door makeDoor();
}

// AbstractProduct
interface Game {
  void createMap(Factory factory);
  void show();
}

public class Main {
  public static void main(String[] args) {
    // 두 개의 ConcreteFactory
    Factory bombFactory = new BombedMazeFactory();
    Factory monsterFactory = new MonsterFactory();

    // 폭탄 테마의 미로 게임 맵을 만든다
    Game bomberMan = new MazeGame();
    bomberMan.createMap(bombFactory);

    // 괴물 테마의 미로 게임 맵을 만든다
    Game diablo = new MazeGame();
    diablo.createMap(monsterFactory);

    // 괴물 테마의 슈팅 게임 맵을 만든다
    Game doom = new ShootingGame();
    doom.createMap(monsterFactory);
  }
}

의존성 주입 (Dependency Injection)

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

의존성 주입은 제어 역전 (Inversion Of Control)을 적용한 매커니즘이다.

의존성 관리 맥락에서 객체는 의존성 자체를 인스턴스로 만드는 책임은 지지 않는다. 대신에 이런 책임을 다른 전담 매커니즘에 맡긴다.

대게 책임질 매커니즘으로 특수 컨테이너를 이용하기도 한다.

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

진정한 의존성 주입은 여기서 더 나아가서 클래스가 의존성을 해결하려 시도하지 않는다. 클래스는 완전히 수동적이다.


Principle 2. 의사 결정을 최적화 해라

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

도시든 소프트웨어든 시스템에서 한 사람이 모든 결정을 내리긴 어렵다.

그러므로 가장 적합한 사람에게 책임을 맡기면 가장 좋다.

때때로 가능한 마지막 순간까지 결정을 미루는 방법이 최선이 될 수도 있다. 최대한 정보를 모아 최선의 결정을 내리기 위해서다.

관심사를 모듈로 분리한 POJO 시스템은 이런 방식에 적합하다. 최신 정보에 기만해 최선의 시점에 최적의 결정을 내리기 쉬워진다.


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

EJB2는 단지 표준이라는 이유만으로 많은 팀이 사용했다.

가볍고 간단한 설계로 충분했을 프로젝트에서도 EJB2를 체택했다.

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

하지만 때로는 표준을 만드는 시간이 너무 오래 걸려 업계가 기다리지 못하는 경우도 있다.


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

소프트웨어 분야에서도 최근 들어 DSL (Domain Specific Language)가 조명받기 시작했다.

DSL은 간단한 스크립트 언어나 표준 언어로 구현한 API를 말한다.

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

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

효과적으로 사용한 DSL은 추상화 수준을 코드 관용구나 디자인 패턴 이상으로 끌여올려준다. 그래서 개발자가 적절한 추상화 수준에서 코드 의도를 표현할 수 있다.

profile
좋은 습관을 가지고 싶은 평범한 개발자입니다.

0개의 댓글