DEV ℧ Developer Diary

[Refactoring] 냄새 17. 메시지 체인

해당 포스트는 inflearn의 백기선님의 강의인 리팩토링 을 듣고 정리한 글입니다.

냄새 17. 메시지 체인

  • 레퍼런스를 따라 계속해서 메소드 호출이 이어지는 코드
    • 예) this.member.getCredit().getLevel().getDescription()
  • 해당 코드의 클라이언트가 코드 체인을 모두 이해해야 한다.
  • 체인 중 일부가 변경된가면 클라이언트의 코드도 변경해야 한다.
  • 관련 리팩토링
    • “위임 숨기기 (Hide Delegate)”를 사용해 메시지 체인을 캡슐화를 할 수 있다. 캡슐화를 통해 클라이언트가 최소한의 정보만 알 수 있도록 변경할 수 있다.
    • “함수 추출하기 (Extract Function)”로 메시지 체인 일부를 함수로 추출한 뒤 “함수 옮기기 (Move Function)”으로 해당 함수를 적절하게 이동할 수 있다.

리팩토링 37. 위임 숨기기

  • 캡슐화 (Encapsulation)란 어떤 모듈이 시스템의 다른 모듈을 최소한으로 알아야 한다는 것이다. 그래야 어떤 모듈을 변경할 때, 최사한의 모듈만 그 변경에 영향을 받을 것이고, 그래야 무언가를 변경하기 쉽다.
  • 처음 개게 지향에서 캡슐화를 배울 때 필드를 메소드로 숨기는 것이라 배우지만, 메소드 호출도 숨길 수 있다.
    • person.department().manager(); -> person.getManager()
    • 이전의 코드는 Department 를 통해 Manager에 접근할 수 있다는 정보를 알아야 하지만, getManager()를 통해 위임을 숨긴다면 클라이언트는 person의 getManager()만 알아도 된다. 나중에 getManager() 내부 구현이 변경될지라도 getManager()를 사용한 코드는 그대로 유지할 수 있다.
  • Department
public class Department {

    private String chargeCode;

    private Person manager;

    public Department(String chargeCode, Person manager) {
        this.chargeCode = chargeCode;
        this.manager = manager;
    }

    public String getChargeCode() {
        return chargeCode;
    }

    public Person getManager() {
        return manager;
    }
}
  • Person
public class Person {

    private String name;

    private Department department;

    public Person(String name) {
        this.name = name;
    }

    public String getName() {
        return name;
    }

    public Department getDepartment() {
        return department;
    }

    public void setDepartment(Department department) {
        this.department = department;
    }
}

여기서 해당 체이닝을 사용하는곳은 테스트코드에서 확인할 수 있다. 테스트 코드를 살펴보면 keesun.getDepartment().getManager(); 으로 Manager 정보를 가져오는 것을 볼 수 있다. person -> department -> manager 의 순으로 데이터를 가져온다.

@Test
void manager() {
    Person keesun = new Person("keesun");
    Person nick = new Person("nick");
    keesun.setDepartment(new Department("m365deploy", nick));

    Person manager = keesun.getDepartment().getManager();
    assertEquals(nick, manager);
}

테스트는 성공했다.

리팩토링1

이제 캡슐화를 통해 해당 체이닝을 변경해보도록 하자.

Person클래스에 getManager() 메소드를 추가하여 manager 정보를 반환하도록 하였다.

public class Person {

    ...

    Person getManager() {
        return getDepartment().getManager();
    }
}

해당 캡슐화를 진행한 이유는

만약 Manager의 정보를 Department를 더이상 거치지 않고 바로 가져오게 된다면, getManager()의 코드는 아래와 같이 변경될 것이다.

public class Person {
    private Person Manager;

    ...

    Person getManager() {
        return this.Manager;
    }
}

하지만 호출부인 테스트 코드의 getManager()은 변경하지않아도 정상작동을 확인할 수 있다.

@Test
void getManager_refactor() {
    Person nick = new Person("nick", null);
    Person keesun = new Person("keesun", new Department(nick));
    assertEquals(nick, keesun.getManager());
}

리팩토링2