Skip to content

bhlee chapter 07 Behavior

BryanLee edited this page Nov 7, 2019 · 2 revisions

출처: 켄트벡의 구현패턴 7장 행위

컴퓨터는 명령어를 순서대로 하나씩 수행한다.
이러한 프로그램의 행위(Behavior)를 표현하는 방법에 대해 알아본다.

제어 흐름(Control Flow)

  • 조건문을 사용해 특정 상태에서만 코드를 수행할 수 있다.

  • 루프를 사용해 반복적으로 코드를 수행할 수 있다.

  • 메시지를 사용해 서브루틴을 수행할 수 있다.

  • 예외를 사용해 현재 흐름을 벗어나(pop) 이전 스택을 수행할 수 있다.

주요 흐름(Main Flow)

  • 프로그램의 과정이 어디서 시작하고 어디서 끝나는지에 대한 주요 흐름을 명확히 표현해야한다.

  • 흔치 않은 상황이나 에러 상황은 예외와 조건절을 사용해서 표현하면 된다.

메시지(Message)

  • 객체지향 프로그래밍의 좋은 점 중 하나는 같은 프로시저를 사용해도 더 풍부한 내용을 전달할 수 있다는것이다.

  • 아래 코드는 **"연산 과정은 3단계이며, 지금 당장 자세한 내용을 알 필요는 없다"**는 뜻을 갖고 있다.

    compute() {
        input();
        process();
        output();
    }
    
  • 메시지를 제어 흐름의 메커니즘으로 사용하면 프로그램에서는 상태의 변화가 중요해진다.

선택 메시지(Choosing Message)

  • 아래와 다형적 메시지를 사용하면 명시적 조건문의 사용을 크게 줄일 수 있으며, 추후 확장이 쉽다.

    public void displayShape(Shape subject, Brush brush) {
        brush.display(subject);
    }
    
  • 위와 같이 선택 메시지를 사용하게 되면, 코드를 읽을 때 연산의 세부 구현을 이해하기 위해 여러 개의 클래스를 살펴봐야 한다.

더블 디스패치(Double Dispatch)

  • 선택 메시지가 일차원적 변형에 적합한데,
    두 가지의 독립적인 차원에서의 변형을 표현하기 위해서는 2개의 선택 메시지를 직렬로 연결해야 한다.

    <script src="https://gist.github.com/libliboom/4abf138ef543a44e90a8cc5a22525ecc.js"></script>

분리(순차) 메시지(Decomposing Message)

  • 여러 단계로 구성된 복잡한 알고리즘이 있다면, 관련된 단계들을 모으고 이를 수행하기 위해 메시지를 보낼 수 있다.

  • 메시지의 목적은 구현이 아닌, 함수를 나눈 뒤 그 나누어진 일부 항목을 호출하기 위한 도구일뿐이다.

  • 분리된 메세지의 이름은 코드를 보고 이름만으로 이후 단계에서 어떤 일이 일어나는지 알 수 있도록 지어야 한다.

되돌림 메시지(Reversing Message)

  • 아래와 같이 대칭성을 이용하면 코드의 가독성을 높일 수 있다.

    void compute() {
        input();
        process(helper); // vs helper.process(this);
        output();
    }
    
    void process(Helper helper) {
        helper.process(this);
    }
    
  • 대칭성과 같은 "미학적"인 요소는 코드 패턴 만큼이나 코드 품질에 향상을 도모할 수 있는 중요한 요소이다.

초청 메시지(Inviting Message)

  • 하위클래스의 어떤 연산을 변형시킬 수 있음을 전달하기 위해 적당한 이름의 메시지로 알려주는것이 좋다. (jdk or sdk 예제 찾아 보기)

  • 이름으로 메시지를 변형 가능성의 메시지를 전달하기 어렵다면 추상 메소드 선언을 고려해야한다.

설명 메시지(Explaning Message)

  • 소프트웨어 개발에서 개발자의 의도와 구현을 구분하는 것은 언제나 중요하다.

  • 아래와 같이 한줄로 된 코드에 주석을 붙이고 싶은 경우, 커뮤니케이션을 위해 설명 메시지 사용을 고려해야 한다.

    // example
    flags |= LOADED_BIT; // 로드 비트를 설정
    
    // recommend
    setLoadedFlag();
    

예외 흐름(Exceptional Flow)

  • 예외 흐름은 주요 흐름의 명료성을 훼손하지 않는 범위 내에서 조건절이나 예외를 사용해 표현한다.

  • 다양한 수행 경로를 동등하게 표현하면 프로그램에 플래그가 난무하고 특별한 의미를 가진 값들을 반환해야 한다.

보호절(Guard Clause)

  • 아래와 같이 보호절을 사용하면 간단한 지역적 예외 상황을 지역적인 변화만을 수반하며 표현할 수 있다.

    void initialize() {
        if(!isInitialized()) {
            ... // then 에 해당 하는 코드를 보면서
        }
    
        // else 조건을 찾게 된다.
    }
    
    void initialize() {
        if(isInitialized())
            return ; // 초기화가 된 경우
    
        // 초기화가 되지 않은 경우 
        //   (초기화 메소드가 여러번 호출되도 실제 초기화는 단 한번만 수행)
        //   (초기화가 수행되는 코드가 제어 흐름에서 중요한 부분을 차지하는 경우)
    }
    
  • if-then-else는 동등한 중요성을 갖고 있는 제어 흐름을 표현한다.

  • 보호절은 한 쪽의 제어 흐름이 다른 쪽보다 중요한 경우 유용하다.

  • 여러개의 조건이 있는 경우 보호절을 사용하면 복잡한 제어 구조를 사용하지 않고도 구현할 수 있다.

    <script src="https://gist.github.com/libliboom/d7d7f24b759e608f130bc96488c29934.js"></script> <script src="https://gist.github.com/libliboom/b11e7c1ac6079ae1299a4d1ab3e6b2c5.js"></script>

예외(Exception)

  • 스택 상에서 한참 위쪽에서 문제가 발생했다면(디스크가 가득차거나 네트웍 연결이 끊어진 경우) 이런 문제는 스택의 한참 아래쪽에서 해결하는것이 합리적이다.

  • 예외를 발견한 쪽에서는 예외를 던지고, 예외를 처리하는 쪽에서 예외를 받는 편이 그 사이의 모든 코드에서 예외를 처리하지도 못하면서 예외를 체크하고 전달하며 코드를 지저분하게 하는 것보다는 훨씬 낫다. (코드 예제 찾아보기)

  • 조건절과 메시지로 구현할 수 있는 코드를 예외로 구현하면 단순 제어 흐름 구조 이외에 어떤 일이 벌어지는지 알아내야 하기 때문에 코드를 읽고 이해하기가 훨씬 어렵다.

  • 가능하면 순차적 구문, 메시지, 루프, 제어문 중에서 선호하는 방식으로 제어 흐름을 표현하고 이 방법이 주요 흐름의 이해를 방해하는 경우에만 예외를 사용하는것이 좋다.

체크 예외(Checked Exception)

  • Unchecked 예외를 던졌을 때 아무도 그 예외를 받지 않는 경우 프로그램 수행은 종료된다. 따라서 사용자에게 어떤 일이 일어났는지 알려주기 위한 정보를 출력하는 편이 좋다.

  • Checked 예외는 컴파일러에 의해 체크되기 때문에 그 예외를 받거나 다른 클래스로 념겨줘야 한다.

예외 전달(Exception Propagation)

  • 하위 수준의 예외는 문제를 진단하는데 유용한 정보를 제공해주는 경우가 많기떄문에 하위 수준 예외를 문제 해결에 도움되는 메시지를 출력할 수 있는 상위 수준 예외로 포장해 사용하면 좋다. (코드 예제 찾아보기)