25 May 2017

Implementation Patterns 03

Behavior

  • 프로그램의 행위에 대한 패턴
    • 제어 흐름: 연산을 여러 단계로 나눈다.
    • 주요 흐름: 주요 제어 흐름을 명확하게 표현한다.
    • 메시지: 메시지를 보내어 제어 흐름을 표현한다.
    • 선택 메시지: 여러 선택 사항을 나타내기 위해 메시지 구현자를 다양화한다.
    • 더블 디스패치: 두 가지 축으로 메시지 구현자를 다양화해서 중첩된 선택을 표현한다.
    • 분리 메시지: 복잡한 연산은 밀접한 단위의 연산을 나눈다.
    • 되돌림 메시지: 메시지를 같은 수신자에게 보내서 제어 흐름에 대칭성을 부여한다.
    • 초청 메시지: 다른 방식으로 구현될 수 있는 메시지를 보내서 미래에 일어날 변형을 대비한다.
    • 설명 메시지: 로직을 설명하기 위해 메시지를 보낸다.
    • 예외 흐름: 주요 흐름에 대한 표현을 방해하지 않으면서, 가급적 명확하게 예외적 제어 흐름을 표현한다.
    • 보호 구문: 지역적 예외 흐름은 이른 반환을 통해 표현한다.
    • 예외: 비지역적 예외 흐름은 예외로 표현한다.
    • 체크 예외: 명시적 선언으로 예외를 처리한다.
    • 예외 전달: 예외를 전달할 때에는 예외 처리자에게 적합한 정보를 전달할 수 있도록 필요에 따라 예외의 형태를 변화한다.


제어 흐름

  • 자바 언어의 경우 제어 흐름은 언어의 근간 중 하나
    • 조건문을 사용해 특정 상태에서만 코드 수행
    • 루프를 사용하면 반복적으로 코드 수행
    • 예외를 사용하면 제어 흐름을 스택 아래쪽으로 점프 가능

제어 흐름은 관련된 것들끼리 모아서 코드를 처음 보는 사람들에게도 손쉽게 이해할 수 있게 하는 것이 좋다.


주요 흐름

  • 대부분의 프로그램에는 주요 흐름이 존재
    • 언어를 통해 주요 흐름을 명확히 표현해야 한다.
    • 예외와 같은, 아주 가끔 수행되는 코드를 명확하게 나타내면, 사람들이 이해해야 할 주요 흐름 파악이 어려울 수 있다.


메시지

  • 자바는 다음과 같이 메시지를 통해 로직을 표현한다.
    void compute() {
    input();
    process();
    output();
    }
    
  • 메시지를 제어 흐름의 메커니즘으로 사용하면 프로그램에서는 상태의 변화가 중요해진다.


선택 메시지

  • if 문이나 switch 문 대신에 다음과 같이 객체를 통해 다형적 메시지를 사용할 수 있다.
    public void displayShape(Shape subject, Brush brush) {
    brush.display(subject);
    }
    
  • 위의 display() 메시지는 런타임에 Brush의 타입에 따라 구현을 선택한다.
  • 이러한 선택 메시지를 사용하면 명시적 조건문의 사용을 크게 줄일 수 있으며, 추후 확장이 쉽다.

선택 메시지를 많이 사용하는 프로그램을 이해하기 위해서는 연산의 세부 구현을 이해하기 위해 여러 클래스를 살펴봐야 할 수도 있다. 과도한 선택 메시지의 사용은 좋지 않으며, 당장 연산의 변형이 필요하지 않은 경우라면 굳이 지금 선택 메시지를 사용할 필요 없다.


분리 메시지

  • 여러 단계로 구성되는 복잡한 알고리즘이 있다면, 관련된 단계들을 모으고 이를 수행하기 위한 메시지를 보낼 수 있다.
    • 분리 메시지는 이름을 잘 지어서 코드를 보고, 이름만으로도 이후 단계에서 어떤 일이 일어날지 짐작할 수 있어야 한다..
    • 이름을 짓기 힘들다면, 분리 메시지를 사용하는 것이 적합하지 않다.
  • 파라미터 리스트가 긴 경우, 호출되는 모듈을 인라인시킨 후 메소드 객체등의 패턴을 사용하는 것이 좋다.


되돌림 메시지

  • 대칭성을 이용하여 코드의 가독성을 높일 수 있다.
    void compute() {
    input();
    helper.process(this);
    output();
    }
    
  • 위의 코드를 도우미 메소드를 이용해 메소드의 가독성을 높일 수 있다.
    void process(Helper helper) {
    helper.process(this);
    }
    void compute() {
    input();
    process(helper);
    output();
    }
    


설명 메시지

  • 소프트웨어 개발에서 개발자의 의도와 구현을 분리하는 것은 언제나 중요하다. 이를 통해 연산의 핵심을 파악할 수 있고, 필요한 경우에만 세부 구현에 관심을 기울이면 된다.

  • 한 줄로 된 코드를 가급적 메소드로 구현하면 커뮤니케이션을 도울 수 있다.

flags |= LOADED_BIT;

...

setLoadedFlag();


예외 흐름

  • 예외 흐름은 수행 빈도가 낮고 개념적으로 덜 중요하므로 주요 흐름에 비해 커뮤니케이션의 중요성이 떨어진다.
  • 주요 흐름은 명료하게 나타내고, 예외 흐름은 주요 흐름의 명료성을 훼손하지 않는 범위 내에서 나타내라.


보호절

  • 다음과 같은 두 가지의 코드가 있다고 하자.
    void initialize() {
    if (!isInitialized()) {
      ...
    }
    }
    
    void initialize() {
    if (isInitialized()) {
      return;
    }
    ...
    }
    
  • 첫째 코드와 같이 어떤 특정 루틴을 수행하기 전에 이러한 조건이 많으면 코드를 읽을 때 방해 요소가 된다.
  • 두번째 코드와 같이 조건문만 읽으면 초기화가 되어 있으면 루틴이 수행된다는 것을 바로 알 수 있다.

  • if / else 는 동등한 중요성을 가지는 두 가지의 제어 흐름을 반영한다. 그러나 두 번째 코드와 같은 조건은 한 쪽의 제어 흐름이 다른 쪽보다 중요할 경우에 유용하다.

  • 다음과 같은 코드가 있다고 생각하자.
    void compute() {
    Server server = getServer();
    if (server != null) {
      Client client = server.getClient();
    
      if (client != null) {
        Request current = client.getRequest();
    
        if (current != null) {
          processRequest(current);
        }
      }
    }
    }
    
    • 위의 코드와 같이 중첩된 조건을 사용하면 문제가 발생할 확률도 높을 뿐만 아니라 가독성에도 좋지 않다.
  • 다음과 같이 코드를 작성하면 복잡한 제어 구조를 사용하지 않아도 되면서, 코드를 읽을 때에도 가독성이 더 뛰어나다.
    void compute() {
    Server server = getServer();
    if (server == null) {
      return;
    }
    
    Client client = server.getClient();
    if (client == null) {
      return;
    }
    
    Request current = client.getRequest();
    if (current == null) {
      return;
    }
    
    processRequest(current);
    }
    

이와 같이 예외에 대해 프로그램을 보호하는 if문, 즉 보호절 의 포인트는 주요 흐름과 예외 경우 처리의 차이점을 부각시키는 것이다.


예외

  • 예외는 여러 함수 호출을 걸쳐서 제어 흐름을 바꾸는 경우에 유용

  • 예외에는 비용이 들어가며 호출된 메소드가 예외를 던지면 예외를 처리할 메소드까지, 호출 경로 상의 모든 메소드의 설계와 구현에 영향을 미친다.
    • 예외가 발생할 경우 다음에 수행되는 문장이 다른 메소드나 다른 객체, 다른 패키지에 있을 수 있으므로 제어 흐름을 따라가기가 어렵다.
    • 조건문이나 메시지로 구현할 수 있는 코드를 예외로 구현하면 코드를 읽고 이해하기가 더 어려워진다.
    • 가능한 순차적 구문, 루프, 제어문 등을 사용해서 예외 처리를 진행하는 것이 좋다.
  • 예외의 위험성은 예외를 던졌는데 아무도 그 예외를 받지 않을 때이다.
    • 예기치 않은 상태로 프로그램이 종료하는 경우, 사용자에게 어떤 일이 벌어졌는지 정보를 출력하는 편이 좋다.

Tags:
Stats:
0 comments