Guard Clause란 무엇일까

if/else 분기는 프로그래밍을 처음 배울때 가장 먼저 접하는 컨셉중 하나입니다. 조건 분기가 필요한 부분에서 가장 흔하게 사용되는 statement 이기도 한데요, 이 간단한 if/else가 복잡해지고 중첩되면 부담이 되고 가독성을 크게 해칠 수 있습니다. 쉽게 복잡해질 수 있는 중첩의 수준, 즉, 코드의 depth가 깊어지는 것을 막을 수 있는 방법으로 Guard Clause를 고려해 볼 수 있습니다. 이 Code Clause를 통해 중첩된 조건문을 평탄화(flattening)시켜 코드의 가독성을 향상시킬 수 있습니다.

일단 Guard가 뭔지 알아보자

“In computer programming, a guard is a boolean expression that must evaluate to true if the program execution is to continue in the branch in question. Regardless of which programming language is used, guard code or a guard clause is a check of integrity preconditions used to avoid errors during execution.” — Wikipedia

쉽게 생각하면, null 체크와 같이 조건분기에서 계속 실행되기 위해 true여야 하는 boolean expression을 의미합니다.

Concept

다음과 같은 기존의 코드가 있습니다.

1
2
3
if (guard) {
...
}

이 코드를 not guard의 조건으로 바꾸고 조기에 return합니다 (return early). 그 다음, 기존의 guard 조건을 가진 분기의 코드 블락을 아래와 같이 not guard이후에 작성하면 됩니다. 이제 위의 분기 코드를 아래와 같이 표현할 수 있습니다.

1
2
3
4
if(not guard) {
return;
}
...

여기까지만 보면 오히려 멀쩡한 조건문에 not을 씌워 가독성을 해치는 것으로 보일 수 있습니다. 하지만 실제로 중첩 조건문에서 적용하여 중첩 조건문을 평탄화 하여 코드의 깊이를 줄이는 것을 보여드리겠습니다.

중첩 조건문에 적용

예시로 아래와 같이 token을 인증하는 가상의 코드가 있다고 했을 때, user가 존재 하는지 확인하고 해당 token을 가져와 존재하는지 확인한 후, tokenpurposereset인지를 확인해서 이경우 true를 반환합니다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
public async verifyToken(email: string, token: string, purpose: string) {
const user = await UserService.getUserByEmail(email);

if(user) {
const token = await user.getToken({...});

if (token) {
if(token.purpose === 'reset') {
return true;
} else {
return false;
}
} else {
return false;
}
}
}

이런 간단한 코드의 경우에도 이미 중첩 조건문이 3번 중첩됩니다. 코드가 더 복잡해지면 쉽게 코드의 depth가 깊어져 가독성을 해치고 코드를 이해하는데 어려움을 겪게 됩니다. 이 코드에서는 if(user), if(token) 그리고 if(token.purpose === 'reset')Guard가 됩니다.

Guard Clause 적용

앞서 말한대로 코드를 refactor해보도록 하겠습니다. Guard조건들을 Not Guard로 바꾸고 early return해줍니다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
public async verifyToken(email: string, token: string, purpose: string) {
const user = await UserService.getUserByEmail(email);

// 아래의 조건문 이후로, user가 존재한다는 것이 보장됩니다.
if(!user) {
return false;
}

const token = await user.getToken({...});
if (!token || token.purpose !== 'reset') {
return false;
}

// 여기까지 온 경우, true가 반환되기 위한 조건을 모두 갖췄다고 볼 수 있습니다.
return true;
}

Guard Clause를 토대로 refactor를 마친 코드를 보면 중첩된 조건문이 사라진건 알 수 있습니다. 이렇게 복잡한 조건문을 쉽고 가독성 높은 코드로 풀어낼 수 있는 Guard Clause에 대해 알아 봤습니다.