10. 메소드 호출의 단순화

  • 이해하기 쉽고 사용하기 쉬운 인터페이스에 대해 생각하는 것은 좋은 객체 지향 소프트웨어를 개발하는데 있어서 중요한 기술이다.
  • 가장 간단하고 중요한 것은 메소드의 이름을 바꾸는 것이다. 메소드의 이름은 그 목적을 드러내고 있어야 한다.
  • 파라미터는 그 자체가 인터페이스로서 큰 역할을 한다. 너무 많은 파라미터를 사용한다면 객체를 사용하여 파라미터 리스트를 짧게 유지해라

Rename Method

  • 메소드의 이름이 그 목적을 드러내지 못하고 있다면 메소드의 이름을 바꿔라.

동기

  • 메소드는 의도를 잘 나타내도록 이름이 지어져야 한다.
  • 이름을 잘 짓는 좋은 방법은 메소드에 달린 주석의 의미를 생각해 보고, 그 주석을 메소드의 이름으로 바꾸는 것이다.
  • 이름이 잘못된 메소드를 발견하면 반드시 이름을 바꿔야 한다. 코드는 첫째로 사람이 읽기 좋아야 하고, 컴퓨터는 그 다음이라는 것을 기억하라.

Add Parameter

  • 어떤 메소드가 그 메소드를 호출하는 부분에서 더 많은 정보를 필요로 한다면 이 정보를 넘길 수 있는 객체에 대한 파라미터를 추가해라.

동기

  • Add Parameter는 매우 일반적인 리팩토링이고, 모두들 이 리팩토링을 한적이 있을 것이다.
  • 메소드를 변경해야 하는데, 변경하려면 이전에는 필요하지 않었던 정보가 필요하다 그래서 파라미터를 추가한다.
  • 될수 있으면 파라미터를 추가하지 말아라, 긴 파라미터 리스트는 기억하기 어렵고 종종 데이터 덩어리를 필요로 한다.

Remove Parameter

  • 파라미터가 메소드 몸체에서 더 이상 사용되지 않는다면, 그 파라미터를 제거하라.

동기

  • 불 필요한 파라미터를 제거하지 않으면, 그 메소드를 사용하는 사람들이 불필요한 일을 더 많이 하게 된다.

Seperate Query from Modifier

  • 값을 리턴할 뿐만 아니라 객체의 상태도 변경하는 메소드를 가지고 있는 경우,
    두 개의 메소드를 만들어서 하나는 값을 리턴하는 역할을 하고, 하나는 객체의 상태를 변경하는 역할을 하게 하라.

동기

  • 값을 리턴하는 동시에 부작용도 가지고 있는 메소드를 발견한다면, 질의하는 부분과 변경하는 부분을 분리해야 한다.
  • P321 예제, 경고를 보내고(alert) 이름을 알려주는(return) 부분의 메소드를 분리 하였다.

Parameterize Method

  • 몇 몇 메소드가 메소드 몸체에 다른 값을 포함하고 있는 것을 제외하고는 비슷한 일을 하고 있다면,
    다른 값을 파라미터로 넘겨 받는 하나의 메소드를 만들어라.

동기

  • 비슷한 일을 하고 있지만 몇 가지 값에 따라 달라지는 메소드를 발견하면,
    달라지는 값을 파라미터로 처리하는 하나의 메소드로 대체하라

예제

변경 전변경 후
{code}
public class Employee {
void tenPercentRaise(){
salary *=1.1;
}

void fivePercentRaise(){
salary *=1.05;
}
}

|

public class Employee{
void raise(double factor){
salary *=(1+factor);
}
}

|

h2. Replace Parameter with Explicit Methods
 * 파라미터의 값에 따라서 다른 코드를 실행하는 메소드가 있다면, 각각의 파라미터 값에 대한 별도의 메소드를 만들어라.

||변경 전||변경 후||
|

void setValue (String name, int value) {
if (name.equals("height")) {
_height = value;
return;
}

if (name.equals("width")) {
_width = value;
return;
}

Assert.shouldNeverReachHere();
}

|

void setHeight(int arg) {
_height = arg;
}
void setWidth (int arg) {
_width = arg;
}

|
 
h4. 동기
 * 위와 같이 별도의 메소드를 만들어서 조건문을 거치지 않게 하면 조건에 따른 행동을 피할 뿐만 아니라 컴파일 할 때 확인 할 수 있다
   또한 인터페이스가 좀 더 명확해 진다. 

h2. Preserve Whole Object
 * 어떤 객체에서 여러 개의 값을 얻은 다음 메소드를 호출하면서 파라미터로 넘기고 있다면, 대신 그 객체를 파라미터로 넘겨라.

||변경 전||변경 후||
|

int low = daysTempRange().getLow();
int high = daysTempRange().getHigh();
withinPlan = plan.withinRange(low, high);

|

withinPlan = plan.withinRange(daysTempRange());

|
 
h4. 동기
 * Preserve Whole Object은 파라미터 리스트가 변경에 유연하게 대처 할 수 있도록 해준다. 
 * 종종 코드를 좀 더 읽기 좋게 만든다. 

h2. Replace Parameter with Method
 * 객체가 메소드를 호출한 다음, 결과를 다른 메소드에 대한 파라미터로 넘기고 있다. 
  수신자(Receiver:파라미터를 넘겨받는 메소드) 또한 이 메소드를 호출할 수 있다면, 
  그 파라미터를 제거하고 수신자가 그 메소드를 호출하도록 하라
  
||변경 전||변경 후||
|

int basePrice = _quantity * _itemPrice;
discountLevel = getDiscountLevel();

double finalPrice = discountedPrice (basePrice, discountLevel);

|

int basePrice = _quantity * _itemPrice;
double finalPrice = discountedPrice (basePrice);

|

h4. 동기
 * 어떤 메소드가 파라미터로 넘겨 받는 값을 다른 방법을 사용해서 얻을 수 있다면, 그렇게 해야 한다. 
 * 긴 파라미터 리스트는 이해하기 어려우므로, 파라미터 리스트를 가능한 짧게 줄여야 한다. 
 
h4. 예제
||변경 전||변경 후||
|

int basePrice = _quantity * _itemPrice;
discountLevel = getDiscountLevel();

double finalPrice = discountedPrice (basePrice, discountLevel);

private double discountedPrice(int basePrice, int discountLevel){
if(discountLevel == 2) return basePrice * 0.1;
else return basePrice * 0.05;

}

|

int basePrice = _quantity * _itemPrice;
double finalPrice = discountedPrice (basePrice);

private double discountedPrice(int basePrice){
if(getDiscountLevel() == 2) return basePrice * 0.1;
else return basePrice * 0.05;

}

|


h2. Introduce Parameter Object
 * 자연스럽게 몰려다니는 파라미터 그룹을 가지고 있다면, 그것들을 객체로 바꾸어라.
 
 !9.introParamObj.gif!
 
h4. 동기
 * 종종 같이 넘겨지는 경향이 있는 특별한 그룹의 파라미터를 보게 된다면 그 그룹은 데이터 덩어리이고 이 데이터 전부를 운반하는 객체로 바꿀 수 있다. 
 * 이 파라미터를 객체로 바꾸는 것은 파라미터 리스트의 길이를 줄이기 때문에 유용하다. 
 * 대체하려는 파라미터 그룹을 나타낼 새로운 클래스는 불변성(immutable)으로 한다. 
 * [immutable 패턴|http://blog.naver.com/kittenjun?Redirect=Log&logNo=10003991072]
 * [immutable과 static factory method | /wiki/pages/1507380]


h2. Remove Setting Method
 * 어떤 필드가 객체 생성시에 값이 정해지고 그 이후에는 변경되지 않아야 한다면, 그 필드 값을 설정하는 모든 메소드를 제거하라.
 
 !10.removeSettingMethod.gif!

h4. 동기
 * 객체가 생성되고 나서 어떤 필드 값이 바뀌는 것을 원하지 않는다면, 그 필드에 대한 set 메소드를 제공하지 마라.
 * 이런 방법을 사용하면 명확해 지고, 필드 값이 변할지도 모르는 가능성이 없어지게 된다. 
 
h4. 예제
||변경 전||변경 후||
|

public class Account{

private String _id;

Account (String id){
setId(id);
}

void setId (String arg){
id = arg;
}
}

|

public class Account {
private final String _id;

Account (String id){
id = id;
}
}

|


h2. Hide Method
 * 메소드가 다른 클래스에서 사용되지 않는다면, 그 메소드를 private으로 만들어라.

 !11.hideMethod.gif!

h4. 절차
 * 메소드의 접근 권한을 좀 더 줄일 수 있는지 정기적으로 검사한다. 
 * 가능하면 각각의 메소드의 접근 권한을 private로 한다.  


h2. Replace Constructor with Factory Method
 * 객체를 생성할 때 단순히 생성하는 것 이외에 다른 작업도 하고 있다면, 생성자를 팩토리 메소드로 대체하라.

||변경 전||변경 후||
|

Employee (int type){
_type = type;
}

|

static Employee create(int type){
return new Employee(type);
}

|

h4. 동기
 * Replace Constructor with Factory Method를 사용해야 하는 가장 명확한 때는 타입 코드를 서브클래스로 대체할 때이다. 

h4. 예제
||① 최초||② 중간||
|

class Employee{

private int _type;
static final int ENGINEER = 0;
static final int SALESMAN = 1;
static final int MANAGER = 2;

Employee (int type){
_type = type;
}

static Employee create(int type){
return new Employee(type);
}
}

//client code..
Employee emp = Employee.create(Employee.ENGINEER);

|

//Replace Type Code with Subclass를 적용
static Employee create(int type){
switch(type){
case ENGINEER :
return new Engineer();
case SALESMAN :
return new Salesman();
case MANAGER :
return new Manager();
default :
throw new IllegalArgumentException("Incorrect type code value");
}
}

|

||③ 최종||
|

static Employee create(int type){
switch(type){
case ENGINEER :
return create("Engineer");
case SALESMAN :
return create("Salesman");
case MANAGER :
return create("Manager");
default :
throw new IllegalArgumentException("Incorrect type code value");
}
}

//Class.forName을 사용한다.
static Employee create(String name){
try{
return (Employee)Class.forName(name).newInstance();
}catch(Exception e)
throw new IllegalArgumentException("Incorrect type code value");
}
}

//client code..
Employee emp = Employee.create("Engineer");

|




h2. Encapsulate Downcast
 * 메소드가 그 호출부에서 다운캐스트(downcast) 될 필요가 있는 객체를 리턴하고 있다면, 다운캐스트 하는 것을 메소드 안으로 옮겨라.

||변경 전||변경 후||
|

Object lastReading(){
return readings.lastElement();
}

|

Reading lastReading(){
return (Reading) readings.lastElement();
}

|

h4. 동기
 * 가능하면 다운캐스팅을 적게 사용해야 한다. 
 * 클라이언트에서 다운캐스팅을 하도록 하는 것보다 가능하면 가장 구체적인 타입을 항상 제공해야 한다. 
   불필요한 작업을 클라이언트에 강요하는 것이다. 
 * Generic을 잘 활용하면 좋겠죠. 



h2. Replace Error Code with Exception
 * 메소드가 에러를 나타내는 특별한 코드를 가지고 있다면, 대신 예외를 던져라.

||변경 전||변경 후||
|

int withdraw(int amount) {
if (amount > _balance)
return -1;
else {
_balance -= amount;
return 0;
}
}

|

void withdraw(int amount) throws BalanceException {
if (amount > _balance) throw new BalanceException();
_balance -= amount;
}

|

h4. 동기
 * 예외는 일반적인 처리 과정과 예외 처리 과정을 명확하게 분리하기 때문에 더 좋다. 
 * 이는 프로그램을 이해하기 쉽게 만든다.
 


h2. Replace Exception with Test
 * 호출부에서 먼저 검사할 수 있는 조건에 대해 예외를 던지고 있다면, 호출부가 먼저 검사하도록 바꿔라
 
||변경 전||변경 후||
|

double getValueForPeriod (int periodNumber) {
try {
return _values[periodNumber];
} catch (ArrayIndexOutOfBoundsException e) {
return 0;
}
}

|

double getValueForPeriod (int periodNumber) {
if (periodNumber >= _values.length) return 0;
return _values[periodNumber];
}

|
 
h4. 동기
 * 예외는 예외적인 동작(예상치 못한 에러)에 대해서 사용되야 한다. (예외를 과도하게 사용해서는 안 된다.)
 * 예외가 조건 테스트를 대신하는 역할을 하면 안된다.
 * 호출부에서 동적을 호출하기 전에 조건을 테스트할 수 있다면 테스트를 제공해야 하고, 호출부에서는 그것을 사용해야 한다. 



h2. 문서에 대하여
* 이 문서의 내용은 [Refactoring- 나쁜 디자인의 코드를 좋은 디자인으로 바꾸는 방법|http://kangcom.com/common/bookinfo/bookinfo.asp?sku=200204020003] 교재를 스터디 하면서 정리한 내용 입니다.
* 최초작성자 : [김정식]
* 최초작성일 : 2007년 12월 03일
* 이 문서는 [오라클클럽|http://www.gurubee.net] [자바 웹개발자 스터디|2007년 하반기 - 제2차 자바 웹개발 스터디] 모임에서 작성하였습니다.
* 이 문서를 다른 블로그나 홈페이지에 퍼가실 경우에는 출처를 꼭 밝혀 주시면 고맙겠습니다.~^\