...
자바스크립트 조건문 리팩토링
아무리 고급적인 프로그래밍 문법, 기법들을 익히고 그걸 활용하더라도, 자신의 코드에서 가장 많이 비중을 차지하는 문법은 아마 조건문 일 것이다. 프로그램은 반복문과 조건문만 있으면 돌아간다라는 말이 있듯이 조건 분기문은 프로그래밍의 기본이기도 하다. 그런데 이 if-else 분기문을 생각없이 지나치게 중첩하고 생각없이 코드를 나열한다면 가독성이 크게 떨어진다는 문제점이 생기게 된다. 예를들어 if문 안에 if가 있고 그 else문 안에 if~else 안의 if 등등.. 이렇게 코드를 짜듯이 말이다. 농담 삼아 아래와 같이 코드가 마치 아됴겐을 맞은것과 같은 모습이 되어 버릴지도 모른다.
물론 컴퓨터 입장에선 코드 로직상 문제는 없을텐지만 이를 나중에 사람이 유지보수 하기 위해 코드를 복기하는 과정에서 굉장한 노력과 시간 그리고 스트레스가 소모될 것이다. 그래서 이러한 if문을 최적화하는 스타일 기법이 존재한다.
이번 포스팅에선 4가지의 방법론을 소개할 예정인데, 첫번째와 두번째는 if문의 위치를 조정함으로써 좀더 보기 편하게 유지보수 좋게 만들어주는 코드 순서에 가깝다면, 세번째 네번째는 '디자인 패턴'에 가깝다.
주니어 프로그래머와 시니어 프로그래머를 가르는 기준은 회사마다 '사바사' 이기 때문에 항상 논란이 되지만, if문을 어떤식으로 짜느냐에 따라 이 둘을 가를수 있다는 점은 누구나 동의한다. 그만큼 조건문 리팩토링은 사소한 것 처럼 보이지만 유지보수가 중요한 현업에서 굉장히 중요하게 여겨진다. 지금부터 주니어 딱지의 1mg 이라도 떼보러 가보자.
길다란 조건식은 함수로 분리
아래 예제는 날짜를 구해서 비교를 통해 여름 방학인지, 겨울 방학인지 구하는 조건식이다. 한눈에 봐도 조건식의 코드가 굉장히 난잡한 것을 볼 수 있다.
const now = new Date();
const year = date.getFullYear();
if (now > new Date(year, 6, 25) && now < new Date(year, 7, 31)) {
// 매년 6월 25일 ~ 7월 31일 일 경우
console.log('여름 방학 입니다');
} else if (now > new Date(year, 11, 19) && now < new Date(year + 1, 1, 28)) {
// 매년 11월 19일 ~ 내년 1월 28일 일 경우
console.log('겨울 방학 입니다');
}
주석을 통해 어느정도 뜻하는 바를 한눈에 알 수는 있지만 분기문 코드 자체가 여전히 난잡하고 불성 사납다.
이럴 경우 조건식 자체를 아예 함수로 만들어서 심플하게 구성하는 것이 좋다. 이때 함수명을 이름만 보고도 무엇을 판단하는 로직인지 쉽게 알 수 있게 하기 위해 적절한 함수명을 지어준다. 조건문을 리팩토링한 결과는 아래와 같다.
function isSummerVacation(now) {
const year = date.getFullYear();
return now > new Date(year, 6, 25) && now < new Date(year, 7, 31)
}
function isWinterVacation(now) {
const year = date.getFullYear();
return now > new Date(year, 11, 19) && now < new Date(year + 1, 1, 28)
}
const now = new Date();
if (isSummerVacation(now)) {
// 매년 6월 25일 ~ 7월 31일 일 경우
console.log('여름 방학 입니다');
} else if (isWinterVacation(now)) {
// 매년 11월 19일 ~ 내년 1월 28일 일 경우
console.log('겨울 방학 입니다');
}
isSummerVacation() 함수와 isWinterVacation() 함수를 만들어주고 길다란 조건문을 함수의 return 문으로 넣었다. 그리고 if 문의 블록에는 함수 호출문으로 변경해준다. 이렇게 함으로써 코드가 더욱 가독성이 좋아짐을 볼수있다. 전체적인 로직 자체는 변한게 없다. 코드를 모듈화하여 그저 if 문의 조건식 코드 길이를 함수 호출문을 통해 획기적으로 줄인 것이다.
Early Return 기법
Early Return이란 조건문에서 먼저 Return할 수 있는 부분은 분리하여 우선 초반 라인의 if문 내에서 Return하여 함수를 미리 종료하는 기법을 말한다. if-else문이 너무 많으면 읽기가 어렵고 조건에 대해 명시적이지 않을 수 있는데, Early Return을 적용하면 뒤의 코드로 진입하지 않아 else문을 사용하지 않아, 로직은 변함이 없으면서 가독성이 좋고 더 명시적인 코드로 리팩토링 할 수 있다.
다음과 같이 loginService() 라는 함수에 로그인 여부, 토큰 여부, 가입 여부를 확인하는 중첩 분기문이 있다고 가정해보자.
function loginService(isLogin, user) {
let result = '';
// 1. 로그인 여부 확인
if (isLogin == false) {
// 2. 토큰 존재 확인
if (checkToken() == true) {
// 3. 가입 여부 재확인
if (user.nickName == undefined) {
registerUser(user); // 회원 가입하기
result = '회원가입 성공';
} else {
refreshToken(); // 토큰 발급
result = '로그인 성공';
}
} else {
throw new Error('에러 - 토큰이 없습니다 !');
}
} else {
result = '이미 로그인 중';
}
result += ` (+ 시도 횟수 ${count++}번)`;
return result;
}
이 코드 모습을 대충 그림으로 흐름도를 표현하자면 아래와 같이 될 것이다.
의도하고자 하는 바는 알겠지만 계속 조건을 중첩해서 들어가 생각해야 되기 때문에 코드 가독성이 그렇게 좋지 않아 보인다. 따라서 이를 최적화 해보자.
Early Return 리팩토링 순서
1. if문 다음에 나오는 공통된 절차를 첫번째 분기점 내부에 각각 넣는다
코드 중복을 피하려고 꺼내놨더니 거꾸로로 다시 중복 시켜놓는 것으로 보일 수 있다. 다만 이는 리팩토링에 있어 필요한 과정이니 그대로 이행하자.
이때 여기서 첫번째 분기점이란, 코드 레벨의 첫 if문을 말한다. 즉, if문 안의 if문 같은 경우 제외 된다.
function loginService(isLogin, user) {
let result = '';
if (isLogin == false) {
if (checkToken() == true) {
if (user.nickName == undefined) {
registerUser(user);
result = '회원가입 성공';
} else {
refreshToken();
result = '로그인 성공';
}
} else {
throw new Error('에러 - 토큰이 없습니다 !');
}
result += ` (+ 시도 횟수 ${count++}번)`; // 공통된 절차
return result; // 공통된 절차
} else {
result = '이미 로그인 중';
result += ` (+ 시도 횟수 ${count++}번)`; // 공통된 절차
return result; // 공통된 절차
}
}
2. 분기점에서 짧은 절차부터 실행하게 if문을 반전 시킨다
if문 과 else문을 블럭을 보면 코드 절차가 긴것은 if문이다. 따라서 절차가 짧은 else 블럭 코드 부분을 if 문에 넣어주기 위해 if-else를 반전 시킨다. 간단하게 기존의 if (isLogin == false) 를 거꾸로 if (isLogin == true) 로 구성해주면 코드를 뒤바꿀 수 있게 되어 짧은 부분이 위쪽으로 오게 되고 길다란 부분이 아래쪽으로 오게 된다.
function loginService(isLogin, user) {
let result = '';
if (isLogin == true) { // else와 뒤바꾸기 위하여 boolean 을 반전시킨다
result = '이미 로그인 중';
result += ` (+ 시도 횟수 ${count++}번)`;
return result;
} else {
if (checkToken() == true) {
if (user.nickName == undefined) {
registerUser(user);
result = '회원가입 성공';
} else {
refreshToken();
result = '로그인 성공';
}
} else {
throw new Error('에러 - 토큰이 없습니다 !');
}
result += ` (+ 시도 횟수 ${count++}번)`;
return result;
}
}
만일 아래와 같이 첫 if문이 else문이 없는 순수 if문이라서 진행에 무리가 있다면, 아래와 같이 간단하게 빈 else문을 만들어 버리고 진행하면 된다. (그게 그거기 때문)
3. 짧은 절차가 끝나면 return(함수 내부의 경우)이나 break(for문 내부의 경우) 로 일찍 중단점을 추가한다
본문의 예제는 함수문 이고, 이미 리턴문을 위로 올렸으니 그대로 진행하면 된다.
4. else를 제거한다. (이 때 중첩하나가 제거된다)
function loginService(isLogin, user) {
let result = '';
if (isLogin == true) {
result = '이미 로그인 중';
result += ` (+ 시도 횟수 ${count++}번)`;
return result;
} // else { 를 제거한다
if (checkToken() == true) {
if (user.nickName == undefined) {
registerUser(user);
result = '회원가입 성공';
} else {
refreshToken();
result = '로그인 성공';
}
} else {
throw new Error('에러 - 토큰이 없습니다 !');
}
result += ` (+ 시도 횟수 ${count++}번)`;
return result;
}
5. 다시 1번 부터 돌아가 작업을 반복한다
6. 리팩토링 결과
이렇게 중첩 조건문들을 하나하나 꺼내면서 리팩토링한 결과 코드의 모습은 마치 아래와 같이 중첩되지 않고 하나의 레벨에서 각 조건식을 판별하는 형태가 되게 된다. 이러헥 구성하면 로직이 의미하고자 하는 바를 한눈에 알 수 있고 조건식을 중첩하여 생각하지않고 분리하여 생각함으로써 코드를 빠르게 복기할 수 있게 된다.
function loginService(isLogin, user) {
let result = '';
// 1. 로그인 여부 확인
if (isLogin == true) {
result = '이미 로그인 중';
result += ` (+ 시도 횟수 ${count++}번)`;
return result;
}
// 2. 토큰 존재 확인
if (checkToken() == false) {
throw new Error('에러 - 토큰이 없습니다 !');
}
// 3. 가입 여부 재확인
if (user.nickName == undefined) {
registerUser(user); // 회원 가입하기
result = '회원가입 성공';
} else {
refreshToken(); // 토큰 발급
result = '로그인 성공';
}
result += ` (+ 시도 횟수 ${count++}번)`;
return result;
}
3번째 분기인 가입 여부 재확인 부분은 다시한번 리팩토링을 하여 else를 없애도 되고 안없애도 된다.
Early Return 기법은 비단 자바스크립트 프로그래밍 언어 뿐만 아니라 다른 프로그래밍 언어에도 당연히 똑같이 적용되는 부분이니 연습해두면 좋다.
Lookup Table 기법
이번에는 좀더 자바스크립트 스러운 조건문 클린 코드를 소개해볼 것이다. 이 리팩토링 기법은 자바스크립트 프로그래밍 언어만의 일급 객체의 특성을 이용한 방법으로, 기존의 else if 로직을 key-value 쌍의 형태로 각각의 논리를 캡슐화한 것이다. 마치 객체 테이블을 조회하여 분기를 실행하는것과 같다하여 Lookup Table 기법이라 불리운다.
예를 들어 아래와 같이 각 소셜 서비스들의 전용 로그인 함수들이 있다고 가정하고, socialLogin() 함수에서 하나의 매개변수 where에 대해 조건별로 로그인 분기가 일자 형태로 나뉘어져 있다고 하자.
// ... 복잡한 로그인 과정 생략한 모듈
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(id);
} else if (where === 'kakao') {
domain = kakaoLogin(id);
} else if (where === 'facebook') {
domain = facebookLogin(id);
} else if (where === 'google') {
domain = googleLogin(id);
}
return `${domain} ${id}`;
};
console.log(socialLogin('naver', 'inpa'));
console.log(socialLogin('google', 'inpa'));
else if의 연속으로, 이 코드 모습을 대충 그림으로 흐름도를 표현하자면 아래와 같이 될 것이다.
보기엔 가독성 면에서 그렇게 큰 문제가 되지 않아 보이지만, 이러한 구성의 코드를 좀더 효율적으로 짤 수 가 있다.
Lookup Table 리팩토링 순서
1. 조건문만을 따로 분리한다
가장 먼저 socialLogin() 함수내 분기문 코드만 따로 뽑아내어 별도의 executeLogin() 함수로 분리해 구성해준다. 그러면 기존 socialLogin() 함수는 executeLogin() 함수를 호출해 결과값을 받는 형식이 된다.
const executeLogin = (where, id) => {
if (where === 'naver') {
domain = naverLogin(id);
} else if (where === 'kakao') {
domain = kakaoLogin(id);
} else if (where === 'facebook') {
domain = facebookLogin(id);
} else if (where === 'google') {
domain = googleLogin(id);
}
};
const socialLogin = (where, id) => {
let domain = executeLogin(where, id);
return `${domain} ${id}`;
};
2. if-else 문을 switch-case 문으로 변환한다
중첩 if문이 아닌 형태는 switch 문으로 변환이 가능하다. 변환하면 아래와 같이 구성될 것이다.
const executeLogin = (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) => {
let domain = executeLogin(where, id);
return `${domain} ${id}`;
};
참고로 자바스크립트에서 switch-case문일 경우, 다른 언어에서는 해당 케이스로 jump를 해서 바로 원하는 곳으로 이동하지만, js는 case를 나열된 순서대로 평가하기 때문에 사용을 지양하라는 설도 있다.
3. switch-case 문을 객체로 변환한다
switch문을 잘보면 각 문자열 case마다 이에 맞는 함수를 호출하고 있다는 규칙성을 찾을 수 있다. 따라서 case 부분을 객체의 key 값으로, 리턴문을 객체의 value 값으로 구성해준다면 아래와 같이 자바스크립트 객체로 똑같이 구성이 가능하게 된다.
const executeLoginMap = {
naver: naverLogin,
kakao: kakaoLogin,
facebook: facebookLogin,
google: googleLogin,
};
const socialLogin = (where, id) => {
let domain = executeLoginMap[where](id); // naver일 경우 naverLogin(id) 로 함수 실행
return `${domain} ${id}`;
};
그리고 기존 socialLogin() 함수에서 executeLoginMap 객체를 호출 할때 배열 인자를 통해 호출할 key값을 얻게하고, executeLoginMap[where] 통해 얻은 객체의 value값인 함수 표현식을 가져와 그대로 (id) 매개변수를 할당하여 함수를 실행함으로써 결과값을 얻는, 마치 아름다운(?) 코드 디자인 패턴의 일종이라고 볼 수 있다.
처음 그림과 비교하자면, 기존에는 올바른 분기를 검사하기 위해 일일히 각 A, B, C 분기를 일일히 모두 순회하여야 하지만, Lookup Table 기법을 적용하면 모든 조건을 순회할 필요없이 매개변수의 값에 따라 이에 해당하는 객체의 key-value 구성에 따라 각각의 논리는 별도의 함수로 다시 작성되므로 함수를 호출하는 식으로 구성하였기 때문에, 프로세스가 아래 그림과 같이 병렬적으로 분기에 접근하게 되는 형태가 되고 이는 곧 성능 향상으로 직결되게 된다.
Responsibility chain pattern
다만 Lookup Table 기법은 매치된 분기가 있으면 그 분기만 실행하고 함수를 종료한다. 그리고 key 매칭 방식을 사용하기 때문에 하나의 인자값(where)에 대해서만 비교를 하니 만일 여러개의 인자를 비교한다고 하면 적합하지가 않게 된다.
따라서 이에 대한 대응 기법으로 책임 연쇄 패턴(Chain Responsility Pattern)이 존재한다.
책임 연쇄 패턴은 GOF 디자인 패턴의 한 종류로서, 분기문의 블럭들을 객체화 하여, 다수의 처리 객체(핸들러)들을 체인 형태로 묶는 패턴이다. 그래서 어떠한 요청이 발생하였을 때 그 요구를 처리할 객체 핸들러를 순회하는 식으로 하여 분기문을 객체 지향적으로 표현한 기법이라고 보면 된다.
위의 Early Return 예제의 결과를 다시 가져와 보았다.
const refreshToken = () => {};
const registerUser = () => {};
function loginService(isLogin, checkToken, user) {
let result = '';
let count = 0;
// 1. 로그인 여부 확인
if (isLogin == true) {
result += '이미 로그인 중';
result += ` (+ 시도 횟수 ${++count}번)`;
return result;
}
// 2. 토큰 존재 확인
if (checkToken == false) {
throw new Error('에러 - 토큰이 없습니다 !');
}
// 3. 가입 여부 재확인
if (user.nickName == undefined) {
registerUser(user); // 회원 가입하기
result += '회원가입 성공';
result += ` (+ 시도 횟수 ${++count}번)`;
return result;
}
refreshToken(); // 토큰 발급
result += '로그인 성공';
result += ` (+ 시도 횟수 ${++count}번)`;
return result;
}
console.log(loginService(false, true, { nickName: 'inpa' })); // 로그인 성공 (+ 시도 횟수 1번)
console.log(loginService(true, false, { nickName: 'inpa' })); // 이미 로그인 중 (+ 시도 횟수 1번)
console.log(loginService(false, true, {})); // 회원가입 성공 (+ 시도 횟수 1번)
console.log(loginService(false, false, { nickName: 'inpa' })); // Error: 에러 - 토큰이 없습니다 !
이 자체로도 문제는 없지만 만일 새로운 분기문이 추가될 경우 함수 코드 자체를 통짜로 뜯어 고쳐야 할 수도 있다. 이는 하나의 함수 안에 여러가지의 인자에 대한 조건 판단을 하는 책임을 중앙 집권 적으로 모아져 있기 때문이다.
클래스를 이용한 책임 연쇄 패턴 설계
따라서 위의 Early Return 예제의 결과를 책임 연쇄 패턴으로 재구성 해보자. 코드를 자바(Java) 프로그래밍 언어 스럽게 클래스를 이용해 설계하자면 다음과 같다.
class Handler {
nextHandler; // 연결될 다음 핸들러 객체를 저장하는 필드
result = '';
count = 0;
setNext(handler) {
this.nextHandler = handler; // 메서드를 통해 연결할 핸들러를 등록한다
return handler; // 메서드 체이닝을 위해 인자의 핸들러를 리턴한다
}
}
class LoginHandler extends Handler {
check(isLogin, checkToken, user) {
// 1. 로그인 여부 확인
if (isLogin == true) {
this.result = '이미 로그인 중';
this.result += ` (+ 시도 횟수 ${++this.count}번)`;
return this.result;
} else {
return this.nextHandler.check(isLogin, checkToken, user);
}
}
}
class TokenHandler extends Handler {
check(isLogin, checkToken, user) {
// 2. 토큰 존재 확인
if (checkToken == false) {
throw new Error('에러 - 토큰이 없습니다 !');
} else {
return this.nextHandler.check(isLogin, checkToken, user);
}
}
}
class JoinHandler extends Handler {
check(isLogin, checkToken, user) {
// 3. 가입 여부 재확인
if (user.nickName == undefined) {
registerUser(user); // 회원 가입하기
this.result = '회원가입 성공';
this.result += ` (+ 시도 횟수 ${++this.count}번)`;
return this.result;
} else {
return this.nextHandler.check(isLogin, checkToken, user);
}
}
}
class SuccessHandler extends Handler {
check(isLogin, checkToken, user) {
refreshToken(); // 토큰 발급
this.result = '로그인 성공';
this.result += ` (+ 시도 횟수 ${++this.count}번)`;
return this.result;
}
}
const loginService = (isLogin, checkToken, user) => {
let result = '';
// 1. 핸들러(처리 객체) 생성
const handler1 = new LoginHandler();
const handler2 = new TokenHandler();
const handler3 = new JoinHandler();
const handler4 = new SuccessHandler();
// 2. 핸들러 체이닝 등록
handler1.setNext(handler2).setNext(handler3).setNext(handler4);
// 3. 핸들러 실행
result = handler1.check(isLogin, checkToken, user); // handler1 → handler2 → handler3 → handler4
return result;
};
console.log(loginService(false, true, { nickName: 'inpa' })); // 로그인 성공 (+ 시도 횟수 1번)
console.log(loginService(true, false, { nickName: 'inpa' })); // 이미 로그인 중 (+ 시도 횟수 1번)
console.log(loginService(false, true, {})); // 회원가입 성공 (+ 시도 횟수 1번)
console.log(loginService(false, false, { nickName: 'inpa' })); // Error: 에러 - 토큰이 없습니다 !
각 핸들러들이 자기 고유의 요청에 대한 처리 책임을 지니고 있으며, 실행부에서 setNext() 메서드를 통해 핸들러를 체인 시켜준다. 그리고 실행 함수를 호출하면 마치 if-else 처럼 분기를 객체마다 순회하면서 처리를 하게 되는 것이다. 이런 식으로 복합문 else if 논리를 분리하는 데 성공했다.
좀더 심플한 책임 연쇄 패턴 설계
다만 자바스크립트는 일급 객체의 특징을 갖는 언어로서 굳이 클래스 지향으로 설계할 필요가 없다. 이 부분은 모든 GOF 디자인 패턴의 단점이기도 한데, 당장 위의 코드만 봐도 엄청나게 길어진걸 확인 할 수 있다. 따라서 일반 객체를 이용해 다음과 같이 좀 더 자바스크립트 프로그래밍 답게 심플하게 리팩토링 할 수가 있다.
먼저 객체를 담은 배열을 생성하고, 원소 객체마다 match와 action이라는 함수를 각각 만들어 준다. match 부분은 if문의 조건식을 넣는 부분이고, action은 if문 블록 안의 실행 코드를 넣는 부분이다. match는 false일 경우 다음 match로 넘어가게 된다. action은 match가 true 일경우 실행되고 평가를 종료하게 된다.
const rules = [
{
match: function (a, b, c) { /* ... */ },
action: function (a, b, c) { /* ... */ }
},
{
match: function (a, b, c) { /* ... */ },
action: function (a, b, c) { /* ... */ }
},
{
match: function (a, b, c) { /* ... */ },
action: function (a, b, c) { /* ... */ }
}
]
이러한 기법을 이용해서 최종으로 구성한 자바스크립트 책임 연쇄 패턴 코드는 다음과 같다.
const Handler = {
result: '',
count: 0,
rules: [
{
// match는 false일 경우 다음 match로 넘어가게 된다.
match(isLogin, checkToken, user) {
return isLogin ? true : false; // 1. 로그인 여부 확인
},
// action은 match가 true 인 실행 부분을 기재한다
action(isLogin, checkToken, user) {
Handler.result = '이미 로그인 중';
Handler.result += ` (+ 시도 횟수 ${Handler.count++}번)`;
return Handler.result;
},
},
{
match(isLogin, checkToken, user) {
return !checkToken ? true : false; // 2. 토큰 존재 확인
},
action(isLogin, checkToken, user) {
throw new Error('에러 - 토큰이 없습니다 !');
},
},
{
match(isLogin, checkToken, user) {
return user.nickName == undefined ? true : false; // 3. 가입 여부 재확인
},
action(isLogin, checkToken, user) {
registerUser(user); // 회원 가입하기
Handler.result = '회원가입 성공';
Handler.result += ` (+ 시도 횟수 ${++Handler.count}번)`;
return Handler.result;
},
},
{
match(isLogin, checkToken, user) {
return true; // 마지막 핸들러 부분이니까 바로 action() 이 실행되도록
},
action(isLogin, checkToken, user) {
refreshToken(); // 토큰 발급
Handler.result = '로그인 성공';
Handler.result += ` (+ 시도 횟수 ${++Handler.count}번)`;
return Handler.result;
},
},
],
};
const socialLogin = (isLogin, checkToken, user) => {
let result = '';
// {march, action} 핸들러 객체가 들어있는 의사 결정 규칙 배열 rules를 순회하면서
for (const rule of Handler.rules) {
// 만일 해당 분기에 해당되면
if (rule.match(isLogin, checkToken, user)) {
result += rule.action(isLogin, checkToken, user); // 그 분기의 실행부를 호출한다
return result;
}
}
};
console.log(loginService(false, true, { nickName: 'inpa' })); // 로그인 성공 (+ 시도 횟수 1번)
console.log(loginService(true, false, { nickName: 'inpa' })); // 이미 로그인 중 (+ 시도 횟수 1번)
console.log(loginService(false, true, {})); // 회원가입 성공 (+ 시도 횟수 1번)
console.log(loginService(false, false, { nickName: 'inpa' })); // Error: 에러 - 토큰이 없습니다 !
# 참고자료
FE재남 유튜브 - if else 리팩토링
https://javascript.plainenglish.io/how-to-refactor-your-complex-nested-if-else-code-28aa162047d5
이 글이 좋으셨다면 구독 & 좋아요
여러분의 구독과 좋아요는
저자에게 큰 힘이 됩니다.