리팩토링 26. 함수 옮기기

    들어가기 전

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


    리팩토링 26. 클래스 추출하기(Extract Class)

    • 클래스가 다루는 책임이 많아질수록 클래스가 점차 커진다.
    • 클래스를 쪼개는 기준
      • 데이터나 메서드 중 일부가 매우 밀접한 관련이 있는 경우
      • 몇몇 데이터가 대부분 같이 바뀌는 경우
      • 데이터 또는 메서드 중 일부를 삭제한다면 어떻게 될 것인가?
      • 필드나 메서드를 한번 추출해보면, 같이 빠져나가야 하는게 무엇인지 컴파일 에러로 확인할 수 있음.
    • 하위 클래스를 만들어 책임을 분산 시킬 수도 있다. 
    • 클래스를 옮긴 후 해야 할 일
      • 추출된 클래스에 있는 필드 / 메서드의 문맥을 확인함. (메서드, 필드 이름은 적절한지?)
      • 이전 클래스의 메서드 중, 추출된 클래스의 변수를 많이 참조하는 것이 있으면 추출된 클래스로 이동. 
      • 전체적인 접근 지시자, final 키워드등을 점검한다. 
      • 순환 참조가 발생하는지도 점검
    • 정리
      1. Refactor → Extract Deligate로 일단 추출해봄.
      2. 필요한 필드 / 메서드를 클래스로 옮김
      3. 문맥을 고려한 필드명 / 메서드를 확인. 접근 지시자 확인. 
      4. 기존 클래스 정리. 
        1. 기존 클래스에 있던 메서드는 Deligation을 통해 처리 / 메서드 통째로 삭제 등으로 접근할 수 있음.

     

    클래스가 하는 일이 많아지면 클래스가 커진다. 너무 큰 클래스는 코드 복잡도가 높기 때문에 읽기 어렵다. 그렇기 때문에 트스트 하기도 어렵고, 클래스가 하는 일을 이해하기도 어려워진다. 따라서 이런 경우 클래스를 나눠서 코드 복잡도를 분산시킬 수 있다. 큰 클래스에서 다른 클래스를 추출해내는 작업을 해야하는 것이다. 

    • 클래스에 들어있는 필드 / 메서드들이 밀접하게 관련 있는 경우. 
    • 필드나 메서드를 바꿀 때, 같이 변경되는 경우.  (필드나 메서드를 한번 빼내보면, 같이 빠져나가야 하는 게 뭐가 있는지 컴파일 에러로 볼 수 있다.)

     


    코드

    Person 클래스를 살펴보면 officeAreaCode, officeNumber 필드는 관련이 있어 보인다. 관련있는 두 필드를 다른 클래스로 추출해보려고 한다면, 어떤 메서드 + 필드가 함께 추출되어야 하는지 알 수 있게 된다.

    public class Person {
    
        private String name;
    
        // 아래 두 필드는 관련이 있다. 이것을 extract deligate로 빼보자.
        // 빼보면 어떤 메서드 / 필드가 함께 나가야하는지 알 수 있음.
        private String officeAreaCode;
        private String officeNumber;
    
        public String telephoneNumber() {
            return this.officeAreaCode + " " + this.officeNumber;
        }
    
        public String name() {
            return name;
        }
    
        public void setName(String name) {
            this.name = name;
        }
    
        public String officeAreaCode() {
            return officeAreaCode;
        }
    
        public void setOfficeAreaCode(String officeAreaCode) {
            this.officeAreaCode = officeAreaCode;
        }
    
        public String officeNumber() {
            return officeNumber;
        }
    
        public void setOfficeNumber(String officeNumber) {
            this.officeNumber = officeNumber;
        }
    }

    아래의 Refactor → Extract Deligate를 클릭하면 특정 필드를 특정 클래스로 추출할 수 있다.

    이렇게 필드를 다른 클래스로 추출하고 나면, 기존 클래스에서는 필드에 영향을 받은 메서드들이 컴파일 에러를 보여주며 남아있다. 이 때 해당 메서드들을 처리하는 방법은 두 가지가 존재한다.

    1. 해당 메서드를 그대로 놔두고, 추출한 클래스로부터 Deligation을 통해서 처리한다.
    2. 해당 메서드를 삭제한다. 

    기존 클래스에서 컴파일 에러를 보여주는 메서드는 일단 추출한 클래스로 함께 복사시켜야 한다. 그리고 남은 메서드를 어떻게 처리할지를 고민해야한다. 위 두 가지 방법 중 하나를 고려해야한다. 

    그 다음으로 고려해야 할 부분은 새롭게 추출된 클래스에 있는 필드 / 메서드들이 문맥을 살펴보는 것이다. 예를 들어 officeAreaCode, officeNumber는 Person 클래스에서 사용될 때는 맞는 의미였다. 그렇지만 TelephoneNumber 클래스 관점에서 살펴보면 오피스 전화번호 일 수도 있고, 개인 전화번호를 표현할 수도 있게 되고 그렇다. 따라서 TelephoneNumber라는 클래스 이름을 고려해서 필드이름을 officeAreaCode → areaCode 등으로 수정해줘야한다. 


    최종 코드

    최종 코드를 살펴보면 다음과 같이 변경되는 것을 확인할 수 있다. 먼저 기존 Person 클래스다. 

    public class Person {
    
        private final TelephoneNumber telephoneNumber;
        private String name;
    
        public Person(TelephoneNumber telephoneNumber, String name) {
            this.telephoneNumber = telephoneNumber;
            this.name = name;
        }
    
        public String name() {
            return name;
        }
    
        public void setName(String name) {
            this.name = name;
        }
    
        public String telephoneNumber() {
            return this.telephoneNumber.toString();
        }
    
    }
    

    새롭게 추출된 TelephoneNumber 클래스다.

    public class TelephoneNumber {
    
        // 필드이름이 TelephoneNumber 클래스에 걸맞지 않다.
        private String areaCode;
        private String phoneNumber;
    
        public TelephoneNumber(String areaCode, String phoneNumber) {
            this.areaCode = areaCode;
            this.phoneNumber = phoneNumber;
        }
    
    
    
        public String officeAreaCode() {
            return this.areaCode;
        }
    
        public void setOfficeAreaCode(String officeAreaCode) {
            this.areaCode = officeAreaCode;
        }
    
        public String officeNumber() {
            return this.phoneNumber;
        }
    
        public void setOfficeNumber(String officeNumber) {
            this.phoneNumber = officeNumber;
        }
    
        @Override
        public String toString() {
            return this.areaCode + " " + this.phoneNumber;
        }
    }

     

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

    리팩토링 27. 필드 옮기기  (0) 2023.05.10
    냄새 8. 산탄총 수술  (0) 2023.05.10
    리팩토링 25. 함수 옮기기  (0) 2023.05.10
    리팩토링 24. 단계 쪼개기  (0) 2023.05.10
    냄새 7. 뒤엉킨 변경  (0) 2023.05.10

    댓글

    Designed by JB FACTORY