if문은 자바에 핵심 문법입니다. 하지만 너무 많은 if문은 코드를 읽기 힘들게 만들고, 코드를 수정하기도 힘들게 만들고, 문제가 발생했을 때 디버깅도 어렵습니다. 따라서 같은 로직이라면 if문이 없이 작성된 코드가 가독성이 높은 코드가 될 가능성이 높습니다.
public class Client {
public int someMethod(CalculateCommand calculateCommand) {
CalculateType calculateType = calculateCommand.getCalculateType();
int num1 = calculateCommand.getNum1();
int num2 = calculateCommand.getNum2();
int result = 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;
}
return result;
}
}
이 코드는 많은 if문을 가지고 있습니다. 하지만 그렇다고 해서 읽기 어려운 코드는 아닙니다.
하지만 이 코드는 동작적으로도 문제를 가지고 있습니다.
1. calculateType에 대한 null 체크 없음
2. 나누기 시 num2가 0이 아닌지 체크 없음
해당 로직을 추가해보도록 하겠습니다.
public class Client {
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;
}
}
코드가 굉장히 복잡해진 것을 확인할 수 있습니다. 간단한 체크 로직이 추가되었지만 코드의 가독성이 굉장히 떨어진 것을 확인할 수 있습니다. 이 코드를 개선해보도록 하겠습니다.
public class Clienttwo {
public int someMethod(CalculateCommand calculateCommand) {
CalculateType calculateType = calculateCommand.getCalculateType();
int num1 = calculateCommand.getNum1();
int num2 = calculateCommand.getNum2();
int result = 0;
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;
}
return result;
}
}
계산하는 로직 전에 체크하는 로직을 먼저 진행하여 Early return 또는 exception을 발생하도록 하는 코드입니다. 직전의 코드보다 가독성이 나아졌습니다.
public enum CalculateType {
ADD((num1, num2) -> num1 + num2),
MINUS((num1, num2) -> num1 - num2),
MULTIPLY((num1, num2) -> num1 * num2),
DIVIDE((num1, num2) -> num1 / num2);
private BiFunction<Integer, Integer, Integer> expression;
CalculateType(BiFunction<Integer, Integer, Integer> expression) {
this.expression = expression;
}
public int calculate(int num1, int num2) {
return this.expression.apply(num1, num2);
}
}
enum 클래스에 연산을 넣었습니다. 해당 코드의 설명은 전에 게시글에 있으니 참고하시면 되겠습니다.
public class Client {
public int someMethod(CalculateCommand calculateCommand) {
CalculateType calculateType = calculateCommand.getCalculateType();
int num1 = calculateCommand.getNum1();
int num2 = calculateCommand.getNum2();
int result = 0;
if (calculateType == null) {
return result;
}
if (calculateType.equals(CalculateType.DIVIDE) && num2 == 0) {
throw new RuntimeException("0으로 나눌 수 없습니다");
}
result = calculateType.calculate(num1, num2);
return result;
}
}
Client 클래스의 코드에 if문을 지워지게 됩니다. 지금 코드도 나쁘지 않지만 더 개선해보도록 하겠습니다.
if (calculateType == null) {
return result;
}
if (calculateType.equals(CalculateType.DIVIDE) && num2 == 0) {
throw new RuntimeException("0으로 나눌 수 없습니다");
}
이 코드는 말하자면 유효성 검사를 하는 코드입니다. 이 코드는 언제 실행되어야 할까요? 현재는 두변수를 사용하는 시점에 유효성 검사를 하고 있습니다. 이 체크 로직은 변수를 사용하는 곳에는 항상 들어가야하는 코드가 되었습니다.
사용하는 시점이 아닌 CalculateCommand를 생성하는 시점에 유효성 검사를 하는 것이 맞습니다.
public class CalculateCommand {
private CalculateType calculateType;
private int num1;
private int num2;
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;
}
public CalculateType getCalculateType() {
return calculateType;
}
public int getNum1() {
return num1;
}
public int getNum2() {
return num2;
}
}
CalculateCommand 생성자에서 유효성 검사 코드를 추가하였습니다. 이제 Client 코드를 확인해보도록 하겠습니다.
public class Client {
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;
}
}
if문이 완전히 없어진 코드가 되었습니다. 코드의 가독성은 굉장히 개선되었고, 이제 코드를 사용할 때마다 유효성 검사를 하지 않아도 CalculateCommand를 생성하는 시점에 알아서 유효성 검사를 할 수 있게 되었습니다.
결과의 변경없이 코드의 구조를 재조정 하는 것, 가독성을 높이거나 유지보수를 편하게 하는 것 입니다. 위에 코드를 리팩토링 했다고 할 수 있는 것 입니다.
결과의 변경없이 코드가 바뀌었다는 것을 어떻게 알 수 있을까요? 바로 테스트코드가 이를 증명해줍니다. 리팩토링 전의 테스트코드의 결과와 바뀐 코드의 테스트코드의 결과가 같다면 그것은 결과가 같다는 것을 보장해주는 것과 같습니다.
개발자는 단순히 기능을 개발하는 것 뿐만 아니라 추후 유지보수에 대해서도 생각을 해야하고 확장성과 가독성을 생각해서 코드를 작성해야 합니다. 그것을 가능하게 하기 위해서는 객체 지향적으로 코드를 작성할 줄 알아야 하고, 테스트 코드도 작성할 줄 알아야 좋은 개발자가 된다고 생각합니다.
해당 게시글은 프로그래머스 스쿨 강의
"실무 자바 개발을 위한 OOP와 핵심 디자인 패턴(푸)"
를 정리한 내용입니다. 쉽게 잘 설명해주시니 여러분도 강의를 듣는 것을 추천드립니다.