Post

if else 리팩토링

if else 조건문 리팩토링 하기 - 소셜 로그인

[Javascript 미세팁] if else 리팩토링 #1 (feat. 객체 + 함수)을 보고 정리한 내용입니다.

조건문 코드 개선해보기

네이버, 페이스북, 구글, 카카오 등 소셜 로그인 로직을 작성해보자.

■ if 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
25
26
27
28
29
30
31
32
33
34
35
const naverLogin = (id) => {
  // 로직
  return "네이버";
};
const kakaoLogin = (id) => {
  // 로직
  return "카카오";
};
const facebookLogin = (id) => {
  // 로직
  return "페이스북";
};
const googleLogin = (id) => {
  // 로직
  return "구글";
};

const socialLogin = (where, id) => {
  let domain = "";
  if (where === "naver") {
    domain = naverLogin();
  } else if (where === "kakao") {
    domain = kakaoLogin();
  } else if (where === "facebook") {
    domain = facebookLogin();
  } else if (where === "google") {
    domain = googleLogin();
  }
  return `${domain} ${id}`;
};

console.log(socialLogin("naver", "hyemin"));
// "naver hyemin"
console.log(socialLogin("google", "hyemin"));
// "google hyemin"
  • 가독성이 떨어지고, facebook으로 로그인하려면 where이 naver인지, 카카오인지 확인하고 facebook인지 확인하기때문에 성능에도 좋지 않다.

■ where의 값으로 switch문으로 바꿔 가독성과 성능 높이기

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
const socialLogin = (where, id) => {
  let domain = "";
  switch (where) {
    case "naver":
      domain = naverLogin(id);
      break;
    case "kakao":
      domain = kakaoLogin(id);
      break;
    case "facebook":
      domain = facebookLogin(id);
      break;
    case "google":
      domain = googleLogin(id);
      break;
  }
  return `${domain} ${id}`;
};

console.log(socialLogin("naver", "hyemin"));
// "naver hyemin"
console.log(socialLogin("google", "hyemin"));
// "google hyemin"
  • if-else보다는 가독성이 높아졌지만, break가 생겼다.

■ break를 없애기 위해 switch문을 별도의 함수로 분리하자.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
const socialLoginMap = (where, id) => {
  switch (where) {
    case "naver":
      return naverLogin(id);
    case "kakao":
      return kakaoLogin(id);
    case "facebook":
      return facebookLogin(id);
    case "google":
      return googleLogin(id);
  }
};

const socialLogin = (where, id) => {
  const domain = socialLoginMap(where);
  return `${domain} ${id}`;
};

console.log(socialLogin("naver", "hyemin"));
// "naver hyemin"
console.log(socialLogin("google", "hyemin"));
// "google hyemin"
  • socialLoginMap함수를 보면 규칙이 있는 것을 확인할 수 있고, return만 있기때문에 객체로 변경할 수 있다.

■ switch문이 들어있는 함수를 객체로 바꾸자.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
const socialLoginMap = {
  naver: naverLogin,
  kakao: kakaoLogin,
  facebook: facebookLogin,
  google: googleLogin
};

const socialLogin = (where, id) => {
  const domain = socialLoginMap[where](id);
  return `${domain} ${id}`;
};

console.log(socialLogin("naver", "hyemin"));
// "naver hyemin"
console.log(socialLogin("google", "hyemin"));
// "google hyemin"
  • 객체의 맵핑이 들어가있는 부분은 함수들로만 이루어져있고, socialLogin 함수는 함수 본연의 기능만 하고 있는 것을 확인할 수 있다. (역할 분리)
  • socialLoginMap객체에 데이터를 몰아놓고, 실제 동작하는 부분은 별도의 socialLogin함수로 존재

■ 비교해보기

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
// if-else문
const socialLogin = (where, id) => {
  let domain = "";
  if (where === "naver") {
    domain = naverLogin();
  } else if (where === "kakao") {
    domain = kakaoLogin();
  } else if (where === "facebook") {
    domain = facebookLogin();
  } else if (where === "google") {
    domain = googleLogin();
  }
  return `${domain} ${id}`;

// 객체 사용하여 역할 분리한 로직
const socialLoginMap = {
  naver: naverLogin,
  kakao: kakaoLogin,
  facebook: facebookLogin,
  google: googleLogin
};

const socialLogin = (where, id) => {
  const domain = socialLoginMap[where](id);
  return `${domain} ${id}`;
};
  • if else문보다 가독성이 높아지고, if-else에 비해 성능이 좋아짐

if else 조건문 리팩토링 하기 - 구간별 적용 사례 (계절)

  • 3월 ~ 5월 : 봄
  • 6월 ~ 8월 : 여름
  • 9월 ~ 11월 : 가을
  • 12월 ~ 2월 : 겨울

■ if문으로 작성하기

1
2
3
4
5
6
const getSeason = (month) => {
  if (month >= 3 && month <= 5) return "";
  if (month >= 6 && month <= 8) return "여름";
  if (month >= 9 && month <= 11) return "가을";
  if (month >= 12 || month <= 2) return "겨울";
};

■ switch문으로 바꾸기

  • 정수 형태로만 연속성을 가지기 때문에 값이 딱 12개만 존재
  • 그래서 값별로 케이스를 전부 지정할 수 있다.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
const getSeason = (month) => {
  switch (month) {
    case 3:
    case 4:
    case 5:
      return "";
    case 6:
    case 7:
    case 8:
      return "여름";
    case 9:
    case 10:
    case 11:
      return "가을";
    case 12:
    case 1:
    case 2:
      return "겨울";
  }
};

간단하긴 하지만 정수였기때문에 위와 같은 코드를 작성할 수 있었다.

  • 3개씩 나눠져있는 규칙을 바탕으로 변형을 주면 더 깔끔하게 작성할 수 있다.

■ 조금 더 깔끔하게 작성해보자

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
const getSeason = (month) => {
  const shiftedMonth = month - 3;
  switch (month) {
    case 0:
    case 1:
    case 2:
      return "";
    case 3:
    case 4:
    case 5:
      return "여름";
    case 6:
    case 7:
    case 8:
      return "가을";
    case 9:
    case -1:
    case -2:
      return "겨울";
  }
};
  • 이렇게 작성해놓고 보니, -1, -2가 거슬린다…

■ -1, -2 처리하기

  • -1 을 10으로, -2를 11로 만들려면 month - 3에 12를 더하면 된다.
  • 하지만 전부에 12를 더하면 값이 12부터 시작할테니까 12를 더한 것에 대한 나머지를 구하면 된다.
1
2
3
4
5
6
const month = (month - 3 + 12) % 12;

// (10 - 3 + 12) % 12 = 7
// (12 - 3 + 12) % 12 = 9
// (1 - 3 + 12) % 12 = 10
// (2 - 3 + 12) % 12 = 10
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
const getSeason = (month) => {
  const shiftedMonth = (month + 9) % 12;
  switch (month) {
    case 0:
    case 1:
    case 2:
      return "";
    case 3:
    case 4:
    case 5:
      return "여름";
    case 6:
    case 7:
    case 8:
      return "가을";
    case 9:
    case 10:
    case 11:
      return "겨울";
  }
};
  • 위의 코드를 보면
    • case 0 ~ 2는 3으로 나눴을 때 몫이 0인 값
    • case 3 ~ 6는 3으로 나눴을 때 몫이 1인 값
    • case 7 ~ 8는 3으로 나눴을 때 몫이 2인 값
    • case 9 ~ 11는 3으로 나눴을 때 몫이 3인 값
  • 인것을 확인할 수 있다.

■ shiftedMonth를 3으로 나눠서 보다 짧게 코드를 작성해보자

1
2
3
4
5
6
7
8
9
10
11
12
13
const getSeason = (month) => {
  const shiftedMonth = Math.floor(((month + 9) % 12) / 3);
  switch (month) {
    case 0:
      return "";
    case 1:
      return "여름";
    case 2:
      return "가을";
    case 3:
      return "겨울";
  }
};

■ 정리된 코드를 객체로 추출해보자

1
2
3
4
5
6
7
8
9
10
11
const seasonMap = {
  0: "",
  1: "여름",
  2: "가을",
  3: "겨울"
};

const getSeason = (month) => {
  const shiftedMonth = Math.floor(((month + 9) % 12) / 3);
  return seasonMap[shiftedMonth];
};
  • seasonMap을 보면 0부터 시작하고, 1, 2, 3이 key값인 것을 확인할 수 있다.

■ 배열로 바꾸기

1
2
3
4
5
const seasons = ["", "여름", "가을", "겨울"];
const getSeason = (month) => {
  const shiftedMonth = Math.floor(((month + 9) % 12) / 3);
  return seasons[shiftedMonth];
};

■ 조금 더 정리해보자

1. shiftedMonth가 한번 사용되니 return값에 직접 대입하기

1
2
3
4
const seasons = ["", "여름", "가을", "겨울"];
const getSeason = (month) => {
  return seasons[Math.floor(((month + 9) % 12) / 3)];
};
  • 바로 리턴시킬 수도 있다.
1
2
const seasons = ["", "여름", "가을", "겨울"];
const getSeason = (month) => seasons[Math.floor(((month + 9) % 12) / 3)];

2. shiftedMonth도 분리시키기

1
2
3
const seasons = ["", "여름", "가을", "겨울"];
const getSeasonIndex = (month) => Math.floor(((month + 9) % 12) / 3);
const getSeason = (month) => seasons[getSeasonIndex(month)];

비교해보기

1
2
3
4
5
6
7
8
9
10
11
12
// if문
const getSeason = (month) => {
  if (month >= 3 && month <= 5) return "";
  if (month >= 6 && month <= 8) return "여름";
  if (month >= 9 && month <= 11) return "가을";
  if (month >= 12 || month <= 2) return "겨울";
};

// 코드를 분리한 로직
const seasons = ["", "여름", "가을", "겨울"];
const getSeasonIndex = (month) => Math.floor(((month + 9) % 12) / 3);
const getSeason = (month) => seasons[getSeasonIndex(month)];
  • 이 영상을 찍은 유튜버는 if문이 복잡해보인다고 하였으나, 두개의 로직을 처음 보는 사람이라면 if문의 코드가 더 쉽게 읽힐 것이라는 생각이 들었다.
  • 댓글에서도 if문을 사용해서 더 직관적으로 적는 것이 읽기 쉽다는 사람이 있었다.
  • getSeasonNameFromMonthNumber과 같이 getSeasonIndex 함수명을 조금 더 직관적으로 보일 것 같다는 의견도 있었다. 하지만 함수명이 너무 길어서 작성하기 힘들 것 같다…
1
2
3
4
const seasons = ["", "여름", "가을", "겨울"];
const getSeasonNameFromMonthNumber = (month) =>
  Math.floor(((month + 9) % 12) / 3);
const getSeason = (month) => seasons[getSeasonNameFromMonthNumber(month)];
  • 코드를 리팩토링하는 것에는 정답이 없고, 내가 속해 있는 팀별 성향에 맞게 작성하면 되는 것 같다.
  • 어떤 사람은 if문에 작성된 코드가 더 쉽게 읽히고, 어떤 사람은 아래 코드가 더 효율적이라고 생각하기 때문이다.

정리

if else가 중복되면 switch case로 바꿔보자.
=> 나름의 규칙성을 발견하기 쉬워진다.
=> 규칙을 발견하면 이를 바탕으로 객체로 추출 가능하다!

객체를 함수 외부로 분리하면
공통로직 / 분기별 실제 동작 정의가 분리되어 읽기도 수월해지고, 수정사항이 생겨도 공통로직은 건드리지 않아도 되어 편리하다.

리팩토링할 소재를 발견할 수도 있고,
소재가 발견되지 않더라도 객체 형태로 전환한 그 자체로 이미 깨끗해진다.

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