DEV ℧ Developer Diary

[Refactoring] 냄새 12. 반복되는 switch 문

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

냄새 12. 반복되는 switch 문

  • 예전에는 switch 문이 한번만 등장해도 코드 냄새로 생각하고 다형성 적용을 권장했다.
  • 하지만 최근에는 다형성이 꽤 널리 사용되고 있으며, 여러 프로그래밍 언어에서 보다 세련된 형태의 switch 문을 지원하고 있다.
  • 따라서 오늘날은 “반복해서 등장하는 동일한 switch 문”을 냄새로 여기고 있다.
  • 반복해서 동일한 switch 문이 존재할 경우, 새로운 조건을 추가하거나 기존의 조건을 변경할 때모든 switch 문을 찾아서 코드를 고쳐야 할지도 모른다.

예제 코드

아래의 코드는 Full-time, Part-time, Temporal의 각각에 타입을 switch 문을 통해 휴게시간을 반환하는 로직이다.

아래의 코드를 Factory 패턴을 이용하여, 리팩토링 해보자.

  • Employee
public class Employee {

    public int vacationHours(String type) {
        int result;
        switch (type) {
            case "full-time": result = 120;
            case "part-time": result = 80;
            case "temporal": result = 32;
            default: result = 0;
        }
        return result;
    }
}

먼저 당연하게도 테스트를 돌려보도록 하자.

리팩토링1

테스트를 실패하는 것을 볼 수 있다. 여기서 테스트의 중요성을 확인 해볼 수 있다.

나중에 리팩토링 후에 로직이 제대로 돌아가지 않을경우 리팩토링이 문제였는지, 원래의 코드가 문제였는지 파악을 하기 어렵기 때문이다.

해당 버그는 간단한다.

switch문에 break;로 빠져나오지 않기 때문에, default의 조건까지 계속 타고 내려가기 때문에 0을 return 한다.

해당 코드를 수정하고 다시 테스트를 돌려보자. 테스트를 성공하는 것을 확인할 수 있다.

리팩토링2

이제 리팩토링을 진행해 보도록 하자.

먼저 Employee를 추상화 하여, 자식객체에서 구현할 수 있도록 한다.

public abstract class Employee {
    public abstract int vacationHours();
}

각 타입에 맞춰 Emplyee를 상속하는 자식 객체를 만들어 준다.

그리고 vacationHours 를 @Override받아 구현해주도록 하자.

public class FullTimeEmployee extends Employee {
    @Override
    public int vacationHours() {
        return 120;
    }
}
public class PartTimeEmployee extends Employee {
    @Override
    public int vacationHours() {
        return 80;
    }
}
public class TemporalEmployee extends Employee {
    @Override
    public int vacationHours() {
        return 32;
    }
}

switch 문 대신 하위 instance문을 반환하는 Factory 클래스를 만들어 준다.

type을 key값으로 가지고 자식객체를 값으로 가지는 HashMap을 만들어 type이 들어올경우 알맞은 자식객체의 인스턴스를 생성하도록 한다.

public class EmployeeFactory {

    private static Map<String, Employee> employeeMap = new HashMap<>();

    static {
        employeeMap.put("full-time", new FullTimeEmployee());
        employeeMap.put("part-time", new PartTimeEmployee());
        employeeMap.put("temporal", new TemporalEmployee());
    }

    public static Employee createEmployee(String type) {
        if (employeeMap.containsKey(type))
            return getInstanceEmployee(type);
        else
            throw new IllegalArgumentException(type);
    }

    private static Employee getInstanceEmployee(String type) {
        return employeeMap.get(type);
    }
}

이제 테스트 코드를 수정하고 다시 테스트를 돌려보도록 하자.

리팩토링3