기본형을 객체로 바꾸기

3/7/2024


개발 초기에는 단순한 정보를 숫자나 문자열과 같은 기본형의 간단한 데이터로 표현할 때가 많다.
그러나 개발 규모가 커지고 점차 기능이 복잡해지면서, 더이상 이러한 정보들이 단순하지 않아지면서 어려움이 생긴다.

User의 멤버변수 phoneNumber

예를들어 사용자 클래스에 전화번호를 담는 멤버변수가 있다고 해보자.

public class User {
  ...
  private String phoneNumber;
  public String getPhoneNumber(){
    return this.phoneNumber;
  }
  public void setPhoneNumber(String _phoneNumber){
    this.phoneNumber = _phoneNumber;
  }
}

우리나라 전화번호는 대개 000-0000-0000의 형식으로 나타난다.
때문에 대부분 String타입으로 설정할 것이다.

그런데, 누군가는 전화번호를 01012340000과 같이 -문구가 없는 형태를 선호할 수도 있지 않을까?
정하기 나름이다. 내부적으로는 '우리는 전화번호를 -문구가 있는 형태 규정합니다.' 라고 하면 되고.
전화번호를 직접 입력하는 사용자들에게는 -문구를 입력하도록 강제하거나 validation과정을 거치면 된다.
그럼 이제 새로운 개발자가 들어올 때 해당 내용을 인지할 수 있도록 코드 규칙을 문서화 해두거나, 잘 알려주면 될 것이다.

하지만 사람은 잘 까먹고 실수하는 존재다.
누군가 실수로 -가 없는 문자열을 넣기라도 하면 문제가 생기는 것을 막을 수 없다.
그렇다면 setter에서 이를 방지하거나 변형해주는 코드를 넣어서 누군가 실수로 -없이 입력 하는것을 사전에 차단할 수 있을 것이다.

그런데 서비스가 글로벌하게 확장되면서 서로 다른 국가의 전화번호도 입력해야 하는 상황이 되었다고 하자.
나라마다 전화번호의 형식이 다르기 때문에 이제는 위에서 정한 코드 규칙을 유지할 수 없다.
심지어 전화번호를 보고 어느 나라인지를 확인할 수 있어야 한다는 요구사항이 있어 더욱 복잡해졌다.
setter코드 안에 나라마다 형식을 검증하는 로직을 만들고,
전화번호가 어느 나라의 형식인지 반환해주는 getter도 만들어야 한다.
요구사항이 추가되고, 전화번호로 수행하는 기능이 복잡해질 때마다 User클래스는 점점 관련된 코드로 가득 차 버릴 것이다.

Util클래스 만들기

Util클래스를 만들어서 이를 해결할 수도 있다. PhoneNumberUtil클래스를 만들고 그 안에 각종 로직을 담당하는 메서드를 만드는 것이다.

public class PhoneNumberUtil {

  public static String getCountry(String phoneNumber){
    ... (국적을 찾는 로직)
    return country;
  }

  public static boolean validation(String phoneNumber, String country){
    ... (국적에 따른 형식 검증 로직)
    return result //true or false;
  }
  ...
}

이렇게 하면 User클래스에 작성되는 코드를 줄이고 결합도를 다소 낮출 수 있어 보인다.
하지만, 전화번호를 가지고 수행해야하는 대부분의 메서드에서는 PhoneNumberUtil을 사용해야 한다.
문제는 PhoneNumberUtil클래스는 UserphoneNumber와 이름의 관계 외에는 전혀 프로그래밍적인 연결성이 존재하지 않는다는 것이다.
PhoneNumberUtil클래스의 존재, 사용방법에 대해서 제대로 인지하지 못할 경우,
전화번호에 대한 요구사항을 수행하기 위한 중복 코드가 발생할 수 있는 것이다.

객체화 하기

본 글에서 제시하고자 하는 또 다른 방법은 전화번호를 객체화 하는 것이다.
전화번호는 더이상 단순한 문자열이 아니다. 여러 의미를 담아야 하고, 또 변환해서 보여주어야 한다.
이제는 복잡한 기능을 담당해야 할 대상이 여전히 단순한 String이기에 어려움이 있는 것이다.

방법은 간단하다.
PhoneNumber클래스를 만들고, 요구사항에 대한 모든 내용을 해당 클래스에 구현하는 것이다.
그리고 User클래스의 phoneNumber는 이제 String이 아닌, PhoneNumber클래스의 인스턴스인 것이다.

public class User {
  ...
  private PhoneNumber phoneNumber;
  public PhoneNumber getPhoneNumber(){
    return this.phoneNumber;
  }
  public void setPhoneNumber(String _phoneNumber){
    this.phoneNumber = new PhoneNumber(_phoneNumber);
  }
}
public class PhoneNumber {
  private String phoneNumber;
  private String country;
  public PhoneNumber(String _phoneNumber){
    validation(_phoneNumber)
    this.country = findCountry(_phoneNumber) // 국적을 찾는 메서드
    this.phoneNumber = _phoneNumber
  }
  public String toString(){
    return this.phoneNumber;
  }
  public String getCountry(){
    return this.country;
  }

  public boolean validation(String phoneNumber){
    ... (국적에 따른 형식 검증 로직)
    return result //true or false;
  }
  ...
}

User클래스는 더이상 전화번호에 대한 기능을 위한 코드를 담고 있을 필요가 없다.
전화번호를 위한 기능은 User클래스에서 찾을 필요도, 내가 혹시 모르고 있을 Util클래스를 찾을 필요도 없다.
그저 User클래스에서 getPhoneNumber를 호출해 얻은 객체를 통해 모든 것을 이룰 수 있는 것이다.
안그래도 복잡하고 잘 지켜지지도 않는 코드 규칙 한 줄을 지울 수 있다는 효과도 있겠다.

리팩터링 정리

Inhyeok Kim

Email : inhyeok.kim@icloud.com

GithubPortfolio