프로그래머스 데브코스

프로그래머스 데브코스 8일차 - Null 제거와, Pattern 객체 캐싱

Bombo_ 2023. 6. 11. 01:50
728x90

오늘은 코드 리뷰를 해주신 것에 대해서 리팩토링을 진행하는 것에 상당히 많은 시간을 할애했다.

먼저 기존의 코드에서 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 자격검정 실전 문제를 통해 공부를 하는게 좋지 않나하고 생각한다.해당 자격증을 공부하는 이유가 실전에서 써먹기 위함아닌가? 단순히 시험을 보기 위한 목적이라면 이기적 문제집을 보고 공부해도 상관이 없을 것 같다.