Eternity's Chit-Chat

aeternum.egloos.com



유연한 설계를 위한 패턴과 원리 - 4.잃어버린 시간을 찾아서 2부 Supple Design

닦고 조이고 기름치자

Martin Fowler는 나쁜 냄새를 풍기는 코드의 여러 가지 특징 중 하나로 긴 파라미터 리스트(Long Parameter List)를 꼽았다. 

따라서 객체를 사용한다면 모든 것을 파라미터로 넘길 필요가 없다. 대신 적당히 넘기고 메소드로 필요한 모든 것을 얻으면 된다. 메소드가 필요로 하는 많은 정보는 메소드를 포함하고 있는 클래스에서 구할 수 있다. 객체지향 프로그램에서는 보통 전통적인 프로그램보다 파라미터 리스트가 짧다.

긴 파라미터 리스트는 이해하기도 어렵고, 일관성이 없거나 사용하기 어려울 뿐만 아니라, 다른 데이터가 필요할 때마다 계속 고쳐야 하기 때문에, 파라미터 리스트는 짧은 것이 좋다.

- Martin Fowler, Refactoring

메소드의 파라미터 개수가 많을 경우 프로그래머가 감당해야하는 개념적 복잡도가 증가한다. 메소드와 파라미터의 의미 뿐만 아니라 파라미터의 순서도 기억해야 하기 때문이다. 일반적으로 파라미터의 개수가 많을수록 메소드의 사용성이 저하되고 코드의 가독성이 떨어진다. 이런 경우 함께 사용되는 파라미터를 객체 단위로 묶어 파라미터의 개수를 줄일 수 있도록 INTRODUCE PARAMETER OBJECT 리팩토링을 하는 것이 좋다.

TimePoint에서 제공하는 가장 긴 CREATION METHOD인 at()의 경우 년, 월, 일, 시, 분, 초, 밀리초, 표준 시간대의 순서로 8개의 파라미터를 가진다. 각 파라미터의 순서가 일반적인 날짜 표기 순서를 따르기 때문에 기억하기에 어렵지는 않지만 이 경우에도 코드의 가독성과 이해도는 떨어진다. 또한 C++처럼 파라미터의 기본값을 지정할 수 있는 문법을 제공하지 않는 Java의 경우 과도하게 메소드를 오버로딩 하지 않는 이상 기본값을 처리할 수 있는 방법이 없다.

예를 들어 2008년 12월 25일 12시 30분을 표현하는 경우를 생각해 보자. at()메소드를 오버라이딩하여 TimePoint.at(int year, int month, int day, int hour, int minute, TimeZone zone) 메소드를 제공하거나, 프로그래머가 TimePoint.at(2005, 12, 25, 25, 30, 0, 0, TimePoint.KST)과 같이 second와 milisecond 파라미터에 기본값인 0을 직접 전달해야 한다. 이 경우 파라미터 중 날짜를 나타내는 부분을 DayOfYear로, 시간을 나타내는 부분을 TimeOfDay 로 나눈다면 메소드의 사용성과 가독성 모두 향상될 것이다.

일년 중의 특정일을 표현할 DayOfYear부터 작성하자. CREATION METHOD at()을 추가하자. DayOfYear를 사용해서 TimePoint를 생성하기 위해서는 시간과 관련된 프로퍼티를 반환하는 getter를 추가할 필요가 있다. getter는 동일 패키지에 위치하는 TimePoint에 의해서만 사용되기 때문에 가시성을 패키지 내부로 제한한다. 가시성을 패키지로 제한하면 동일 패키지 내의 시간 관련 클래스 이외에는 직접 DayOfYear를 구성하는 개별 속성에 접근할 수 없기 때문에 견고한 캡슐화를 유지할 수 있다.


DayOfYear.java
private
int year;

private int month;

private int day;

      

public static DayOfYear at(int year, int month, int day) {

return new DayOfYear(year, month, day);

}

 

private DayOfYear(int year, int month, int day) {

this.year = year;

this.month = month;

this.day = day;

}

 

int getYear() {

return year;

}

 

int getMonth() {

return month;

}

 

int getDay() {

return day;

}

DayOfYear 역시 비교가 가능해야 한다. 비교 메소드의 이름으로 TimePoint에서 사용한 isBefore()와 isAfter()를 사용한다. Comparable 인터페이스를 구현하고 isBefore(), isAfter(), equals()와 hashCode() 메소드를 오버라이딩한다.
DayOfYear.java
public
class DayOfYear implements Comparable<DayOfYear> {

......

 

public boolean isBefore(DayOfYear other) {

if (year < other.year) {

      return true;

    }

       

    if (year > other.year) {

      return false;

    }

 

    if (month < other.month) {

      return true;

    }

 

    if (month > other.month) {

      return false;

    }

 

if (day < other.day) {

return true;

}

 

return false;

}

 

public boolean isAfter(DayOfYear other) {

return !isBefore(other) && !equals(other);

}

 

public int compareTo(DayOfYear other) {

if (isBefore(other)) {

       return -1;

}

 

if (isAfter(other)) {

       return 1;

}

            

return 0;

}

}