Java

If문 제거하기 (리펙토링)

void_melody 2023. 9. 21. 12:06

때는 약 1년 전으로 돌아가 우테코 프리코스를 하던 시절..

당시 과제들의 최우선 순위로 해야할 미션은 바로 클린 코딩이었다.

그 중에서도 나에게 신선한 충격을 주었던 건, if문을 최대 2중 중첩까지만 쓸 수 있게 제한한 것이었다.

If문의 중첩 수를 줄이기 위해서는 최대한 메서드를 쪼개거나, 도중에 return 하는 등의 방식을 사용했다.

 

그리고 인턴 기간 중에도 수많은 if문들은 나를 괴롭혔다. 

내가 짠 코드도 그렇게 짜놓으면 보기 힘든데 남이 보면 얼마나 힘들까.

그래서 다음 인턴이 왔을 때 내 코드를 좀 편하게 보기 위해 엄청나게 리펙토링 하고 간 기억이 난다.

 

이제 오늘 데브코스에서 배운 강의를 활용해 나름의 팁? 을 써보려 한다.

public enum CalculateType {
    ADD, MINUS, MULTIPLY, DIVIDE
}

public class CalculateCommand {
    private CalculateType calculateType;
    private int num1;
    private int num2;
    
    // 생성자, getter 생략
}

public int someMethod(CalculateCommand calculateCommand) {
        CalculateType calculateType = calculateCommand.getCalculateType();
        int num1 = calculateCommand.getNum1();
        int num2 = calculateCommand.getNum2();

        int result = 0;

        if(calculateType != null && calculateType.equals(CalculateType.ADD)) {
            result = num1 + num2;
        } else if(calculateType != null && calculateType.equals(CalculateType.MINUS)) {
            result = num1 - num2;
        } else if(calculateType != null && calculateType.equals(CalculateType.MULTIPLY)) {
            result = num1 * num2;
        } else if(calculateType != null && calculateType.equals(CalculateType.DIVIDE)) {
            if(num2 == 0) {
                throw new RuntimeException("0으로 나눌 수 없습니다.");
            } else {
                result = num1 / num2;
            }
        }

        return result;
    }

저 수많은 if문을 보아라...!

우선 좀 불편한 지점들을 보면

1. if문들에 calculateType != null 부분이 중복되어 있다.

2. 조건문에 실행되는 함수들이 어느정도 패턴이 유사한데 이를 묶을 수 없을까?

3. num2가 0인 경우를 사전에 미리 예외처리하면 어떨까?

 

먼저 1번과 3번을 실행해보자!

        if(calculateType == null) {
            return result;
        }

        if(calculateType.equals(CalculateType.DIVIDE) && num2 == 0) {
            throw new RuntimeException("0으로 나눌 수 없습니다.");
        }

        if(calculateType.equals(CalculateType.ADD)) {
            result = num1 + num2;
        } else if(calculateType.equals(CalculateType.MINUS)) {
            result = num1 - num2;
        } else if(calculateType.equals(CalculateType.MULTIPLY)) {
            result = num1 * num2;
        } else if(calculateType.equals(CalculateType.DIVIDE)) {
            result = num1 / num2;
        }

좀 괜찮아보인다. 하지만 단순히 null일 때 result를 return하는 것보다는 예외를 던지는 것이 조금 더 알맞는 것 같고, 

처음부터 calculateType이라는 객체가 생성될 때 이를 처리할 수 없을까?

 

public CalculateCommand(CalculateType calculateType, int num1, int num2) {
        if(calculateType == null) {
            throw new RuntimeException("CalculateType은 필수 값 입니다.");
        }

        if(calculateType.equals(CalculateType.DIVIDE) && num2 == 0) {
            throw new RuntimeException("0으로 나눌 수 없습니다.");
        }

        this.calculateType = calculateType;
        this.num1 = num1;
        this.num2 = num2;
    }

생성자 코드에서 넘겨받은 값에 대해 예외처리를 해줌으로써 이제 생성시점에서 바로 알 수 있게 되었다.

그러면 이제 검증 로직을 서비스단에서 해줄 필요가 없어졌다.

 

또 2번인 패턴을 묶어보자.

해당 Enum 변수에 따라오는 함수를 생성자 시점에서 만들어놓는 것이다.

public enum CalculateType {
    ADD ((num1, num2) -> num1 + num2),
    MINUS ((num1, num2) -> num1 - num2),
    MULTIPLY ((num1, num2) -> num1 * num2),
    DIVIDE ((num1, num2) -> num1 / num2);

    CalculateType(BiFunction<Integer, Integer, Integer> expression) {
        this.expression = expression;
    }

    private BiFunction<Integer, Integer, Integer> expression;

    public int calculate(int num1, int num2) {
        return this.expression.apply(num1, num2);
    }
}

이렇게 enum의 생성자 시점에서 해당 호출할 함수를 가지게 해놓으면, 그냥 난 호출하면서 해당 공통 함수를 호출하면 끝이다.

 

public int someMethod(CalculateCommand calculateCommand) {
        CalculateType calculateType = calculateCommand.getCalculateType();
        int num1 = calculateCommand.getNum1();
        int num2 = calculateCommand.getNum2();

        int result = calculateType.calculate(num1, num2);

        return result;
    }

매우 심플해졌다.ㅎㅎㅎ,,,,

 

정리를 해보자.

1. 사전에 미리 return을 해보자.

2. Enum 안에 생성자로 넣어보자.

3. 생성 시점에 유효성 검사를 미리 해보자.