오늘은 코드 리뷰를 해주신 것에 대해서 리팩토링을 진행하는 것에 상당히 많은 시간을 할애했다.
먼저 기존의 코드에서 null 을 제거하는 것 부터 진행하기로 했다.
Optional을 이용한 null 제거
리팩토링 하기 전의 코드는 다음과 같이 구성이 되어있다.
private MenuType makeMenuType(String inputMenuNumber) {
MenuType menu = null;
try {
menu = MenuType.findMenuType(inputMenuNumber);
} catch (WrongInputMenuException e) {
Console.printError(e.getMessage());
}
return menu;
}
private String calculate(String expression) {
String result = null;
try {
result = compute.compute(expression);
} catch (WrongInputExpressionException | ArithmeticException e) {
Console.printError(e.getMessage());
}
return result;
}
그리고 실제 서비스 로직에서는 다음과 같이 null 값이 들어오면 다르게 처리하도록 했었다.
if(menu == null)
continue;
...
if(result == null)
break;
확실히 보기 좋은 코드는 아닌 것 처럼 보인다. 이를 위해서 Optional 을 이용하여, 다음과 같이 코드를 리팩토링 할 수 있었다.
private Optional<MenuType> makeMenu(String expression) {
Optional<MenuType> menu = Optional.empty();
try {
menu = Optional.ofNullable(MenuType.findMenuType(expression));
} catch (IllegalArgumentException e) {
Console.printError(e.getMessage());
}
return menu;
}
private Optional<String> calculate(String expression) {
Optional<String> result = Optional.empty();
try {
result = Optional.ofNullable(accumulator.calculate(expression));
} catch (IllegalArgumentException | ArithmeticException e) {
Console.printError(e.getMessage());
}
return result;
}
하지만 첫 번째 케이스에서 Nullable인 상태에서도 한 가지 문제가 있었다. menu 필드는 switch 문에서 동작하도록 되어있는데, switch 문에 null 값이 들어가게 되면 Null Pointer Exception이 발생했고, 어떻게든 default 를 사용하여 잘못된 메뉴 입력 값이 들어오게 되면 내부적으로 아무런 작업도 하지 않고 멈추게끔 하고 싶었다. 따라서 이를 해결하기 위해서 새로운 Enum 객체를 만들어내었다.
// Enum MenuType
NULL("999")
public static MenuType findMenuType(String inputNumber) {
if (!MENU_TYPE_MAP.containsKey(inputNumber)) {
throw new IllegalArgumentException("잘못된 입력 메뉴가 들어왔습니다. 1, 2, 3 만 가능합니다.");
}
return MENU_TYPE_MAP.get(inputNumber);
}
...
// logic
MenuType menu = makeMenu(Console.inputMenuNumber())
.orElse(MenuType.NULL);
다음과 같이 새로운 상수 객체를 하나 더 추가해줌으로써 default로 null을 받을 수 있도록 하였다. 하지만 이러한 방법이 맞는지는 모르겠다. 뭔가 null 처리를 하기 위해서 어거지로 만들어낸 것 같다는 느낌이랄까? 해당 내용에 대해서는 멘토님에게 코드 리뷰를 재 요청드릴까 한다! 해당 리팩토링 과정을 진행하면서 생각보다 많은 시간을 쓰게 되었다. Optional 의 존재는 알고있지만 아직 적절하게 Optional을 사용하지는 못하는 것 같다ㅠㅠ 많은 숙달이 필요할 것으로 생각이 된다.
Pattern 객체의 캐싱
두 번째로는 Pattern 객체의 캐싱이다. 우선 이전에는 다음과 같은 코드로 구성이 되어있었다.
public class ConstantRegex {
public static final String EXPRESSION_FILTER_REGEX = "[()+\\-*/]|\\d+";
public static final String EXPRESSION_VALIDATION_REGEX = "\\s+|[()+\\-*/]|\\d+";
}
그리고 해당 코드에 대한 Matching을 확인하는 과정에서는 다음과 같이 코드를 작성했다.
Pattern pattern = Pattern.compile(ConstantRegex.EXPRESSION_FILTER_REGEX);
Matcher matcher = pattern.matcher(expression);
List<String> filterExpression = new ArrayList<>();
while(matcher.find()) {
filterExpression.add(matcher.group());
}
하지만 정규표현식을 사용하여 Pattern 객체를 생성하는 비용은 상당하다. 따라서 이러한 경우에는 미리 캐싱해서 객체를 재사용하는 것이 좋기 때문에 다음과 같이 변환하여 사용이 가능하다.
public class ConstantRegex {
public static final Pattern EXPRESSION_FILTER_REGEX_COMPILE = Pattern.compile(EXPRESSION_FILTER_REGEX);
public static final Pattern EXPRESSION_VALIDATION_REGEX_COMPILE = Pattern.compile("\\s+|[()+\\-*/]|\\d+");
}
Matcher matcher = ConstantRegex.EXPRESSION_FILTER_REGEX_COMPILE.matcher(expression);
List<String> filterExpression = new ArrayList<>();
while (matcher.find()) {
filterExpression.add(matcher.group());
}
위와 같은 방식으로 객체 생성에 대한 비용을 줄여서 사용할 수 있게 되었다!
회고
정말 아무것도 없는 상태에서 코드를 짜는 것이 더 쉽고 리팩토링 하는 과정이 생각보다 어렵다는 것을 몸소 깨달을 수 있었다. 이것 말고도 연산 과정을 스트림으로 변환하는 과정, 메서드를 객체지향 생활체조원칙에 맡게 depth를 1이상이 되지 않도록 하는 작업 등.. 어려운 작업들이 많았다. 하지만, 이러한 노력들이 쌓이고 쌓여서 더 좋은 객체지향에 맞는 개발을 하는 것이라는 생각이 든다!
추가적으로 금일 sqld (SQL 개발자) 자격증 시험을 보게 되었다. 이기적에서 제공해주는 SQL 개발자 문제집으로 공부를 했는데 거기에 나온 문제에 비해 난이도가 생각보다 더 쉬웠던 느낌이다. 하지만 그렇다고 이기적 출판사의 SQL 개발자 문제집은 추천을 하지 않는다. 오히려 노랭이 문제집 SQL 자격검정 실전 문제를 더 추천한다.
이유는 이기적의 문제집이 잘못된 답이 너무 많기 때문이다. 나는 3판으로 2번이나 잘못된 답이나 문제들이 개선되었음에도 불구하고, 계속해서 잘못된 답들이 나오는 것을 볼 수 있었다. 처음 배우는 입장에서 지식을 잘 못 익히게 되면 언러닝이라는 말이 있듯이, 다시 새로운 지식으로 갈아치우는 작업은 리소스가 많이 들기때문에 오히려 조금 더 어려워도 SQL 자격검정 실전 문제를 통해 공부를 하는게 좋지 않나하고 생각한다.해당 자격증을 공부하는 이유가 실전에서 써먹기 위함아닌가? 단순히 시험을 보기 위한 목적이라면 이기적 문제집을 보고 공부해도 상관이 없을 것 같다.
'프로그래머스 데브코스' 카테고리의 다른 글
프로그래머스 데브코스 10일차 - 팩토리 패턴 (2) | 2023.06.14 |
---|---|
프로그래머스 데브코스 9일차 - Static Inner 클래스를 사용해야 하는 이유, Optional orElse, orElseGet의 차이 (0) | 2023.06.12 |
프로그래머스 데브코스 7일차 - 롬복 트러블 슈팅, Enum 최적화 (1) | 2023.06.10 |
프로그래머스 데브코스 6일차 - 전략패턴 (3) | 2023.06.08 |
프로그래머스 데브코스 5일차 (0) | 2023.06.08 |