엔터프라이즈 컴퓨팅 중간고사 5)

 5) [10점] 가상대학에 올라와 있는 프로그램숙제의 예제들을 조사해서 잘못된 점들을 지적하고 수정하여라. 왜 잘못되었는지를 설명하고 수정 후에는 어떻게 좋아졌는지를 설명하라.


[#M_답 보기|닫기|

WorkerListManagerClass 안에 txt파일로부터 worker 객체를 생성하는 부분을 수업시간에 지적한 바 있습니다. 이 부분의 코드 때문에새로운 worker class를 추가 할 때마다 이 클래스를 변경해야 합니다.


이것은 이 클래스가 “단일 책임 원칙”에 위배되기 때문입니다. List를 관리하는 역할만을 수행하도록 이 클래스에서 txt파일로부터 파일을 읽어오는 행동은 다른 클래스를 통해서 하도록 수정해야 합니다.


현재 WorkerListManager 클래스 안에있는 메소드 중에 txt파일로부터 정보를 읽어와서 worker 객체를 생성하고 그 각각의 객체를 workers List에 추가하는 코드 부분은 아래와 같습니다.


Martin Fowler의 리팩토링 카탈로그에 있는 Extract Class를 사용합니다.


간략하게 방법을 소개 하자면 먼저 새로운 클래스를 만들고 빼내고자 하는 메소드와 변수들은 Extract Method, Extract Member를 사용하면 되겠습니다.


기존의 WorkerListManager가 위와 같았다면..


리팩토링을 통해서 다음과 같이 변하게 됩니다.



이렇게 바뀌게 됩니다. 여기서 더 유연하게 만들고 싶다면 LoadDataManager를 Interface로 바꾸고 그것을 구현한 LoadDataFromTxtManager 클래스를 작성하여 사용할 수 있습니다.


LoadDataManager Source Code를 끝으로 이번 시험을 마치겠습니다.
_M#]


[#M_코드보기|닫기| _M#]

3장 코드 속의 나쁜 냄새 – 긴 메소드

긴 메소드(Long Method)

최적의 상태로 가장 오래 살아 남는 객체 프로그램은 메소드가 짧다. 인디렉션(indirection)의 이점은 모두 짧은 메소드에 의해 제공되는 것이다.

인디렉션이란?

[#M_위키피디아의 정의 펼쳐 보기|닫기|

Indirection

From Wikipedia, the free encyclopedia

Jump to: navigation, search

In computer programming, indirection is the ability to reference something using a name, reference, or container instead of the value itself.

The simplest form of indirection is the act of manipulating a value through its memory address. For example, accessing a variable through the use of a pointer. A stored pointer that exists to provide a reference to an object by double indirection is called an indirection node.

In some older computer architectures, indirect words supported a variety of more-or-less complicated addressing modes.

Object-oriented programming makes use of indirection extensively, a simple example being dynamic dispatch. Higher-level examples of indirection are the design patterns of the proxy and the proxy server. Delegation is another classic example of an indirection pattern.

In strongly-typed interpreted languages with dynamic datatypes,most variable references require a level of indirection: first the typeof the variable is checked for safety, and then the pointer to theactual value is dereferenced and acted on.

_M#]
원문에는 더 많은 내용이 나와있지만 간략히 요약하면 인디렉션이란 실제 값 자체가 아닌 그것을 참조하는 다른 이름을 사용하는 기능을 말한다는 것 같다. 이렇게 함으로써 프록시와 dynamic dispatch도 가능한 듯하다.

책의 82쪽에는 이런말이 있다.

컴퓨터 과학은 인디렉션 계층을 한 단계 더 만들면 모든 문제를 풀 수 있다고 믿는 학문이다.
-Dennis DeBruler

프로그래밍 초창기에는 서브루틴을 호출 할 때 오버헤드가 있어서 짧은 메소드 사용을 꺼려 했다고 한다. 그러나 요즘엔 프로세스 내부 호출(in-process call)로 인런 오버헤드가 상당부분 제거됐다고 한다. 하지만 서브루틴이 무엇인지 보려면 일단 그 부분으로 가보야 하기 때문에 사람들에게는 여전히 오버헤드가 존재한다. 그 오버헤드를 줄이기 위해서는 메소드의 이름을 잘 지어야 한다.

부분의 경우 메소드의 길이를 줄이기 위해서는 Extract Method를 사용한다. 메소드에 파라미터와 임시변수가 많으면 메소드를 추출하기가 어렵다.

  • 임시변수를 줄이기 위해서는 Replace Temp with Query를 사용할 수 있고
  • 긴 파라미터 리스트는 Introduce Parameter Object와 Preserve Whole Object로 짧게 할 수 있다.
  • 그럼에도 여전히 임시변수와 파라미터가 많다면 Replace Method with Method Object를 사용한다.

뽑아낼 코드 덩어리 식별 방법으로는 주석을 찾는 것이 좋다. 그리고 조건문과 루프 또한 추출이 필요하다는 신호를 준다. 조건식을 다루지 위해서는 Decompose Conditional을 사용한다. 루프의 경우는 Extract Method에서 예제로 다루고 있다.

Extract Class

두 개의 클래스가 해야 할 일을 하나의 클래스가 하고 있는 경우,
새로운 클래스를 만들어서 관련 있는 필드와 메소드를 예전 클래에서 새로운 클래스로 옮겨라.

동기

클래스가 너무 많은 메소드와 데이터를 가지고 있어서 쉽게 이해할 수 없는 경우가 있다. 이때 데이터의 부분집합과 메소드의 부분집합이 몰려 다닌다면 새로운 클래스로 분리할 수 있다는 좋은 신호다.

절차

  1. 클래스의 책임을 어떻게 나눌지를 결정하라.
  2. 분리된 책임을 떠맡을 새로운 클래스를 만든다.
    • 책임을 분리한 후 이전 클래스의 책임이 이름과 더 이상 맞지 않는다면, 이전 클래스의 이름을 변경한다.
  3. 이전 클래스에서 새로 만든 클래스에 대한 링크를 만든다.
    • 양방향 링크가 필요할지도 모른다. 그러나 필요해지기 전에는 새로 만든 클래스에서 이전 클래스로 링크를 만들지 마라.
  4. 옮기고자 하는 각각의 필드에 대해 Move Field를 사용한다.
  5. 각각의 필드를 옮길 때마다 컴파일, 테스트를 한다.
  6. Move Method를 사용해서 이전 클래스에서 새로 만든 클래스로 메소드를 옮긴다. 저수준 메소드(호출하기 보다는 호출되는 메소드)부터 시작해서 짐점 고수준의 메소드에 적용한다.
  7. 각각의 메소드를 옮길 때마다 컴파이르 테스트를 한다.
  8. 각 클래스를 검토하고, 인터페이스를 줄인다.
    • 양방향 링크를 가지고 있다면, 단바양 링크로 만들 수 있는지 알아본다.
  9. 새로운 클래스를 공개할지 결정한다. 새로운 클래슬 공개하기로 결정했다면, 참조 객체로 드러낼지 또는 불변성 값 객체(immutable value object)로 드러낼지를 결정한다.

예제

[#M_코드보기|닫기|
public class Person {

   private String name;
   private String officeAreaCode;
   private String officeNumber;

   public String getName() {
       return name;
   }

   public String getTelephoneNumber() {
       return (“(” + officeAreaCode + “) ” + officeNumber);
   }

   public String getOfficeAreaCode() {
       return officeAreaCode;
   }

   public void setOfficeAreaCode(String officeAreaCode) {
       this.officeAreaCode = officeAreaCode;
   }

   public String getOfficeNumber() {
       return officeNumber;
   }

   public void setOfficeNumber(String officeNumber) {
       this.officeNumber = officeNumber;
   }
}_M#]

위 예제 코드를 보시면 officeAreaCode와 officeNumber 데이타가 몰려 다니고 거기에 따른 getter와 setter들이 몰려 다니는 것을 볼 수 있습니다. 이것을 TelephoneNumber라는 class로 따로 뽑아내기 위해 먼저 TelephoneNumber 클래스를 만들고 Move Field를 사용합니다.

이클립스에서 클래스를 만들고 이동하고자 하는 필드를 선택하고 Alt + Shift + v 를 클릭하면 다음과 같은 팝업창이 뜨게 되고 이동시킬 클래슬 선택할 수 있다.


하지만 단순히 데이터만 옮겨 줄뿐 거기에 딸린 getter와 setter를 같이 옮겨 주지 않는 것은 조금 아쉬웠다. 이번에는 옮기고 싶은 것을 통째로 드래그를 해보았다. 그러나 메소드를 옮기는 일이 쉽지가 않은 듯하다.

결국 잘라내기 붙여넣기를 사용하여 아래와 같이 두개의 클래로 분리하였다.

[#M_Person 클래스 보기|닫기|
public class Person {

   private String name;
   private TelephoneNumber officeTelephone;

   public String getName() {
       return name;
   }

   public String getTelephoneNumber() {
       return officeTelephone.getTelephoneNumber();
   }
}
_M#]
[#M_TelephoneNumber 클래스 보기|닫기|
public class TelephoneNumber {

   private String areaCode;
   private String number;

   public String getTelephoneNumber() {
       return (“(” + areaCode + “) ” + number);
   }

   public String getAreaCode() {
       return areaCode;
   }

   public void setAreaCode(String officeAreaCode) {
       this.areaCode = officeAreaCode;
   }

   public String getNumber() {
       return number;
   }

   public void setNumber(String officeNumber) {
       this.number = officeNumber;
   }

}_M#]

Substitute Algorithm

알고리즘을 보다 명환한 것으로 바꾸고 싶을 때는, 메소드의 몸체를 새로운 알고리즘으로 바꾼다.


위 코드를 보시면 people이라는 문자열 배열안에 Don, John, Kent라는 이름이 있는지 확인하고 만약에 있다면 해당하는 이름을 반환하는 메소드 입니다. 이것을 좀더 명확하게 표현하자면 아래와 같이 변경할 수 있습니다.


동기

어떤 것을 할 때 여러가지 방법이 있다면 그 중에 쉬운 것이 있다면 그것을 선택할 것입니다. 알고리즘도 마찬가지로 여러가지 방법 중에 좀더 명확한 방법을 선택하여야 합니다. 이 작업을 할 때는 가능한 많이 메소드를 분해해 두어야 합니다. 크고 복잡한 알고리즘을 바꾸는 것은 어렵기 때문이죠.

절차

  1. 대체 알고리즘을 준비한다. 적용하여 컴파일한다.
  2. 알고리즘을 테스트한다. 만약 결과가 같다면 작업은 끝난 것이다.
  3. 만약 결과가 같지 않다면, 테스트에서 비교하기 위해 예전의 알고리즘을 사용하여 디버깅한다.
    • 예전 알고리즘과 새 알고리즘에 대해 각각의 테스트 케이스를 실행시키고 두 결과를 본다. 이것은 어떤 테스트 케이스가 어떻게 문제를 일으키는지 찾는 데에 도움을 줄 것이다.

Pull Up Method

동일한 일을 하는 메소드를 여러 서브클래스에서 가지고 있다면, 이 메소드를 수퍼클래스로 옮겨라.

동기

  • 중복된 동작을 제거해야 하기 때문입니다. 한쪽은 바꿨는데 다른 쪽은 그대로 라면 수정하기도 번거롭고 나중에 버그가 될 가능성이 높을 것입니다.

Pull Up Method가 필요한 경우는 서브클래스에 있는 메소드가 수퍼클래스에 있는 메소드를 오버라이드하는데 여전히 같은 일을 하는 때입니다.
이 때 성사긴 것은 서브클래스의 메소드에서 수퍼클래스에 없는 메소드나 변수를 참조할 때입니다. 이 때는 메소드도 같이 일반화하거나 수퍼클래스에 추상 메소드를 만들 수 있습니다. (이럴 때 메소드의 시그너쳐를 변경하거나 위임 메소드(?)를 만들어야 할 지도 모른다고 합니다.)
비슷하지만 동일한 메소드가 아닐때는 Form Template Method를 사용할 수 있을지도 모릅니다.

절차

  1. 메소드들을 조사해서 그것이 동일한지 확인한다.
    • 만약 같은 일을 하지만 코드가 다른경우 메소드들 중 하나에 Substitute Algorithm을 사용하여 코드가 동일하게 만들어라.
  2. 메소드가 서로 다른 시그너쳐를 가지고 있다면, 수퍼 클래스에서 사용하고 싶은 시그너처로 바꾼다.
  3. 수퍼클래스에 새로운 메소드를 만들고, 메소드들 중 하나의 몸체를 새로운 메소드에 복사해서 적절히 수정한 후 컴파일한다.
    • 그 메소드가 수퍼클래스에 없는 메소드를 호출한다면, 수퍼클래스에 추상 메소드를 만들어라.
    • 수퍼클래스에 없는 필드를 사용한다면, Pull Up Field 또는 Self Encapsulate Field를 사용하고 추상 get메소드를 선언하여 사용하라.
  4. 서브클래스 중 하나를 골라서 메소드를 삭제한다.
  5. 컴파일, 테스트를 한다.
  6. 수퍼 클래스 메소드만 남을 떄까지 서브클래 메소드를 삭제하고 테스트를 계속한다.
  7. 필요한 타입을 수퍼클래스로 변경할 수 있는지 살펴보기 위해서 이 메소드의 호출부를 살펴본다.

예제

두 개의 서브클래스(RegularCustomer, PreferredCustomer)를 가진 Customer 클래스가 있다. 그리고 두 서브클래스에 있는 createBill 메소드는 둘다 다음과 같은 코드로 동일하다.

void createBill(Date date) {
   double chargeAmount = chatgeFor(lastBillDate, date);
   addBill(date, charge);
}


색칠 된 부분의 코드가 모두 중복되고 있지만 둘 중에 녹색으로 칠해진 chargeFor 메소드는 위로 복사해서 옮길 수가 없습니다. 클래스 몸체가 서브클래스마다 다르기 때문입니다. 이때는 수퍼클래스에 추상 메소드로 선언합니다.


addBill 메소드는 수퍼클래스로 옮겼고 chargeFor 메소드는 수퍼클래스에 추상 메소드로 선언했습니다.(UML 표기상 추상 메소드는 이탤릭체로 표기합니다. 그리고 추상 메소드를 가진 클래스는 추상 클래스가 되기 때문에 클래스 이름도 이탤릭체로 표기했습니다.)