본문 바로가기
Programming Language 이해하기/Java 이해하기

기본 타입에 대한 강박관념(primitive Obsession) 에 대한 이해

by simplify-len 2020. 9. 27.

Photo by Miguel Bruna on Unsplash

리팩토링에 있는 책의 일부분을 저의 의견과 함께 발췌하여 적었습니다.

들어가기

"기본타입에 대한 강박관념" 이라는 개념은 객체를 처음 접하는 경우, 작은 특정 객체를 사용하는 것을 피하고 Primitive Type 만을 사용하려 하는 경향을 말합니다.

여기서 말하는 작은 특정 객체의 사용의 주요 장점은 기본 타입 클래스와 응용 클래스 간의 경계를 허문다는 점 에 있습니다.

그럼 작은 특정 객체라는 것은 어떻게 만드는 걸까? 우리는 이를 더 명시적이고 정확하게 표현하기 위해 행해져야 합니다.

가장 먼저 볼 것은,

데이터 값을 객체로 전환(Replace Data Value With Object)

데이터 항목에 데이터나 기능을 더 추가해야 할 때는 데이터 항목을 객체로 만들자.

 객체를 처음 접하는 사람은 보통 숫자와 통화를 연동하는 돈 관련 클래스나 전화번호와 우편번호 같은 특수 문자열 클래스 등의 사소한 작업에 작은 객체를 잘 사용하지 않으려는 경향이 있습니다. 이러한 우물 안 개구리를 벗어나려면 데이터 값을 객체로 전환(Replace Data Value With Object) 리팩토링 기법을 사용합니다.

class Order {
  
  private String customer;
  
  public Order (String customer){
    this.customer = customer;
  }
  public String getCustomer(){
    return customer;
  }
  public void setCustomer(String arg){
    customer = arg;
  }
  ...
}

Order 에서 customer 는 String 이라는 타입으로 받고 있습니다. 여기서 customer 는 String으로 표현될 수 있을까요? 계속 코드를 살펴보겠습니다.

// Client 코드
private static int numberOfOrdersFor(Collection orders, String customer){
  int result = 0;
  Iterator iter = orders.iterator();
  while(iter.hasNext()){
    Order each = (Order)iter.next();
    if (each.getCustomer().equals(customer)) result++;
  }
  return result;
}

위와 같은 상태의 코드에서 데이터를 객체로 전환하는 행위를 하기 위해 아래와 같은 순서를 만듭니다.

1. 먼저 Customer 클래스를 만들기

class Customer {

  private final String name;
  
  public Customer (String name){
    name = name;
  }
  
  public String getName() {
    return name;
  }
}

2. Customer 필드를 사용하는 부분을 값 객체로 변환 

class Order {
  
  private Customer customer;
  
  public Order (Customer customer){
    this.customer = customer;
  }
  public Customer getCustomer(){
    return customer;
  }
  public void setCustomer(Customer customer){
    customer = customer;
  }
  ...
}

 

 개발초기에 간단한 Field로 정의한 항목이 특별한 동작이 필요할 만큼 간단하지 않은 경우에 중복과 기능에 대한 욕심(Feature Envy) 증상이 나타납니다. Feature Envy 라는 것은, 즉 간단한 Field 로 만든 행위를 행하는데, 중복된 코드가 발생되는 경우를 말합니다.

이 경우에  주어진 필드를  객체로 바꿈으로써 값을 객체로 변경합니다.

예를 들면, 전화번호 Field의 경우, Formatting이나 지역번호 추출 등 작업이 필요해지면 이를 객체로 전환하여 작업합니다.

 

분류 부호를 클래스로 전환(Replace Type Code with Class)

기능에 영향을 미치는 숫자형 분류 부호가 든 클래스가 있을 땐 그 숫자를 새 클래스로 바꾸자.

데이터 값이 분류 부호일 땐 그 값이 기능에 영향을 주지 않는다면 분류 부호를 클래스로 전환(Replace Type Code with Class) 을 고려합니다.

즉, 열거 타입을 사용합니다.

그 전에 무조건 분류 부호를 클래스로 전환하는 것이 아니라, 다른 것으로 전환할 수 있는지 고려해야합니다. 분류 부호를 클래스로 만드는 건 분류 부호가 순수한 데이터 일때만 실시해야 합니다.

class Person {
  public static final int O = 0;
  public static final int A = 1;
  public static final int B = 2;
  public static final int AB = 3;
  
  private int bloodGroup;
  
  public Person (int bloodGroup){
    bloodGroup = bloodGroup;
  }
  
  public void setBloodGroup(int arg){
    bloodGroup = arg;
  }
  public int getBloodGroup(){
    return bloodGroup;
  }
}

위 코드를 사용하는 부분는 아래와 같이 변경할 수 있습니다.

class BloodGroup {
  public static final BloodGroup O = new BloodGroup(0);
  public static final BloodGroup A = new BloodGroup(1);
  public static final BloodGroup B = new BloodGroup(2);
  public static final BloodGroup AB = new BloodGroup(3);
  
  private static final BloodGroup[] values = {O,A,B,AB}
  
  private final int code;
  
  private BloogGroup (int code){
    code = code;
  }
  ... 
}

 

이제 위 2개의 개념(데이터 값을 객체로 변환, 분류 부호를 클래스로 전환)을 합쳐서 개발하면 아래와 같습니다.

class Person {
  public static final int O = BloodGroup.O.getCode();
  public static final int A = BloodGroup.A.getCode();
  public static final int B = BloodGroup.B.getCode();
  public static final int AB = BloodGroup.AB.getCode();
  
  private BloodGroup bloodGroup;
  
  public Person (int bloodGroup){
    bloodGroup = BloodGroup.code(bloodGroup);
  }
  
  public void setBloodGroup(int arg){
    bloodGroup = BloodGroup.code(arg);
  }
  public int getBloodGroup(){
    return bloodGroup.getCode();
  }
}

이렇게 하면 BloodGroup 클래스 안에서 런타임 분류 부호 검사가 이뤄집니다. 또한, Person 클래스 사용 부분이 int 타입 대신 BloodGroup 을 사용해야 합니다.

class Person {
  public static final int O = BloodGroup.O.getCode();
  public static final int A = BloodGroup.A.getCode();
  public static final int B = BloodGroup.B.getCode();
  public static final int AB = BloodGroup.AB.getCode();
  
  private BloodGroup bloodGroup;
  
  public Person (BloodGroup bloodGroup){
    bloodGroup = bloodGroup;
  }
  
  public void setBloodGroup(BloodGroup arg){
    bloodGroup = arg;
  }
  public BloodGroup getBloodGroup(){
    return bloodGroup;
  }
}

마지막은 위와같은 코드로 변경될 것입니다.

조건문이 분류 부호가 사용될 땐 분류 부호를 하위클래스로 전환(Replace Type Code With SubClass) 기법을 사용합니다.

클래스 기능에 영향을 주는 변경불가 분류 부호가 있을 땐 분류 부호를 하위클래스로 만들자.

 

그외 알아야 될 주의사항으로는 아래와 같습니다.

- 숫자로 된 타입코드나 열거형(enumeration)의 경우 상징적인 이름(Symbolic name)을 사용하면 코드 이해가 편해지나, 반드시 이를 사용하도록 강제할 방법이 없습니다. 따라서 소스의 가독성이 떨어지고 버그의 원인이 될 수 있습니다.

- 이 경우 숫자를 클래스로 바꾸고 클래스에 Factory Method를 제공하여, 유효한 인스턴스만이 생성되는지, 인스턴스가 적절한 객체로 전달되는지 확인할 수 있습니다.

- 이 작업은 타입 코드가 다른 동작을 유발하지 않는 경우에만 적용됩니다. Switch문에 사용되어 다른 동작을 유발하는 경우에는 먼저 Conditional with Polymorphism, Replace Type Code with Subclass, Replace Type Code with State/Strategy를 이용하여 타입코드를 처리하여야 합니다.

기법이나 분류 부호를 상태/전략 패턴으로 전환(Replace Type Code With State/Strategy)

 Replace Type Code with Subclasses와 비슷합니다. 객체 생성 후 타입 코드가 바뀌거나 다른 이유로 하위 클래스를 둬야 하는 경우에도 사용 가능합니다.

이는 Strategy Pattern 의 단순화 버전입니다.

특정 상태를 나타내는 데이터를 다루는 경우에는 State Pattern 을 사용합니다.

 

배열을 객체로 전환(Replace Array with Object)

String[] row = new String[3];
row [0] = "Liverpool";
row [1] = "15";

Performance row = new Performance();
row.setName("Liverpool");
row.setWins("15");

비슷한 객체가 아닌 다른 종류의 객체를 포함하는 배열의 경우 객체로 전환하는 것을 고려합니다. 이렇게 변경하는 경우, 배열 순서에 의한 convention은 기억하기 어려운 부분을 객체를 사용하여 Field나 Method에 이러한 정보를 전달하는 것으로 문제를 해결할 수 있습니다.

 

[참조 자료]

- 리팩토링 책

- www.kdata.or.kr/info/info_04_view.html?field=&keyword=&type=techreport&page=4&dbnum=188881&mode=detail&type=techreport

 

댓글