2020.04.06-개발일지

어떻게 하면 switch 지옥에서 벗어날 수 있을까?

사용자의 입력을 직접적으로 받아야 하기에 다른 대학생들이 하듯 자연스럽게 switch 문을 사용해서 구현했습니다. 문제는 case가 너무 많아 수정하기 어렵고 가독성 또한 많이 떨어진다는 점 입니다.

///
case 1:
    MyuserDAO myuserDAO = MyuserDAO.getInstance();
    myuserDAO.list();
    break;
case 2:
    MovieDAO movieDAO = MovieDAO.getInstance();
    movieDAO.movieMenu();
    break;
case 3:
    PayMentStaticsDAO mentStaticsDAO = PayMentStaticsDAO.getInstance();
    mentStaticsDAO.list();
    break;
case 4:
    AdDAO adDAO = AdDAO.getInstance();
    adDAO.selectAdvertiseMents();
    break;
case 5:
    MovieAdDAO movieAdDAO = MovieAdDAO.getInstance();
    movieAdDAO.list();
    break;
case 6:
    EmployeeDAO employeeDAO = EmployeeDAO.getInstance();
    employeeDAO.selectEmployees();
    break;
///case ....
(이런식으로 30개 정도의 case가 존재한다….)

클린코드에서는 case문과 관련하여 다음과 같은 내용이 있습니다.

​ ···

switch 문은 작게 만들기 어렵다

​ ···

‘한 가지’ 작업만 하는 switch 문도 만들기 어렵다

​ ···

위 함수는 몇가지 문제가 있다. 첫째 함수가 길다. 둘째 한가지 작업만을 수행하지 않는다. 세째 SRP(Single Responsebility Principle)를 위반한다. 네째 OCP(Open Closed Principle)를 위반한다.

​ ···

동일한 문제(수정하기 힘듦, 가독성이 안 좋음)를 겪고 있기에 해당 코드를 수정하기로 결정하면서, 고민한 내용입니다.

디자인 패턴을 이용하여 개발

기본적으로 그리고 당연하게도 MVC 로 개발하려 했으나, 사용자 입력을 어떻게 처리할 수 있을지 고민했습니다. command pattern 또는 template pattern 을 사용하려 했으나 마찬가지로 input이 자꾸 발목을 잡았습니다.

라이브러리를 사용해 볼것

spring shell project 또는 picoli 등이 검색됐으나, terminal 에서 명령어 사용하는 듯한 방식이라 리팩토링 수준이 아니라 아예 새로 작성하는 것과 같음. 오히려 shell script 작성하기 어려울때, 프로그래밍으로 하면 빠르게 할 수 있을때 사용하면 좋아보임

결론! Spring에서의 Controller 같은 것을 만들자….!

사용자의 input 을 처리하는 클래스를 만들고 그것을 feature별로 패키지를 분리합니다. 이때 factory pattern을 사용하면 switch문을 그냥쓰는 것이랑 다를바가 없어서 Mapper라고 불리는 annotaion을 만들기로 하였습니다.

알게된 내용

리플렉션 기능을 이용하려면 클래스 정보를 가져와야 합니다. 객체는 자신의 클래스 정보 또는 인스턴스의 정보는 알 수 있으나 그 외에 정보는 알수가 없습니다. 즉, 실행되는 시점에 참조 불가능한 객체의 클래스 정보는 알수가 없습니다…. ClassLoader 를 이용해야 해서 일종의 컨테이너를 직접 구현해야 하는데 이 부분은 라이브러리를 사용하기로 했습니다.

reflection 은 runtime 시점에 class의 다양한 정보를 가져올 수 있는 일종의 헬퍼 클래스입니다. reflection 을 이용하여 @MenuMapper 어노테이션을 설정한 클래스 정보를 가져옵니다.

private Set<Class<?>> getMenuMapperAnnotatedClasses() {
  Reflections reflections = new Reflections();
  return reflections.getTypesAnnotatedWith(MenuMapper.class);
}

해당 클래스에서 사용자의 input과 매칭되는, @MenuMapping 으로 선언된 메서드 정보를 전부 가져 옵니다.

private List<Method> getMenuMappingMethods(Method[] declaredMethods) {
  List<Method> list = new LinkedList<>();
  for (Method method : declaredMethods) {
    if (method.getAnnotation(MenuMapping.class) != null) {
      list.add(method);
    }
  }
  return list;
}

사용자의 입력은 전부 select 메서드를 호출합니다. 해당 객체 내부에 @MenuMapping 의 value가 사용자 입력과 같다면, 해당 함수를 호출합니다.

public void select(String requestMenu) {
  for (Class cls : getMenuMapperAnnotatedClasses()) {
    for (Method method : getMenuMappingMethods(cls.getDeclaredMethods())) {
      String annotationValue = method.getAnnotation(MenuMapping.class).value();
      if (StringUtils.equals(annotationValue, requestMenu)) {
        try {
          method.invoke(cls.newInstance());
        } catch (IllegalAccessException | InstantiationException | InvocationTargetException e) {
          e.printStackTrace();
        }
      }
    }
  }
}

스프링처럼 코딩하기

그 결과 아래와 switch 문이 없어지고 깔끔하게 분리되었습니다.

///
@MenuMapping("직원 정보")
public void getEmployees() {
  List<EmployeeDTO> employees = EmployeeDAO.getInstance().selectEmployees();
  employees.forEach(employeeDTO -> System.out.println(employeeDTO.toString()));
}

@MenuMapping("직원 정보 검색")
public void getEmployeeByName() throws IOException {
  String employeeName = ConsoleUtil.readString("직원을 검색합니다. 이름을 입력하세요.(*를 누르면 모든 정보를 출력합니다.)");
  List<EmployeeDTO> employees = EmployeeDAO.getInstance().selectEmployeeByName(employeeName);
  employees.forEach(employeeDTO -> System.out.println(employeeDTO.toString()));
}

@MenuMapping("부서 별 직원 검색")
public void getEmployeesByRole() throws IOException {
  String employeeRole = ConsoleUtil.readString("검색하고자 하는 부서의 팀원을 출력합니다");
  List<EmployeeDTO> employees = EmployeeDAO.getInstance().selectEmployeeByEmployeeRole(employeeRole);
  employees.forEach(employeeDTO -> System.out.println(employeeDTO.toString()));
}
///

가능한 스프링의 요소들을 사용하지 않고 개발하려 했습니다. 아이러니하게도 가장 좋은 방법이 스프링을 따라 하는 거였고 리플렉션을 통해 문제를 해결했습니다. 익숙한 방법이 아닌 방법으로 코딩하거나 프레임워크를 따라서 코딩해보는것도 좋은 방식인 것 같습니다.

댓글남기기