그럼에도 불구하고

👨‍💻

[CleanCode] 분기다루기 본문

JavaScript/Clean code

[CleanCode] 분기다루기

zenghyun 2023. 5. 30. 17:03

오늘은 분기 다루는 법에 대해 알아보겠습니다.

 

목차

     

     

    [ 값식문 ] 

    1. () : 함수와 관련되어 있습니다. 주로 함수를 호출할 때 사용합니다.

     

    2. {}: 중괄호 내부에는 값과 식만 넣어야 합니다. 

     

    3. 값과 식을 이용하면 if문을 대체할 수 있습니다.

     

    4. 함수의 인자 안에는 값과 식만 넣을 수 있습니다.

     

    다음 예시를 보겠습니다. 

     

    📌  예시 1

     

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    // 변경 전
     
    function ReactComponent() {
        return (
            <tbody>
                {(() => {
                    const rows = [];
     
                    for (let i = 0; i < objectRows.length; i++) {
                        rows.push(<ObjectRow key={i} data={objectRows[i]} />);
                    }
                    return rows;
                })()}
            </tbody>
        );
    }
     
    // 변경 후 
     
    function ReactComponent() {
        return (
            <tbody>
                  { objectRows.map((object, i) => (
        <ObjectRow key={i} data={object} />
        ))}  
            </tbody>
        );
    }
    cs

     

    변경 전의 코드를 보면 중괄호 안에서 for문을 이용해 rows라는 객체에 값을 초기화시키고 있습니다.

     

    이는 변경 후의 코드와 같이 map 함수를 이용하면 리팩토링 할 수 있습니다. 

     

     

    📌  예시 2

     

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    // 변경 전 
     
    function ReactComponent() {
        return (
            <div>
                {(() => {
                    if (conditionOne) return <span>One</span>;
                    if (conditionTwo) return <span>Two</span>;
                    else conditionOne;
                    return <span>Three</span>;
                })()}
            </div>
        );
    }
     
    // 변경 후 
     
    function ReactComponent() {
        return (
            <div>
                    { conditionOne && <span>One</span>}
                    { conditionTwo && <span>Two</span>}
                    { !conditionTwo && <span>Three</span>}
            </div>
        );
    }
    cs

     

    변경 전의 코드에서는 조건문을 이용하여 conditionOne, conditionTwo의 여부에 따라 값을 출력하고 있습니다. 

     

    이렇게 조건문을 이용하는 방식은 아래의 방식을 이용하면 보다 깔끔하게 작성할 수 있습니다. 

     

    [ 삼항 연산자 다루기 ]

    삼항 연산자를 다루면 코드를 보다 짧게 작성할 수 있습니다.

    하지만, 그렇다고 모든 코드에서 삼항 연산자를 사용하는 것이 좋은 것(?)은 아닙니다.

     

    📌  예시 1

     

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    // 1
    function example() {
      return condition1 ? value1
        : condition2 ? value2
        : condition3 ? value3
        : value4;
    }
     
    // 2
    function example() {
      if (condition1) {
        return value1;
      } else if (condition2) {
        return value2;
      } else if (condition3) {
        return value3;
      } else {
        return value4;
      }
    }
     
    // 3 switch 문은 어때 ?
     
    const key = {
      // condition을 받아올 수 있는 어떤 코드
    }; // 조건을 정리
     
    // 아래와 같이 switch문을 고려
     
    switch (key) {
      case condition1:
        value1;
        break;
      case condition2:
        value2;
        break;
      case condition3:
        value3;
        break;
      default:
        value4;
    }
     
    cs

     

    위의 코드와 같이 condition 값에 따라 반환하는 값이 다른 코드가 있습니다.

     

    1번의 경우 어떤가요? 삼항연산자를 사용하여 condition 값이 무엇인지에 따라 다른 결과를 출력합니다. 

     

    1번의 코드는 2번의 코드와 같은 결괏값을 출력합니다.

     

    1번과 2번 모두 가독성이 떨어지지 않나요? 혹은 명시적으로, 깔끔하게 이해가 되나요? 

     

    1번과 2번의 코드는 3번과 같이 switch문을 사용할 수도 있습니다. 이렇게 삼항 연산자를 써서 코드가 짧아진다고 무조건 좋은 코드는 아니라고 생각합니다. 

     

    📌  예시 2

    1
    2
    3
    4
    5
    6
    7
    8
    // 잘못된 예시
    // 삼항 연산자는 값 또는 식을 반환해야 한다.
    function getMessage(isAdult) {
      isAdult
        ? alert("입장이 가능합니다."// undefined
        : alert("입장이 불가능합니다."); // undefined
     
    }
    cs

     

    위의 예시는 삼항 연산자를 적절하게 사용하지 못한 코드입니다.

     

    왜냐하면 삼항 연산자는 값 또는 식을 반환해야 하기 때문입니다. isAdult의 true, false 여부에 따라 alert로 메시지를 출력하게 되는데 이는 값이나 식이 아니기 때문에  undefined를 출력하는 것과 마찬가지입니다. 

     

    위의 코드를 아래와 같은 방법으로 사용하는 것은 어떨까요?

     

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    // 1.
     
    function getMessage(isAdult) {
      return isAdult ? "입장이 가능합니다." : "입장이 불가능합니다.";
    }
     
    // 2.
     
    function getMessage(isAdult) {
      const adult = isAdult ? "입장이 가능합니다." : "입장이 불가능합니다.";
     
      if (isAdult) {
        // some logic code
      }
     
      if (isAdult && gender === "MAN") {
        // some logic code
      }
    }
     
    cs

    [ Truthy & Falsy ]

    조건문을 사용할 때 값이 참이면 조건을 비교적 간단하게 작성할 수 있습니다.

     

    ※ 단, 철저하게 검사가 필요할 경우는 사용에 유의! 

     

    📌  예시 1

     

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    // Truthy 
     
    if ('string'.length > 0) {
     
    }
     
    if (!isNaN(10)) {
     
    }
     
    if (boolean === true) {
     
    }
     
    // 조건 안의 값은 참이면 아래와 같이 쓸 수 있다. 
     
    if ('string'.length) {
     
    }
     
    if (10) {
     
    }
     
    if (boolean) {
     
    }
    cs

     

     

    📌  예시 2

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    function printName(name) {
        ifname === undefined || name === null) {
            return '사람이 없네요';
        }
     
        return '안녕하세요 ' + name + '님';
    }
     
    // name 값이 존재하면 truthy이고 값이 존재하지 않으면 falsy이다. 
     
    function printName(name) {
        if(!name) {
            return '사람이 없네요';
        }
     
        return '안녕하세요 ' + name + '님';
    }
     
    // 이렇게 바꿀 수 있다. 
    cs

     

    [ 단축 평가 ]

    단축 평가는 AND와 OR의 개념을 이용합니다.

     

    📌  예시 1

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    /**
     * AND
     */
     
    console.log( true && true && '도달 O' );
     
    console.log( true && false && '도달 X' );
     
    /**
     * OR
     */
     
    console.log( false || false || '도달 O'); 
     
    console.log(true || true || '도달 X'); 
     
     
     
    cs

     

     

     

    AND : &&를 사용하며, 모든 항이 전부 true일 때 true 값을 반환합니다. 

     

    OR: ||를 사용하며, 존재하는 항 중에서 하나라도 true일 경우 true 값을 반환합니다. 

     

    1번의 경우 3개의 항 중에 앞의 2개의 항이 true이므로 결괏값의 true & false의 여부는 마지막 '도달 O'에 달려있습니다. '도달 O'가 true이기 때문에 출력 값은 '도달 O'입니다.

     

    2번의 경우 첫 번째 항은 true, 두 번째 항은 false이므로 세 번째 항의 값에 관계없이 false를 출력합니다. 

     

    3번의 경우 첫 번째 항과 두 번째 항이 모두 false이므로 결괏값의 true & false의 여부는 마지막 '도달 O'에 달려있습니다.  '도달 O'가 true이기 때문에 출력 값은 '도달 O'입니다.

     

    4번의 경우 첫 번째 항이 true이므로 나머지 항들의 값에 관계없이 결괏값은 true입니다. 

     

    📌  예시 2

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    // 변경 전 
     
    function fetchData() {
         if (state.data) {
            return state.data;
         } else {
            return 'Fetching...';
         }
    }
     
    // 위의 코드는 아래와 같이 바꿀 수 있다. (OR 연산자의 단축평가)
     
    // 변경 후 
     
    function fetchData() {
        return state.data || 'Fetching...'
    }
     
    cs

     

    📌  예시 3

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    function favoriteDog(someDog) {
        let favoriteDog; 
        if (someDog) {
            favoriteDog = someDog; 
        } else {
            favoriteDog = '냐옹';
        } 
        return favoriteDog + '입니다.';
    }
     
    favoriteDog() // => 냐옹 
    favoriteDog('포메'// => 포메 입니다. 
     
    // 위의 함수를 아래와 같이 바꿀 수 있다. 
     
    function favoriteDog() {
        return (someDog || '냐옹'+ '입니다.';
    }
     
    // someDog에 값이 들어가지 않으면 undefined가 되는데 undefined는 falsy로 평가된다. 
    cs

     

    📌  예시 4

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    const getActiveUserName = (user, isLogin) => {
        if(isLogin) {
            if(user) {
                if(user.name){
                    return user.name;
                } else {
                    return '이름없음'
                }
            }
        }
    }
     
    // 위의 코드는 아래와 같이 바꿀 수 있다. 
     
    const re_getActiveUserName = (user, isLogin) => {
        if(isLogin && user) {
            if(user.name) {
                return user.name;
            } else {
                return '이름없음';
            }
        }
    }
     
    // 위의 코드는 한번 더 줄일 수 있다. 
     
    const rere_getActiveUserName = (user, isLogin) => {
        if(isLogin && user) {
            return user.name || '이름없음'
        }
    }
    cs

     

    예시 2,3,4와 같이 단축 평가를 이용하면 보다 간단하게 코드를 작성할 수 있습니다.

     

    [ else if, else 피하기 ]

     

    📌  예시 1 else if 피하기 

     

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    const x = 5;
     
    if (x >= 5) {
      console.log("x는 5와 같거나 크다");
    else if (x >= 4) {
      console.log("x는 4와 같거나 크다");
    else if (x >= 3) {
      console.log("x는 3와 같거나 크다");
    else if (x >= 2) {
      console.log("x는 2와 같거나 크다");
    else if (x >= 1) {
      console.log("x는 1와 같거나 크다");
    }
     
    // 위와 아래는 논리적으로 같은 코드이다.
     
    if (x >= 5) {
      console.log("x는 5와 같거나 크다");
    else {
      if (x >= 4) {
        console.log("x는 4와 같거나 크다");
      }
    }
    // 생략
    cs

     

    위의 코드는 x의 값에 따라 출력되는 결과가 달라지는 코드의 예시입니다. 

     

    값의 경우에 따라 나눠놓은 코드는 이렇게 if, else if를 통해 구분할 수 있지만, else if는 모호하기 때문에 피하는 것이 좋습니다.

     

    else if를 통해 조건에 따라 나누다 보면 실수하기도 쉽습니다. 또한, else if를 계속 쓴다면 switch문을 쓰는 것과 같은 논리이기 때문에 조

     

    건에 따라 값을 나눈다면 swith문을 쓰는 게 보다 효과적입니다.

     

    📌  예시 2 else  피하기 

     

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    function getActiveUserName(user) {
        if (user.name) {
            return user.name;
        } else {
            return '이름없음';
        }
    }
     
    // 변경 
     
    function getActiveUserName(user) {
        if (user.name) {
            return user.name;
        } 
            return '이름없음';
    }
     
    // 변경 
     
    function getActiveUserName(user) {
        return user.name || '이름없음';
    }
     
     
    cs

     

    📌  예시 3 else  피하기 

     

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    /**
     * age가 20 미만시 특수 함수 실행
     */
     
    // 변경 전
     
    function getHelloCustomer(user) {
      if (user.age < 20) {
        report(user);
      } else {
        return "안녕하세요";
      }
    }
     
    // 변경 후
     
    function getHelloCustomer(user) {
      if (user.age < 20) {
        report(user);
      }
      return "안녕하세요";
    }
     
    cs

     

    위의 코드처럼 코드를 작성하다 보면 무의미한 else를 사용하고는 합니다. 

     

    if에 해당하지 않는다면 실행되는 else는 if의 반대와 같습니다.

     

    else를 쓰지 않아도 될 상황에서 굳이 else를 쓰고 계시지는 않은가요?

     

    [ Early return ]

    Early return : 빠르게 반환한다. 

     

    📌  예시 1

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    function loginService(isLogin, user) {
      if (!isLogin) {
        // 로그인 여부 확인
        if (checkToken()) {
          // 토큰 존재 여부 확인
          if (!user.nickName) {
            // 가입 유저 확인
            return registerUser(user);
          } else {
            refreshToken();
            return "로그인 성공";
          }
        } else {
          throw new Error("No Token");
        }
      }
    }
    cs

     

    위의 코드를 살펴보겠습니다. 

     

    loginService 함수는 isLogin과 user를 인자로 받은 후 처음으로 로그인 여부를 확인합니다. 

     

    로그인이 되어있지 않다면, checkToken() 함수를 통해 토큰의 존재 여부를 확인합니다. 

     

    그 후에 가입 유저인지 확인 후 가입된 유저가 아니라면 registerUser() 함수를 통해 유저를 가입시킵니다. 

     

    만약 가입 유저라면 refreshToken() 함수를 호출하고 "로그인 성공"이라는 문구를 반환합니다. 

     

    checkToken() 함수에서 토큰이 존재하지 않는다면 에러를 발생시킵니다.

     

    로직을 만들다면 위와 같이 작성하는 경우가 종종 있는데 Early return을 이용하면 보다 가독성 좋게 변경할 수 있습니다.

     

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    // 변경 후
    function login() {
      refreshToken();
     
      return "로그인 성공";
    }
     
    function loginService(isLogin, user) {
      // Early Return
      /**
       * 함수를 미리 종료
       * 사고하기 편하다.
       */
      if (isLogin) {
        return;
      }
     
      if (!checkToken) {
        throw new Error("No Token");
      }
     
      if (!user.nickName) {
        return registerUser(user);
      }
     
      login();
    }
     
    cs

     

    isLogin을 통해 truthy라면 함수를 종료합니다. 

     

    ! checkToken()을 통해 토큰이 존재하지 않는다면 에러를 발생시킵니다. 

     

    ! user.nickName을 통해 가입된 유저가 아니라면 registerUser() 함수를 통해 유저를 가입시킵니다. 

     

    마지막으로 로그인합니다.

     

    어떤가요? 위의 코드와 아래의 코드는 로직이 변한 것은 없습니다. 

     

    아래의 코드가 가독성이 훨씬 좋지 않나요?

     

    그렇다고 Early return이 무조건 좋은 것은 아닙니다. 그렇기 때문에 상황에 따라 달리 사용해야 합니다. 

     

    즉, 수많은 Early return을 작성하는 것은 옳지 않지만, 하나의 의존성에 많은 로직을 묶고 있을 때는 Early return을 사용하면 코드가 명시적으로 바뀔 수 있습니다.

     

     

    [ 부정 조건문 지양하기 ]

     

    📌  예시 1

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    // 예시 1 
     
    const isCondition = true;
     
    if(!isCondition) {
        console.log('거짓인 경우에만 실행');
    }
     
    // 예시 2
     
    const isNotCondition = false
     
    if(isNotCondition) {
        console.log('거짓인 경우에만 실행');
    }
     
    cs

     

    예시 1의 경우 isCondition이 false일 경우 결과 값을 출력합니다.

     

    예시 2의 경우 isNotCondition이 true일 경우 결과 값을 출력합니다. 

     

    예시 1은 isCondition에 ! 붙여 결과 값을 한번 뒤집어서 생각해야 하지만 예시 2는 보다 직관적으로 isNotCondition이 true 이면 결괏값을 출력합니다.

     

    📌  예시 2

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    if(!isNaN(3)) {
        console.log('숫자입니다.');
    }
     
    // 위 보다는 아래가 명시적으로 좋다. 
     
    function isNumber(num) {
        return !Number.isNaN(num) && typeof num === 'number';
    }
     
    if(isNumber(3)){
        console.log('숫자입니다.'); 
    cs

     

    예시를 보면 부정 조건문을 사용할 경우 생각을 여러 번 해야 하는 경우가 생깁니다. 지금은 비교적 간단한 코드이지만 조금만 복잡해지다 보면 헷갈리기 쉽습니다.

     

    그렇다고 부정 조건문이 무조건 나쁜 건 아닙니다.

     

    ※  부정 조건문을 사용하기 좋은 경우

     

    1. Early return

     

    2. Form Validation

     

    3. 보안 혹은 검사하는 로직

     

     

    [ Default case 고려하기 ]

     

    사용자의 잘못을 유도할 수 있는 환경이라면 당연히 Default value를 고려해야 합니다.

     

    📌  예시 1

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
     
    // 기본 값을 미리 정의해두자 
    function sum(x, y) {
      x = x || 1;
      y = y || 1;
     
      return x + y;
    }
     
    sum(100200);
     
    cs

    sum 함수에는 어떤 값이 인자로 들어올지 모르기 때문에 x와 y에 기본 값을 명시해 주는 것이 좋습니다. 

     

     

    📌  예시 2

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
     
    function createElement(type, height, width) {
        const element = document.createElement(type || 'div');
     
        element.style.height = height || 100
        element.style.width = width || 100
     
        return element;
    }
     
    createElement();
    cs

     

    createElement 함수의 인자에는 개발자의 바람대로 값이 들어오지 않을 수 있기 때문에 기본 타입인 div와 width, height에 기본 치수를 정해줄 수 있습니다.

     

     

    📌  예시 3

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    function registerDay(userInputDay) {
        switch(userInputDay) {
            case '월요일'// some code
            case '화요일'// some code
            case '수요일'// some code
            case '목요일'// some code
            case '금요일'// some code
            case '토요일'// some code
            case '일요일'// some code
            defaultthrow new Error('입력 값이 유효하지 않습니다.');
        }
    }
     
    e.target.value = '월ㄹ요일';
     
    registerDay(e.target.value);
    cs

     

    누구나 일주일이 월화수목금토일인 것은 알지만 registerDay의 입력 값에 사용자는 오타를 기입할 수 있습니다. 🤣

     

     

    📌  예시 4

    parseInt에 두 번째 매개변수가 있다는 사실 아시나요? 

     

    첫 번째 매개변수는 내가 정수로 바꾸고 싶은 값이 들어가고, 두 번째 매개변수는 그 값을 어떻게 바꿀지에 대해 정의해주어야 합니다. 

     

    기본적으로 10진수로 바뀌는 것이 기본값인 줄 많이 알지만 절대 그렇지 않습니다. 

     

    그렇기 때문에 두 번째 매개변수에 내가 어떤 식으로 반환할 건지 명시해주어야 합니다. 

     

    1
    2
    3
    4
    5
    6
    7
    8
    // parseInt의 두번째 인자가 있음을 유의하자!!! 기본값이 절대 10진수로 변환하는 것이 아니다. 
     
     
    // 누군가 만들어 놓은 코드도 이렇게 맵핑할 수 있다. 
    // 항상 사용자의 실수를 고려하기 위해 default case를 고려해야 한다. 
    function safeParseInt(number, radix) {
        return parseInt(number, radix || 10);
    }
    cs

     

     

    [ Nullish Coalescing Operator (널 병합 연산자) ]

    https://despiteallthat.tistory.com/54

     

    [JavaScript] 단축 평가

    오늘은 단축 평가에 대해 알아보자 [ 논리 연산자를 사용한 단축 평가 ] 논리 합 (| |) 또는 논리 곱 (&&) 연산자 표현식은 언제나 2개의 피연산자 중 어느 한쪽으로 평가된다. 다음 예제를 살펴보자

    despiteallthat.tistory.com

     

    [ 드모르간의 법칙 ]

     

    드모르간의 법칙을 이용하면 보다 쉽게 코드를 작성할 수 있습니다.

     

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    /**
     * 드모르간의 법칙
     * true is not true
     * false is not false 
     */
     
    const isValidUser = false
    const isValidToken = false;
     
    if (! (A && B)) {
        // 성공
    }
     
    // 위와 아래는 같은 코드이다. 
     
    if (!|| !B) {
        // 성공
    }
     
     
    if(!(isValidToken && isValidUser)) {
        console.log('로그인 성공');
    }
     
    // 위와 아래는 같은 코드이다. 
     
    if!isValidToken || !isValidUser) {
        console.log('로그인 성공');
    }
     
     
     
    cs

    'JavaScript > Clean code' 카테고리의 다른 글

    [CleanCode] 객체 다루기  (0) 2023.06.02
    [CleanCode] 배열 다루기  (0) 2023.06.01
    [CleanCode] 경계 다루기  (0) 2023.05.25
    [CleanCode] isNaN? is Not A Number?  (0) 2023.05.24
    [CleanCode] undefined와 null의 차이  (0) 2023.05.24
    Comments