소트웍스 앤솔로지 의 Object Calisthenics 내용을 TypeScript로 적용해봤습니다.
대부분의 프로그래밍 언어는 if
/else
구문을 지원한다. 하지만 조건문이 중첩될수록 가독성은 떨어지기 마련이다. else if
를 넣어 조건문의 분기를 추가하는 것은 코드를 리팩토링하는 것보다 훨씬 쉽지만 이는 가독성이 떨어지고 유지보수하기 어려운 코드를 만들어내곤 한다.
else
가 있는 다음 코드를 살펴보자.
Copy const login = (username : string , password : string ) => {
if ( userRepository .isValid (username , password)) {
redirect ( 'homepage' );
} else {
addFlash ( 'ERROR' , '잘못된 인증 정보입니다.' );
redirect ( 'login' );
}
};
이런 경우 else
구문을 쉽게 없애는 방법은 early return 기법을 적용하는 것이다.
Copy const login = (username : string , password : string ) => {
if ( userRepository .isValid (username , password)) {
return redirect ( 'homepage' );
}
addFlash ( 'ERROR' , '잘못된 인증 정보입니다.' );
return redirect ( 'login' );
};
이제 예외처리를 하는 경우와 기본값으로 실행되어야 하는 경우에 대해 생각해보자.
예외를 먼저 처리하고 마지막에 기본값으로 실행시킬 것인가? (방어적 프로그래밍 과 유사)
기본값을 먼저 처리하고 예외처리 로직을 코드 밑부분에 이을 것인가? (낙관적 프로그래밍)
어려운 말이 아니고 말 그대로 예외를 위에서 처리하는 것이다. 위에 제시된 코드의 경우 isValid
하면 redirect("homepage")
하고 그 외의 예외의 경우 addFlash
와 redirect("login")
를 실행하게 된다.
Copy const login = (username : string , password : string ) => {
if ( ! userRepository .isValid (username , password)) {
addFlash ( 'ERROR' , '잘못된 인증 정보입니다.' );
return redirect ( 'login' );
}
return redirect ( 'homepage' );
};
또는 return
이 싫거나 함수의 반복(이 경우 redirect
)을 줄이고 싶을 경우 이런 방법도 가능하다.
Copy const login = (username : string , password : string ) => {
let redirectRoute = 'homepage'
if ( ! userRepository .isValid (username , password)) {
addFlash ( 'ERROR' , '잘못된 인증 정보입니다.' );
redirectRoute = 'login' ;
}
redirect (redirectRoute);
};
필자의 경우는 early return을 하는 방어적 프로그래밍의 방식을 선호한다. 순서만 바뀌었을뿐이지만, 개인적으로 예외상황을 먼저 처리한다면 null
이나 undefined
를 처리할 때 TypeError
를 잡기도 용이하고 기본 로직을 확장하기 좋다고 생각한다.
객체지향 프로그래밍의 [다형성](https://ko.wikipedia.org/wiki//다형성_(컴퓨터_과학))을 활용한 상태 패턴 과 전략 패턴 을 사용하는 방법도 있다. 이런 패턴들을 사용한다면 상태(Unauthorized
, Authorized
)에 따라 수행되는 로직(next
)을 캡슐화 할 수 있고, 코드를 수직이 아닌 수평적으로 확장할 수 있다. 사실 적당한 예시인지 모르겠지만 주요 디자인 패턴을 다룰 때 살펴보도록 하자.
Copy interface RedirectStatus {
next : () => void ;
}
class Authorized implements RedirectStatus {
next () {
return redirect ( 'homepage' );
}
}
class Unauthorized implements RedirectStatus {
next () {
addFlash ( 'ERROR' , '잘못된 인증 정보입니다.' );
return redirect ( 'login' );
}
}
class Page {
constructor ( private status : RedirectStatus = new Authorized ()) {}
setStatus (status : RedirectStatus ) {
this .status = status;
}
handleNext () {
return this . status .next ();
}
}
const page : Page = new Page ();
const login = (username : string , password : string ) => {
if ( ! userRepository .isValid (username , password)) {
page .setStatus ( new Unauthorized ());
}
page .handleNext ();
};