[Clean Code] 함수의 길이 줄이기(2): If/else ➡️ Bitwise shift/Array & Object mapping 으로 대체해보자

Quartz 쿼츠·2022년 11월 21일
2

Better Code & Design

목록 보기
2/4
post-thumbnail

#0 Introduction

지난 글에 이어서 분기 처리 코드를 줄이는 방법 중 boolean 조건을 가질 때 처리 방법에 대해 알아보자. 예시 코드는 우테코 프리코스 4 주차 미션에서 가져왔다.

불리언도 문자열과 마찬가지로 object mapping을 사용하여 분기 처리를 할 수 있다. 다만, 자바스크립트에서는 key를 문자열로만 저장하므로 아래와 같이 dynamic key를 사용하여야 boolean key를 적용할 수 있다.

const MAP = {
  [true]: // 해당 코드,
  [false]: // 해당 코드,
};

하지만 비교해야 할 boolean 값들이 여러 개라면 어떻게 처리해야 할까? 예를 들어 2 개의 불리언 상태를 사용한다면 우리는 4 개의 경우의 수를 생각할 수 있다. 그러나 boolean 조건을 연산한 결과는 결국 true false 두 개의 결과만을 가지므로 우리는 4 개가 아닌 2 개의 dynamic key만을 갖게 되는 문제가 발생한다.

여러 boolean 값을 하나의 string으로 처리하여 이전 글과 동일하게 object mapping을 사용해보자!

#1 Boolean 조건문의 분기 처리

#1.1 If/else 문을 사용하기

우리는 일반적으로 두 개의 boolean 상태 isFinish isMove 를 연산하여 아래와 같은 분기 처리를 한다. Boolean 상태가 많아질수록 코드가 점점 더 길어질 것이며, 조건문에 각 상태의 true false 여부가 뒤섞여 가독성도 나빠질 것이다.

checkContinueMove() {
  const { isFinish, isMove } = this.getBooleans();
  if (!isFinish && isMove) {
    this.getPlayerMove();
  } else if (!isMove) {
    this.getGameCommand();
  } else if (isFinish && isMove) {
    this.quit();
  }
}

#1.2 비트 연산과 배열 사용하기

이 문제를 해결하기 위해서 다음의 방법을 사용할 수 있다.

  1. 여러 boolean 상태 ➡️ 하나의 숫자로 나타내기
  2. 해당 숫자를 array의 index로 사용하여 mapping

참과 거짓으로 나타나는 boolean 값은 컴퓨터에서는 1과 0의 숫자로 인식된다. 아래 그림의 좌측과 같이 N 개의 boolean 상태들을 나열하면 1과 0의 집합이 되고, 이들을 이진수의 각 N -1 자리수에 대입하면 하나의 이진수로 대체할 수 있다.

#1.2.1 비트 연산

앞서 다룬 isFinish isMove 2 개의 boolean 상태를 다시 예시로 들어보자. 각 boolean 상태는 0 과 1 두 개의 값을 가지며 경우의 수는 4 가지이다.

Left shift(<<): N 개의 boolean 저장하기

각 boolean 값을 하나의 이진수에 저장하기 위해서는 bitwise left shift(<<) 연산자를 사용한다. n 번 shift(<<)하면 이진수의 n 자리에 저장된다.

const binary1 = true; // 이진수: 1, 십진수:1
const binary2 = true << 1; // 이진수: 10, 십진수:2
const binary3 = true << 2; // 이진수: 100, 십진수:4
const binary4 = true << 2 | true ; // 이진수: 101, 십진수:5

OR(|): N 개의 boolean ➡️ 1 개의 숫자 변환하기

이를 활용하여 우리는 두 개의 boolean 값을 아래와 같이 하나의 playerState로 나타낼 수 있다. 이 값은 4 가지 경우의 수를 가지며 십진수로 0, 1, 2, 3로 나타난다.

const playerState = (isFinish << 1) | isMove;

#1.2.2 Boolean 조건문 분기 처리하기

이를 활용하여 우리는 #1.1의 메인로직을 아래와 같이 간결하게 나타낼 수 있다.

// Main logic
checkContinueMove() {
  const { isFinish, isMove } = this.getBooleans();
  const playerState = createPlayerState(isFinish, isMove);
  PLAYER_STATE_FN[playerState](this);
}

비트 연산을 활용하여 두 개의 boolean 값을 하나의 숫자로 나타내고, PLAYER_STATE_ARRAY에서 해당 PLAYER_STATE를 인덱스로 하는 값을 찾는다. 그리고 이전 글과 같이 object mapping으로 함수를 key로 가져와 활용하면 분기 처리 로직을 단순화할 수 있다.

// Bitwise shift
const createPlayerState = (isFinish, isMove) => {
  const state = (isFinish << 1) | isMove;
  return PLAYER_STATE_ARRAY[state];
};

// Array mapping
const PLAYER_STATE_ARRAY = [
  "retry",
  "continue",
  "retry",
  "success",
];

// Object mapping
const PLAYER_STATE_FN = {
  retry(gamePresenter) {
    gamePresenter.getGameCommand();
  },
  continue(gamePresenter) {
    gamePresenter.getPlayerMove();
  },
  success(gamePresenter) {
    gamePresenter.quit();
  },
};

예를들어 isFinish=true isMove=false 인 경우,
playerState는 2로 반환되고 배열의 세 번째 값인 retry가 맵핑되어
gamePresenter.getGameCommand() 함수가 실행된다.

#1.2.1+ 비트 연산

AND(&): 1 개의 boolean 상태 가져오기

추가로 and 연산자를 활용하면 하나로 합친 이진수 값에서 해당 자리 수의 boolean 값을 가져올 수도 있다.

const isFinish = playerState & ( 1 << 1 );

마치며

비트 연산의 개념만 알고 직접 적용해보지는 못했었는데 분기 처리에 활용해보니 왜 기초 CS 지식이 중요한지 알게되었다. 여전히 코드가 얽혀있고 복잡하지만 한 번에 독파한다는 생각보다는 이렇게 하나씩 개선해나가는 것도 좋은 방법이라 생각한다.

profile
Code what we love. 좋아하는 것들을 구현하고 있는 프론트엔드 개발자입니다. 사용자도 함께 만족하는 서비스를 만들고 싶습니다.

2개의 댓글

comment-user-thumbnail
2022년 11월 29일

#1.2 1+ 를 보면서 생긴 궁금증입니다. const a = 1 이고 const b = 3 일때 a & b = 1 의 값을 얻을 수 있는데 위 설명대로라면 알고싶은 boolean 값자리가 1 일때 0 이 나와야하는 것 아닌가요?

1개의 답글