SW개발/Javascript

[Javascript]if, else 문 최소화하기

Javascript를 이용하여 개발을 하다보면 if, else문이 많아짐에 따라 가독성을 상당히 해치는 코드를 종종 볼 수 있습니다.

 

특히나 프론트 UI에서 조건에 따라 렌더링 및 데이터를 보여주는 로직이 존재하기때문에 이런 경우가 더 많은 것 같습니다.

 

저는 backends 언어로 Python을 사용하고 있는데 이때도 마찬가지로 if로 인한 indent가 많아지는 코드를 그다지 선호하지 않습니다.

가장 큰 이유는 가독성이 저하되어 정확히 어떤 기능을 하는 코드인지 파악하기가 힘들어지기 때문입니다.

 

이러던 도중 좋은 자료를 발견하게 되어 번역하면서 공부해보기로 하였습니다.

https://betterprogramming.pub/stop-putting-so-many-if-statements-in-your-javascript-3b65aaa4b86b

 

Stop Putting So Many If Statements in Your JavaScript

4 other ways of handling conditional logic in your code

betterprogramming.pub

 

https://www.digitalocean.com/community/posts/5-tips-to-write-better-conditionals-in-javascript

 

5 Tips to Write Better Conditionals in JavaScript | DigitalOcean

5 Tips to Write Better Conditionals in JavaScript

www.digitalocean.com

 

1. 조건문이 반복된다면 includes 메소드를 활용하자

// bad
function test(fruit) {
    if (fruit === 'apple' || 
        fruit === 'strawberry' ||
        ...
        ...
        ) {
        console.log('red');
    }
}

위와 같은 코드에서 적은수의 조건이라면 가독성이 괜찮은 것처럼 보입니다. 하지만, 조건이 늘어나면 늘어날수록 매우 지저분해집니다.

// better
function test(fruit) {
    const redFruits = ['apple', 'watermelon', 'strawberry']
    // 조건들을 배열에 넣음
    if(redFruits.includes(fruit)) {
    	// 배열의 includes 메소드를 활용
        console.log('red')
    }
}

조건문이 많아진다면 해당 조건들을 배열에 넣은 다음, includes() 메소드를 활용하여 가독성을 높일 수 있습니다. 깔끔하지 않나요?

 

2. 조건문 중첩을 피하고 빨리 리턴하자 (Early Return)

유효하지 않은 조건이라면 빠르게 return 하는 것이 좋습니다. 예시코드를 보겠습니다.

// bad
function test(fruit, quantity) {
  const redFruits = ['apple', 'strawberry', 'cherry', 'cranberries'];

  // 조건 1: fruit의 값이 존재할 경우
  if (fruit) {
    // 조건 2: 빨강일 경우
    if (redFruits.includes(fruit)) {
      console.log('red');

      // 조건 3: 갯수가 만을 경우
      if (quantity > 10) {
        console.log('big quantity');
      }
    }
  } else {
    throw new Error('No fruit!');
  }
}

if문과 else의 중첩이 늘어남에 따라서 코드는 지저분해졌습니다. 유효하지 않은 조건을 Early Return 시켜보겠습니다.

// better
function test(fruit, quantity) {
  const redFruits = ['apple', 'strawberry', 'cherry', 'cranberries'];

  // 조건 1: fruit가 없을경우 Early Return
  if (!fruit) throw new Error('No fruit!');

  // 조건 2: 빨강일 경우
  if (!redFruits.inclues(fruit)) return;
  
  console.log('red');

  // 조건 3: 갯수가 많을 경우
  if (quantity > 10) {
    console.log('big quantity');
  }
}

위와 같이 변경하면서 불필요한 if, else문을 제거할 수 있게 되었습니다. 상황에 따라서 더 좋은 코드는 바뀔 수 있지만, 굳이 if 중첩문 안으로 조건을 끌고갈 이유는 없습니다.

 

3. 함수의 파라미터, 비구조화를 이용하자

// bad
function test(fruit, quantity) {
    if(!fruit) return;
    const q = quantity || 1; 

    console.log(`We have ${q} ${fruit}!`);
}

위의 코드는 파라미터로 과일의 갯수가 들어오면 갯수로, 그렇지 않다면 기본값을 1로 할당해주고 있습니다.

// better
function test(fruit, quantity = 1) {
    if(!fruit) return;

    console.log(`We have ${q} ${fruit}!`);
}

파라미터로 기본값을 할당해주는 방법을 이용하면 코드를 1줄 줄일 수 있게 됩니다.

 

// bad
function test(fruit) { 
    if(fruit && fruit.name)  {
        console.log (fruit.name);
    } else {
        console.log('unknown');
    }
}

위의 코드는 파라미터가 존재하고, fruit 객체에 name 속성이 존재하는지 확인합니다.

// better
function test({name} = {}) {
    console.log (name || 'unknown');
}

이처럼 간단하게 코드를 만들 수 있습니다. {name}은 비구조화로 객체의 name 속성값을 가져옵니다. 또한, 기본값으로 {} 할당을 해주어 객체가 없을 경우에 대한 에러도 방지하였습니다.

 

4. switch를 멀리하고 map, 객체 리터럴을 사용하자

// bad
function test(color) {
    switch (color) {
        case 'red':
            return ['apple', 'strawberry'];
        case 'yellow':
            return ['banana', 'pineapple'];
        case 'purple':
            return ['grape', 'plum'];
        default:
            return [];
    }
}

일반적인 switch case로 코드를 짠 경우입니다. map, 객체리터럴을 통해 간략하게 표현 가능합니다.

// better1, map
const fruitColor = new Map()
  .set('red', ['apple', 'strawberry'])
  .set('yellow', ['banana', 'pineapple'])
  .set('purple', ['grape', 'plum']);

function test(color) {
  return fruitColor.get(color) || [];
}

map을 사용한 방법입니다. map에서 color를 가져오고 값이 없을 시에는 빈 배열을 return 합니다.

// better2, 객체 리터럴
const fruitColor = {
  red: ['apple', 'strawberry'],
  yellow: ['banana', 'pineapple'],
  purple: ['grape', 'plum']
};

function test(color) {
  return fruitColor[color] || [];
}

이번에는 객체 리터럴을 사용한 방법입니다. map과 거의 동일한 방법입니다. 코드는 두 방법 모두 간결합니다.

map vs object의 경우에는 사용하는 경우가 약간 다르니 이것에 대해서는 추후의 포스팅에서 다뤄보겠습니다.

 

5. Array.some, Array.every 활용하기

// bad
const fruits = [
    { name: 'apple', color: 'red' },
    { name: 'banana', color: 'yellow' },
    { name: 'grape', color: 'purple' }
  ];

function test() {
  let isAllRed = true;

  // 조건: 모든 과일이 빨강이어야 한다
  for (let f of fruits) {
    if (!isAllRed) break;
    isAllRed = (f.color === 'red');
  }

  console.log(isAllRed); // false
}

배열을 순회하면서 모든 과일의 색이 red면 True, 아니면 False를 반환하는 함수입니다. 이렇게 작성하면 가독성뿐만 아니라 코드를 이해하기도 어려워집니다. Array.some, Array.every를 사용해서 바꿔보겠습니다.

// better1
function test() {
    const isAllRed = fruits.every(v=> v.color==='red');
    console.log(isAllRed);
}

every() 메서드를 사용하여 모든 과일의 색이 red 경우에만 True를 반환하도록 변경하였습니다. 상당히 간결해지는 것을 볼 수 있습니다.

// better2
function test() {
    const isAnyRed = fruits.some(f => f.color === 'red');
    console.log(isAnyRed);
}

some() 메서드를 사용하면 하나의 과일만이라도 red일 경우에 True, 아니면 False를 반환하도록 작성할 수도 있습니다.

 

6. 삼항연산자를 사용하자

// bad
let action = 'work';
if(isNight) {
    action = 'sleep'
}

위와 같은 간단한 조건문은 삼항연산자를 사용하여 1줄로 바꿀 수 있습니다.

// better
const action = isNight ? 'sleep' : 'work'

삼항연산자를 이용하면 좀 더 간단하게 코드를 작성할 수 있습니다. 또한 let 대신 const를 사용하므로 변수의 재할당에 대한 에러를 방지할 수 있습니다. 하지만, 보다 복잡한 조건문의 경우라면 if, else를 활용하는 편이 좋을 수도 있습니다.

 

7. &&, || (short circuit) 이용하기

// better
const action = isNight && 'sleep' || 'work'

위에서 작성한 코드를 short circuit를 활용하여 변경할 수 있습니다. 다만 이 역시도 너무 많이 사용할 경우 가독성을 해칠 수 있으므로 간단한 조건에만 사용하는 편이 좋습니다.

 

지금까지, 자바스크립트로 개발을 진행하면서 자연스레 생기는 if, else문을 최소하하는 방법을 알아보았습니다. 이러한 방법들이 정답은 아니지만 구성원들끼리 규칙(컨벤션)을 통일해서 사용한다면 조금 더 가독성 높은 코드를 만들 수 있으리라 생각됩니다.

 

읽어주셔서 감사합니다.

 

728x90