4 min read
if/else, switch 대신 action object 사용해보기

우리는 길들여지기를 다중 조건문 속에서 살고 있다.

const changeSomething (state) => {
  
	if(state === "hungry") {
      eatSomething();
    } else if (state === "full") {
      relax(); 
    } else {
      readBooks(); // watchYoutube();
    }
  
}

if/else에서 다뤄야 하는 조건이 많지 않다면 사용하지 않을 이유가 없지만, 만약 조건이 많아져 로직이 복잡해진다면?

code snippet image

물론 이 경우는 장난이지만, 실제로 구별해야 하는 상태가 수십가지라면, 코드는 하염없이 if/else로 절여질 것이다.

switch

뭐? 하지만 if/else를 리팩토링할 수 있는 switch 문법이 있다고??

code snippet image

const changeSomething = (state) => {
    switch(state) {
      case "hungry" :
        eatSomething();
        break;
      case "full" :
        relax();
        break;
      default : 
        readBooks();
    }
}

보기에는 간결해보이나, break을 명시하여서 switch문이 이탈하게끔 해야 한다. 즉, if/else 처럼 위에서 아래로 불필요한 연산이 증가하게 된다. 만약 state가 sleepy라면 , 위에서 hungry와 full 까지 연산을 마친 후에야 readBooks()를 실행하게 된다. (waterfall 모델처럼 switch 문에서 모든 조건을 실행하는 것을 fall-through라고 한다.)

그러므로 조건을 다루는데 있어 if/else문과 switch는 성능이 동일하며, 조건이 들어남에 따라 코드를 더럽고 보기 힘들게 만드는 요인이 된다.

action object

그렇다면 조건문 연산에 있어서 효율도 챙기면서 가독성도 높일 수 있는 방법이 없을까? 바로 action object를 이용해보자 (aciton object는 내가 맘대로 지어보았다)

const changeSomething =(state = "default") => {
  	actions[state]();
}

const actions = {
  	hungry : () => eatSomething(),
  	full : () => relax(),
  	default : () => readBooks(),
}

우리는 객체를 데이터를 담는 공간으로 주로 사용하는데, 일반적인 원시 타입의 데이터가 아니라 참조 타입인 함수까지 담을 수 있다! 즉 행동을 담고 있는 객체라고 하여 action object로 명명하였다.

action object 장점

OOP 달성

if/else나 switch같은 문법은 조건 연산 후 실행할 행동이 외부로 노출되어 있는 상태이다. 하지만 action object를 사용하여 action을 하나의 object로 캡슐화하여 은닉하고 추상화한다. 이를 통해 구조화는 물론 간결한 코드 작성이 가능하다.

연산량 감소

객체는 포인터이다. 즉, 조건이 맞니 아니니 계산하는 것이 아니라 객체 안의 속성을 가리키는 것이다. 그러므로 객체에 state라는 변수의 데이터와 같은 속성을 가리키는 행위는 연산을 1번만 수행해도 된다.

action object 예외처리 추가

하지만 위 로직에서는 actions 객체에 없는 속성인 경우에 대한 예외처리가 고려되어 있지 않다. 그러므로 다음과 같이 리팩토링할 수 있다.

const changeSomething = (state = "default") => {
    try {
  	  actions[state]();
    } catch (err) {
      // do something(); 
    }
}

React에서 action object 사용하기

적용 전 코드

const [userData, setUserData] = useState({
  id : undefined,
  pw : undefined,
  file : undefined
});

const changeState = (e) => {
  const id = e.target.name;
  const value = e.target?.value;
    
  if (id === "id" || id ==="pw") {
    setUserData({...userData, [id] : value});
  } else if (id === "reset") {
    setUserData({})
  } else if (id === "file") {
    // file upload logic ...
  } else {
    // do something ...
  }
};

userData 하나를 객체로 저장하게 될 때 각자 다른 컴포넌트로부터 영향을 받으므로 조건문을 추가해야 하는데, 조건문이 많아진다면 코드가 위 아래로 길어지고 한눈에 파악하기 힘들어진다.

적용 후 코드

const [userData, setUserData] = useState({
  	id : undefined,
    pw : undefined,
    file : undefined
});

const actions = {
	profile : (id,value) => setUserData({...userData, [id] : value}),
    reset : () => setUserData({}),
    file : () => { // do Something ...
    },
}

const changeState = (e) => {
  const id = e.target.name;
  const value = e.target?.value;
   	 
  try {
    actions[id](id,value);
  } catch (err) {
    // do something ...
  }  
};

action object를 통해 actions를 캡슐화하여 간결하면서도 어떤 행동을 할 것인지를 추상화하여 더욱 가독성있게 작성이 가능하다. 상수시간이 걸리는 것은 보너스!