냄새 10. 데이터 뭉치 (Data Clumps)

    들어가기 전

    이 글은 인프런 백기선님의 강의를 복습하며 작성한 글입니다. 


    냄새 10. 데이터 뭉치 (Data Clumps)

    • 항상 뭉쳐 다니는 데이터는 한 곳으로 모아두는 것이 좋다.
      • 여러 클래스에 존재하는 비슷한 필드 목록 (비슷한 필드는 데이터 뭉치일 수도 있음)
      • 여러 함수에 전달하는 매개변수 목록 
    • 관련 리팩토링 기술
      • '클래스 추출하기'를 사용해 여러 필드를 하나의 객체나 클래스로 모을 수 있다.
      • '매개변수 객체 만들기' 또는 '객체 통째로 넘기기'를 사용해 메서드 매개변수를 개선할 수 있다. 

    데이터 뭉치들이 있다면, 우선은 그 녀석들을 클래스로 묶어볼 수 있다는 것을 의미한다. 그리고 이런 데이터 뭉치가 여러 클래스에 존재한다면, 해당 데이터 뭉치를 클래스에서 제거하는 형태로 리팩토링을 할 수 있다. 이 리팩토링의 의미는 여러 클래스에 있는 비슷한 필드를 한 곳으로 모을 수 있다는 점이다. 이것은 클래스 추출하기 관점이다.

    만약 여러 함수에 전달하는 매개변수의 목록이 비슷한 경우라면, 매개변수 객체 만들기 + 객체 통째로 넘기기를 이용해서 메서드의 매개변수 숫자를 줄일 수 있다.


    코드

    아래 코드를 살펴보자. PersonalAreaCode, PersonalNumber는 항상 같이 다니는 변수다. 따라서 데이터 뭉치로 볼 수 있기 때문에 하나의 클래스로 리팩토링 할 수 있다. 아래에서 리팩토링 순서를 살펴보고자 한다. 

    public class Office {
    
        private String location;
        // areaCode + Number가 함께 사용됨.
        // Office 클래스에도 존재함.
        private String officeAreCode;
        private String officeNumber;
    
        public Office(String location, String officeAreCode, String officeNumber) {
            this.location = location;
            this.officeAreCode = officeAreCode;
            this.officeNumber = officeNumber;
        }
    
        public String officePhoneNumber() {
            return officeAreCode + "-" + officeNumber;
        }
    
        public String getOfficeAreCode() {
            return officeAreCode;
        }
    
        public void setOfficeAreCode(String officeAreCode) {
            this.officeAreCode = officeAreCode;
        }
    
        public String getOfficeNumber() {
            return officeNumber;
        }
    
        public void setOfficeNumber(String officeNumber) {
            this.officeNumber = officeNumber;
        }
    }

    아래와 같이 리팩토링 작업을 할 수 있다. 

    1. TelephoneNumber 클래스를 생성하고, 그곳으로 변수를 빼낸다. TelephoneNumber에는 새로운 변수가 추가되었기 때문에 적절한 Getter / Setter를 생성해준다.
    2. 기존의 Employee 클래스에는 PersonalPhoneNumber를 필드로 추가하고, 관련된 생성자를 생성한다. 
    3. Empolyee 클래스에서 personalAreaCode / personalNumber를 삭제하자. 그리고 기존에 사용하던 Getter / Setter는 삭제하면 된다. 왜냐하면 해당 메서드는 이미 TelephoneNumber 클래스로 이동했기 때문이다.
      • 만약 코드를 그대로 남겨둬야 한다면, TelephoneNumber를 통해 Deligate 할 수 있다. 

    이제 Employee 클래스와 TelephoneNumber 클래스의 리팩토링이 끝났다. 같은 데이터 뭉치를 쓰고 있던 Office 클래스도 동일한 방식으로 리팩토링 하면 된다. 결과는 아래와 같이 된다. 

    public class Office {
    
        private String location;
        private TelephoneNumber officePhoneNumber;
    
        public Office(String location, TelephoneNumber officePhoneNumber) {
            this.location = location;
            this.officePhoneNumber = officePhoneNumber;
        }
    }
    
    public class Employee {
    
        private String name;
        private TelephoneNumber personalPhoneNumber;
    
        // areaCode + Number가 함께 사용됨.
        public Employee(String name, TelephoneNumber telephoneNumber) {
            this.name = name;
            this.personalPhoneNumber = telephoneNumber;
        }
    
        public String getName() {
            return name;
        }
    
        public void setName(String name) {
            this.name = name;
        }
    }
    
    public class TelephoneNumber {
    
        private String personalAreaCode;
        private String personalNumber;
    
        public TelephoneNumber(String personalAreaCode, String personalNumber) {
            this.personalAreaCode = personalAreaCode;
            this.personalNumber = personalNumber;
        }
    
        public String getPersonalAreaCode() {
            return personalAreaCode;
        }
    
        public void setPersonalAreaCode(String personalAreaCode) {
            this.personalAreaCode = personalAreaCode;
        }
    
        public String getPersonalNumber() {
            return personalNumber;
        }
    
        public void setPersonalNumber(String personalNumber) {
            this.personalNumber = personalNumber;
        }
    
        public String phoneNumber() {
            return this.personalAreaCode + "-" + this.personalNumber;
        }
    }

    FutureMore → 문맥 점검.

    위에서 리팩토링이 완료된 것처럼 보이지만, 디테일한 부분이 완료되지 않았다. 클래스로 변수를 뺀 후에, 변수의 이름이 Context에 맞는지를 확인하는 작업이 남았다.

    TelephoeNumber 클래스에는 PersonalAreaCode, PersonalPhoneNumber 필드가 있다. 이 녀석들은 Employee 클래스에서 가져온 변수인데, 해당 클래스에 있을 때는 문맥상 맞는 변수였다. 하지만 TelephoneNumber 클래스의 필드 관점에서는 적절하지 않다. 왜냐하면 개인 전화번호가 아니라 사무실 전화번호도 될 수 있기 때문이다.

    따라서 문맥에 맞는 필드명과 메서드명으로 수정해주면, TelephoneNumber 클래스의 최종 모습은 다음과 같다. 

    public class TelephoneNumber {
    
        private String areaCode;
        private String phoneNumber;
    
        public TelephoneNumber(String areaCode, String phoneNumber) {
            this.areaCode = areaCode;
            this.phoneNumber = phoneNumber;
        }
    
        public String getAreaCode() {
            return areaCode;
        }
    
        public void setAreaCode(String areaCode) {
            this.areaCode = areaCode;
        }
    
        public String getPhoneNumber() {
            return phoneNumber;
        }
    
        public void setPhoneNumber(String phoneNumber) {
            this.phoneNumber = phoneNumber;
        }
    
        public String phoneNumber() {
            return this.areaCode + "-" + this.phoneNumber;
        }
    }

    'etc > 리팩토링' 카테고리의 다른 글

    리팩토링 30. 기본형을 객체로 바꾸기  (0) 2023.05.10
    냄새 11. 기본형 집착  (0) 2023.05.10
    냄새 9. 기능 편애  (0) 2023.05.10
    리팩토링 29. 클래스 인라인  (0) 2023.05.10
    리팩토링 28. 함수 인라인  (0) 2023.05.10

    댓글

    Designed by JB FACTORY