Home [리팩터링] chapter 10 조건부 로직 간소화
Post
Cancel

[리팩터링] chapter 10 조건부 로직 간소화

p.354 조건문 분해하기

거대한 코드 블록이 주어지면 코드를 부위별로 분해한 다음 해체된 코드 덩어리들을 각 덩어리의 의도를 살린 이름과 함수 호출로 바꿔주자. 그러면 전체의 의도가 더 확실히 드러난다. 조건문이 보이면 나는 조건식과 각 조건절에 이 작업을 해주길 좋아한다. 이렇게되면 해당 조건이 무엇인지 강조하고, 그래서 무엇을 분기하였는지가 명확해진다. 분기한 이유 역시 더 명확해진다.

기존의 코드

1
2
3
4
5
if (!aDate.isBefore(plan.summerStart) && !aDate.isAfter(plan.summerEnd)) {
  charge = quantity * plan.summerRate;
} else {
  charge = quantity * plan.regularRate + plan.regularCharge;
}

수정된 코드

1
2
3
4
5
6
7
8
9
10
11
12
13
charge = summer() ? summerCharge() : regularCharge();

function summer() {
  return !aDate.isBefore(plan.summerStart) && !aDate.isAfter(plan.summerEnd);
}

function summerCharge() {
  return quantity * plan.summerRate;
}

function regularCharge() {
  return quantity * plan.regularRate + plan.regularCharge;
}

p.357 조건식 통합하기

비교화는 조건은 다르지만 그 결과로 수행하는 동작은 똑같은 코드들이 더러 있는데, 어차피 같은 일을 할거라면 조건 검사도 하나로 통합하는 것이 낫다.

기존의 코드

1
2
3
if (appleCnt < 2) return 0;
if (bananaCnt > 3) return 0;
if (watermelonCnt > 10) return 0;

수정된 코드

1
2
3
4
5
if (fruitCnt()) return 0;

function fruitCnt() {
  return appleCnt < 2 || bananaCnt > 3 || watermelonCnt > 10;
}

조건부 코드를 통합하는게 중요한 이유는 두가지다. 첫째, 여러 조각으로 나뉜 조건들을 하나로 통합함으로써 내가 하려는 일이 더 명확해진다. 나눠서 순서대로 비교해도 결과는 같지만, 읽는 사람은 독립된 검사들이 우연히 함께 나열된 것으로 오해할 수 있다. 두번째 이유는 이 작업이 함수 추출하기 까지 이러질 가능성이 높기 떄문이다. 복잡한 조건식을 함수로 추출하면 코드의 의도가 훨씬 분명하게 드러나는 경우가 많다.

p.360 중첩조건문을 보호구문으로 바꾸기

조건문은 주로 두가지 형태로 쓰인다. 참인 경로와 거짓인 경로 모두 정상 동작으로 이어지는 형태와 한쪽만 정상인 형태다. 두 형태 모두 의도하는 바가 서로 다르므로 그 의도가 코드에 드러나야 한다. 나는 두 경로 모두 정상동작이라면 if와 else 절을 사용한다. 한쪽만 정상이라면 비정상 조건을 if에서 검사한 다음, 조건이 참이면(비정상이면) 함수에서 빠져나온다. 두번쩨 검사 형태를 흔히 보호구문 이라고 한다. … 보호구문은 ‘이건 함수의 핵심이 아니다. 이 일이 일어나면 무언가 조치를 취한 후 함수에서 빠져나온다’라고 이야기한다. … 반환점이 하나여야한다는 규칙은 유용하지 않다. 코드에서는 명확함이 핵심이다. 반환점이 하나일 떄 함수의 로직이 더 명확하다면 그렇게 하자.그렇지 않다면 하지 말자.

기존 코드

1
2
3
4
5
6
7
8
9
10
11
12
function getPayAmount() {
  let result;
  if (isDead) result = deadAmount();
  else {
    if (isSeparted) result = separatedAmount();
    else {
      if (isRetired) result = retiredAmount();
      else result = normalPayAmount();
    }
  }
  return result;
}

수정된 코드

1
2
3
4
5
6
function getPayAmount() {
  if (isDead) return deadAmount();
  if (isSeparated) return separatedAmount();
  if (isRetired) return retiredAmount();
  return normalPayAmount();
}

10.3 10.4

p.404 어서션 추가하기

특정 조건이 참 일때만 제대로 동작하는 코드가 있을 때 어서션을 사용할 수 있다. 어서션은 항상 참이라고 가정하는 조건부 문장으로 어서션이 실패했다는건 프로그래머가 잘 못했다는 뜻이다. 어서션의 실패는 시스템의 다른 부분에서는 절대 검사하지 않아야하며, 어서션이 있고 없고가 프로그램 기능의 정상 동작에 아무런 영향을 주지 않도록 작성되어야 한다. 어서션은 프로그램이 어떤 상태임을 가정한 채 실행되는 지를 다른 개발자에게 알려주는 도구이기도 하다. 디버깅하기 편하고 이런 소통 수단으로서의 가치도 있어서, 추적하던 버그를 잡은 후에도 코드에 남겨두곤 한다. 테스트 코드가 있다면 어서션의 디버깅 용도로서의 효용은 줄어든다. 단위 테스트를 꾸준히 추가하여 사각을 좁히면 어서션보다 나을 떄가 많다. 하지만 소통면에서 어서션도 매력적임

기존 코드

1
2
3
4
5
6
7
8
//이 코드에는 할인율이 항상 양수라는 가정이 깔려있다.
//할인율이 양수임을 확인하는 어서션을 추가해본다.
class Customer {
    ...
  applyDiscount(aNumber) {
    return this.discountRate ? aNumber - this.discountRate * aNumber : aNumber;
  }
}

수정된 코드

1
2
3
4
5
6
7
8
9
10
11
12
13
14
class Customer {
    ...
    //discountRate 세터를 추가해서 discountRate를 받아올 때 양수임을 확인한다.
    set discountRate(aNumber){
      assert(null === aNumber || aNumber >=0);
      this._discountRate = aNumber;
    }
  applyDiscount(aNumber) {
    if (!this.discountRate) return aNumber;
    else {
      return aNumber - this.discountRate * aNumber;
    }
  }
}

p.407 제어 플래그를 탈출문으로 바꾸기

제어플래그란 코드의 동작을 변경하는데 사용되는 변수를 말하며, 어딘가에서 값을 계산해 제어 플래그에 설정한 후 다른 어딘가의 조건문에 검사하는 형태로 쓰인다.

기존 코드

1
2
3
4
5
6
7
8
9
10
for(cont p of people){
    if(!found){
        if(p === 'Vador'){
            sendAlert();
            found = true;
        }
    }
}

수정된 코드

1
2
3
4
5
6
7
8
for(cont p of people){
    if(p === 'Vador'){
        sendAlert();
        break;
    }

}

This post is licensed under CC BY 4.0 by the author.

[HTML] HTML 101 img, aria-label

[추천] 주니어 개발자를 위한 소소한 개발 외 사이트와 VSCode Extension 정리

Comments powered by Disqus.