code bad smell - early return을 사용한 refactoring 기법
early return을 소개하는 이유
if문이 중첩으로 있으면 보통 읽기가 어렵습니다.
이를 개선하기위한 방법 중 하나가, early return이라는 방식이 있습니다.
early return이란?
말 그대로 빨리 리턴을 한다는 뜻입니다.
조건이 부합하지 않으면 곧바로 return을 하도록 하는 코딩 패턴입니다.
이렇게 작성함으로써, 가독성이 좋은 코드가 될 수 있습니다.
early return을 적용한코드와 그렇지 않은코드
javascript 의 예시로 early return을 적용한 코드와, 그렇지 않은 코드의 예시를 살펴보겠습니다.
//early return을 적용한 코드
function foo1(){
if (!bar()) {
return null;
}
return 1;
}
//earyl return이 적용되지 않은 코드
function foo2(){
var ret = null;
if (bar()) {
ret = 1;
}
return ret;
}
earyl return을 사용한 코드는 말그대로,
if(!bar()) 쪽에 조건이 맞지 않으면 return을 써주었습니다.
foo1, foo2는 로직이 똑같고, 가독성도 큰 차이는 없는 것 같습니다.
하지만 코드가 길어진다면 early return을 사용한 foo1 쪽 코드가 더 가독성이 좋아집니다.
좀 더 복잡한 코드를 보고, 코드 개선을 해보면서 살펴보겠습니다.
early return으로 코드 개선하기
아래와 같이 중첩된 if문의 코드를 봅시다.
public int SomeFunction(bool cond1, string name, int value, AuthInfo perms)
{
int retval = SUCCESS;
if (someCondition)
{
if (name != null && name != "")
{
if (value != 0)
{
if (perms.allow(name)
{
// Do Something
}
else
{
retval = PERM_DENY;
}
}
else
{
retval = BAD_VALUE;
}
}
else
{
retval = BAD_NAME;
}
}
return retval;
}
depth 가 깊어질수록, 가독성이 떨어지는것을 느낄 수 있을것입니다.
depth를 더 늘릴수록 에디터 화면 바깥으로 까지 코드가 길어질 수 있기에,
스크롤도 해야하는 불편함도 있을것입니다.
이 코드를 early return으로 개선하려면 어떻게 해야할지 살펴보겠습니다.
먼저, if문이 있고 if문 안의 내용이 길다면 early return을 고려해봄직한 상황입니다.
특히 중첩으로 block이 있으면 더욱 고려를 해볼만 합니다.
이 코드를 early return으로 개선하겠다라고 생각하고
맨 바깥쪽의 if문을 살펴봅시다.
그러면 아래 코드와 같습니다.
public int SomeFunction(bool cond1, string name, int value, AuthInfo perms)
{
int retval = SUCCESS;
if (someCondition)
{
//상세코드..
}
return retval;
}
early return은 조건이 맞지 않을경우 빨리 리턴해주는 것이므로, someCondition을 반전시켜줍니다.
그러고 난 후, 반전시켰을 때의 조건은 어떤값을 return을 하는지를 살펴봅시다.
그러면 retval을 바로 리턴해주는것을 알 수 있고, 아래와 같이 개선할 수 있습니다.
public int SomeFunction(bool cond1, string name, int value, AuthInfo perms)
{
int retval = SUCCESS;
if (!someCondition)
{
return retval;
}
//상세코드..
return retval;
}
"상세코드.." 부분을 다시 풀어서 전체코드를 보면, 아래와 같습니다.
public int SomeFunction(bool cond1, string name, int value, AuthInfo perms)
{
int retval = SUCCESS;
if (!someCondition)
{
return retval;
}
if (name != null && name != "")
{
if (value != 0)
{
if (perms.allow(name)
{
// Do Something
}
else
{
retval = PERM_DENY;
}
}
else
{
retval = BAD_VALUE;
}
}
else
{
retval = BAD_NAME;
}
return retval;
}
아직까지는 별차이가 없는것 같습니다.
다음의 개선된 if문은 제외하고, 다음 if문의 틀을 봅시다.
그러면 아래와 같은 코드를 볼 수 있습니다.
public int SomeFunction(bool cond1, string name, int value, AuthInfo perms)
{
int retval = SUCCESS;
if (!someCondition)
{
return retval;
}
if (name != null && name != "")
{
//상세코드
}
else
{
retval = BAD_NAME;
}
return retval;
}
if~else문으로 구성되어 있는것을 확인할 수 있고,
if문을 early return방식으로 개선하면 (== 조건이 맞지않을때 리턴한다)
아래와 같이 개선이 됩니다.
if(!(name != null && name != ""))
{
retval = BAD_NAME;
return retval;
}
//상세코드
여기서 !(name != null && name != "")부분은 분배법칙에 의해,
괄호를 풀어쓴다면 다음과 같이 작성될 수 있습니다.
(name == null || name == "")
이를 적용하고 상세코드를 풀어쓰면 아래와 같은 코드가 됩니다.
public int SomeFunction(bool cond1, string name, int value, AuthInfo perms)
{
int retval = SUCCESS;
if (!someCondition)
{
return retval;
}
if (name == null || name == "")
{
retval = BAD_NAME;
return retval;
}
if (value != 0)
{
if (perms.allow(name)
{
// Do Something
}
else
{
retval = PERM_DENY;
}
}
else
{
retval = BAD_VALUE;
}
return retval;
}
뭔가 조금씩 개선된게 보이시나요?
마찬가지로 if(value != 0) 에 대해서도 개선을 하면 아래와 같이 될것입니다.
public int SomeFunction(bool cond1, string name, int value, AuthInfo perms)
{
int retval = SUCCESS;
if (!someCondition)
{
return retval;
}
if (name == null || name == "")
{
retval = BAD_NAME;
return retval;
}
if(value == 0)
{
retval = BAD_VALUE;
return retval;
}
if (perms.allow(name))
{
// Do Something
}
else
{
retval = PERM_DENY;
}
return retval;
}
마찬가지 방식으로 if(perms.allow(name)) 코드도 개선하면 아래와 같은 꼴이 됩니다.
public int SomeFunction(bool cond1, string name, int value, AuthInfo perms)
{
int retval = SUCCESS;
if (!someCondition)
{
return retval;
}
if (name == null || name == "")
{
retval = BAD_NAME;
return retval;
}
if(value == 0)
{
retval = BAD_VALUE;
return retval;
}
if (!perms.allow(name))
{
retval = PERM_DENY;
return retval;
}
// Do Something
return retval;
}
지금도 충분히 간단해 보이지만,
각각의 if문에서 retval = BAD_VALUE를 선언하는 부분은
이 값을 바로 리턴해준다면 딱히 필요가 없는 구문이기에 삭제해도 됩니다.
그러면 최종 꼴은 아래 코드와 같아집니다.
public int SomeFunction(bool cond1, string name, int value, AuthInfo perms)
{
int retval = SUCCESS;
if (!someCondition)
{
return SUCCESS;
}
if (name == null || name == "")
{
return BAD_NAME;
}
if(value == 0)
{
return BAD_VALUE;
}
if (!perms.allow(name))
{
return PERM_DENY;
}
// Do Something
return retval;
}
총 23줄의 코드가 되었으며, 가독성이 훨씬 좋아진것을 느낄수 있을 것입니다.
잘 모르시겠으면, 원래 코드를 보겠습니다. 아래와 같았습니다.
public int SomeFunction(bool cond1, string name, int value, AuthInfo perms)
{
int retval = SUCCESS;
if (someCondition)
{
if (name != null && name != "")
{
if (value != 0)
{
if (perms.allow(name)
{
// Do Something
}
else
{
retval = PERM_DENY;
}
}
else
{
retval = BAD_VALUE;
}
}
else
{
retval = BAD_NAME;
}
}
return retval;
}
중첩된 if구문이 무엇이었는지 생각하지 않아도 되고,
길이도 짧아져서 코드 읽기가 쉬워졌습니다.
코드 읽기가 쉬워졌다는 뜻은 곧 유지보수를 빨리할 수 있는(=개발비용이 적게드는)효과를 낸다고 볼 수 있습니다.
총평
개발하다보면 나도모르게 if문을 남발하다가 중첩이 되는경우가 있지 않나 싶습니다.
이게 만일 남의 코드이거나 예전에 짰던 내 코드라면 정말 읽기가 싫어질것입니다.
개인적으로는 if문을 사용할때마다 early return을 적용할 수는 있지 않은지 항상 생각을 하고 있습니다.
특히 for문과 if문 등에 있는 block 이 중첩되면 어떻게 더 개선할 수 없을지 노력합니다.
물론 코드리뷰할때도 잘 살펴보는 항목입니다.
간단한 방법으로 코드 가독성을 크게 개선할 수 있기에,
저처럼 if문이 있을때 마다 ealry return으로 개선할 수 없는지 생각하는것을 습관화 하는것을 적극 추천합니다.
#리팩토링,#code,#bad,#smell,#badsmell,#codesmell,#refactor,#refactoring,#early,#return,#earlyreturn,#빠른리턴,#얼리리턴
댓글