10. 조건부 로직 간소화

느낀점#

  • 10.1 에서 조거문 분해 뿐만아니라 내부 로직을 함수로 추출 하기도 했는데 이번 회사 작업에서 적용해봐야겠다 (함수 하나가 너무 길다 ㅎㅎ)
  • 10.3 은 함수당 인덴트 제한, if 의 else를 최대한 안쓰기, 로컬 변수 최소화를 적용하다보면 자연스레 early return 적용하면서 했던 리팩터링인데 이렇게 따로 빠져있다니 신기
    • 이에 대한 용어로 보호 구문(guard clause) 라고 부르니 더 신기

10.1 조건문 분해하기#

// before
if (!date.isBefore(plan.summerStart()) && !date.isAfter(plan.summerEnd())) {
charge = quantity * plan.summerRate();
} else {
charge = quantity * plan.regularRate() + plan.regularServiceCharge();
}
// after
if (isSummer()) {
charge = summerCharge();
} else {
charge = regularCharge();
}
// after 삼항 ver.
charge = isSummer() ? summerCharge() : regularCharge();
  • 거대한 코드 블록이 주어지면 코드를 부위별로 분해
    • 그 다음 해체된 코드 덩어리들을 함수 추출하여 의미있는 이름 부여
    • 전체적인 의도가 더 확실히 드러남
  • 위와 같이 진행되면 조건이 무엇인지 / 무엇을 분기했는지 / 분기의 이유가 무엇인지 더 명확해짐

10.2 조건식 통합하기#

// before
if (employee.seniroity() < 2) return 0;
if (employee.monthsDisabled() > 12) return 0;
if (employee.isPartTime()) return 0;
// after
if (isNotEligibleForDisability()) return 0;
private boolean isNotEligibleForDisability() {
return (employee.seniority() < 2)
|| (employee.monthsDisabled > 12)
|| (employee.isPartTime());
}
  • 비교하는 조건은 다르지만 수행하는 동작은 같을 때, 위와 같이 통합 가능
  • 조건부 코드 통합이 중요한 이유
    • 하나로 합치며 내가 하려는 일이 명확해짐
    • 해당 작업이 함수 추출하기로 이어질 가능성이 높음
  • 단, 독립된 검사로 판단된다면 해서는 안됨

10.3 중첩 조건문을 보호 구문으로 바꾸기#

// before
public PayAmount getPayAmount() {
PayAmount result;
if (isDead()) {
result = deadAmount();
} else {
if (isSeparated()) {
result = separatedAmount();
} else {
if (isRetired()) {
result = retiredAmount();
} else {
result = normalPayAmount();
}
}
}
return result;
}
// after
public PayAmount getPayAmount() {
if (isDead()) return deadAmount();
if (isSeparated()) return separatedAmount();
if (isRetired()) return retiredAmount();
return normalPayAmount();
}
  • 보호 구문 (guard clause): if에서 검사한 다음, 조건이 참 (즉 비정상) 이면 함수에서 빠져나오는 패턴
  • 보호 구문의 의도는 핵심 의도를 부각하는대 있음

10.4 조건부 로직을 다형성으로 바꾸기#

// before
switch (bird.type()) {
case "유럽 제비":
return "보통이다";
case "아프리카 제비":
return (bird.numberOfCoconuts > 2) ? "지쳤다" : "보통이다";
case "노르웨이 파랑 앵무":
return (bird.voltage() > 100) ? "그을렸다" : "예쁘다";
default:
return "알 수 없다";
}
// after
interface Bird {
public default String plumage() {
return "알 수 없다";
}
}
class EuropeanSwallow implements Bird {
@Override
public String plumage() {
return "보통이다";
}
}
...
  • 복잡한 조건부 로직은 해석하기 가장 난해하여, 다형성을 이용하면 확실히 분리할 수도 있다

10.5 특이 케이스 추가하기#

// before
if (customer.equals("미확인 고객")) {
this.name = "거주자";
}
// after
public class UnkownCustomer extends Customer {
@Override
public String getName() {
return "거주자";
}
}
// enum type
public enum Customer {
NORMAL("일반인"),
UNKNOWN("거주자"),
;
private String name;
...
public String getName() {
return this.name;
}
}
  • 특정값을 확인한 후 똑같은 동작하는 코드를 특이 케이스 패턴으로 리팩터링
  • 특이 케이스 패턴은 특수항 경우 공통 동작을 요소 하나에 모아서 사용하는 패턴
  • Customer의 데이터가 name 밖에 없다면 enum으로 활요하는게 좋아보임
    • 다른 여러 데이터가 동적으로 담겨야 한다면 상속이나 인터페이스로 분리시키는게 좋아보임
  • javascript에서는 동적타이핑을 사용해 따로 상속을 안하는 것이 인상적
    • 개인적 취향으로는 동적타이핑 언어도 타입을 동일화 시키는게 가독성이 좋은 것 같다
  • enrich* 함수 이용시 딥카피(cloneDeep) 을 사용하여 기존 데이터와 분리시키는 모습이 인상적

10.6 어서션 추가하기#

  • 특정 조건이 참일때만 실행하자는 의미 전달을 위해 assert 구문을 넣음
  • 마지막에도 나오지만 정말 참이어야 한다! 의 경우만 assert를 써야하지 남발은 하면 안됨
  • 혹은 자바에서는 검사 예외(Exception), 비검사 예외(RuntimeException)를 구분해서 의도적으로 예외를 구현 혹은 이용하는 것이 바람직하다 생각함

10.7 제어 플래그를 탈출문으로 바꾸기#

  • early return 과 비슷한 맥락으로, loop 시 맨 앞단에 검사하여 continue/break를 넣는 것이 depth를 줄이고, 비정상적 동작과 정상적 동작이 구분됨으로 가독성이 좋아짐
  • 아예 loop안에서 정상적이지 않으면 return을 할 수도 있음
Last updated on