Written by JS970
on
on
소프트웨어시스템설계 2023-03-22 수업정리
Flow
- Refactoring (end)
- More Refactoring Techniques (1)
Refactoring Example
Refactored UML
Step 1
- 응집도를 고려하여
getCharge()
함수와getFrequentRenterPoint()
함수를 Rental 에서 Movie로 이동시켰다.getCharge()
의 경우 Rental이 아닌 Movie의 attribute를 사용하므로 Move method하여 이동시켰다.getFrequentRenterPoint()
의 경우 Rental에서는 _daysRented를 사용하고 Movie의 Movie type 을 연산에 필요로 한다. 중요도 면에서 Movie로 옮기는 것이 더 적절하므로 Move method하였다.
- 이때
getFrequentRenterPoint()
의 경우 Rental의 attribute인 _daysRented 의 정보를 method의 연산 과정에 필요로 하여 초기 코드는 this를 인자로 받았다. - 하지만 이는 coupling issue이므로 this를 넘겨주는 대신 int타입 파라미터를 입력받는 식으로 수정했다.
Step 2
- Movie 의
getCharge()
는 각기 다른 타입의 영화에 대해 연산하는 방식이 다르다. - 이때 OCP를 충족시키기 위해 ploymorphism 을 사용한다.
- Abstract class Price를 생성한다.
- Price를 superclass로 가지는 ChildrenPrice, RegularPrice, NewReleasePrice를 생성한다.
getCharge()
와getFrequentRenterPoint()
를 Price로 Move method한다.- Price의 subclass에서 해당 타입의 Movie에 대한 가격 정보를 포함하고 있다.
- step1에서와 마찬가지로 Price의 attribute를 사용하기 때문이다.
- Price에서는 원래 switch문으로 구현되었던
getCharge()
를 polymorphism을 통해 smell을 없엘 수 있다.
- 아래는 Movie의
setPriceCode()
함수의 코드이다.- 아래 코드는 DIP를 위반하고 있다.
- 이를 해결하기 위해 Price를 통해 각 subclass에 접근해야 한다.
- 객체 생성을 담당하는 새로운 class를 생성한다.
public void setPriceCode(int arg) {
switch(arg) {
case REGULAR: _price = new RegularPrice(); break;
case CHILDREN: _price = new ChildrenPrice(); break;
case NEW_RELEASE: _price = new NewReleasePrice(); break;
default: throw new illegalArgumentException("Incorrect Price Code");
}
}
Step 3
- Customer의
statement()
와htmlStatement()
의 common code를 template method pattern을 사용하여 제거한다. - 이때 subclass의 서로 다른 code를 연산하는 함수를 superclass에 구현한다.
- Refactored UML에서는 Statement class 의
value(Customer)
메소드가 이러한 역할을 하는 것을 확인할 수 있다. value(Customer)
는 template method로 primitive operation에 대한 연산을 수행한다.
- Refactored UML에서는 Statement class 의
More Refactoring Techniques
본 절에서는 리펙토링 실습에서 학습한 리펙토링 기법 이외에 추가적인 리펙토링 기법에 대해서 다룬다.
Introduce Assertion
- 아래 코드에서는 주석을 통해 코드의 특정 위치에서의 요구사항을 정리했다.
double getExpenseLimit() {
// should have either expense limit or a primary project
return(_expenseLimit != NULL_EXPENSE) ?
_expenseLimit:
_primaryProject.fetMemberExpenseLimit();
}
- 하지만 java에서 제공하는 assertion 기능을 이용해 주석(smell)을 없엘 수 있다.
double getExpenseLimit() {
Assert.isTrue (_expenseLimit != NULL_EXPENSE ||
_primaryProject != null);
return(_expenseLimit != NULL_EXPENSE) ?
_expenseLimit:
_primaryProject.fetMemberExpenseLimit();
}
- Assert : 개발자 영역, runtime에는 Assert가 발생하면 안된다.
Rename Method
- 메소드의 이름이 해당 메소드의 목적에 맞도록 rename한다.
Encapsulate Downcast
- method의 호출자가 method의 return을 typecast해서 사용해야 한다면, 그냥 처음부터 typecast를 해서 return 한다.
Replace Error Code with Exception
- int를 반환하는 함수가, 비정상적인 상황에서 -1등의 에러 코드를 반환하는 경우 이는 결국 flag와 다를 것이 없으므로 smell이다.
- 이러한 경우 java 에서는 throws를 활용한 exception handling을 통해 smell을 없에는 것이 가능하다.
Introduce Explaining Variable
- 조건문의 조건 등 직관적이지 않은 코드를 변수로 추출하고 해당 변수에 적절한 이름을 부여하면 가독성을 높일 수 있다.
Encapsulate Field
- public field를 private로 설정하고 getter, setter를 public으로 만들어 사용한다.
Self Encapsulate Field
- Encapsulate Filed와 마찬가지로 한 클래스 내에서도 private를 직접 건드리지 않고 getter, setter를 이용하여 값을 수정하거나 참조한다.
Encapsulate Collection
- 아래 코드에서 특정 자료 구조를 사용하여 데이터를 관리하는 구현을 확인할 수 있다.
Person kent = new Person();
Set s = new HashSet();
s.add(new Course("Smalltalk Programming", false));
s.add(new Course("Appreciating Single Malts", true));
kent.initializeCourses(s);
- 하지만 위와 같은 코드의 경우 특정 자료 구조를 활용한 코드이다
Person.initializeCourses(set)
에서 확인할 수 있듯이, set을 파라미터로 입력받고 있다.
- 이러한 데이터 관리 로직을 Person내부로 이동시키면 fan-out을 줄일 수 있다. 아래는 수정된 코드이다.
Person kent = new Person();
kent.addCourse(new Course("Smalltalk Programming", false));
kent.addCourse(new Course("Appreciating Single Malts", true));
- 위 코드는 set에서 tree로 자료 구조를 변경하더라도 코드의 수정이 필요없다.
- Person내부의 데이터 관리 로직만 수정해 주면 된다.
- 초기 코드에 비해 fan-out이 3에서 2로 줄어든 것을 확인할 수 있다.
Seperate Query from Modifier
- 어떤 클래스의 메소드에서
- Return이 void가 아니라면 Query이다.
- attribute를 수정하면 Modifier이다.
- 이때 한 메소드에서 Query와 Modifier역할은 분리하여 별개의 메소드로 만들어야 한다.
- C++에서 return type이 non-void라면 const member로 선언하여 값의 수정을 막을 수 있다.