0w0

조기 리턴을 적자

자기소개

미디어 엔진 이와모토입니다. 졸업하고 10년 정도 메이커 사내 SE로 COBOL이랑 여러 언어 쓰며 사내의 여러 시스템에 관련했고, 여러 길을 돌아다니다 미디어 엔진에 들어왔습니다. 상당히 버그 만드는 재능이 있었기에 수정하기 쉬운 코드를 쓰는 방법을 배웠습니다.

들어가며

많은 분이 조기 return 혹은 가드라 일컬리는 코딩 기술에 대해 알 것입니다. 읽기 쉬운 코드를 쓰는 사람가 모두 조기 리턴을 사용하는 것은 아니지만, 필요한 부분에는 반드시 사용하고 있습니다.

초보 간혹 베테랑 중에서도 이를 활용하지 못하는 경우도 있습니다. 중요한 것이라 생각하므로 아직 익숙하지 않은 분을 위해서 적겠습니다.

조기 리턴이란

간단히 말하면 return를 사용해서 if 등으로 생기는 네스트를 줄이는 기술입니다. 예를 보는게 빠르겠죠

조기 리턴 예시

유저가 시스템을 이용 가능한가 알아보는 함수 isActiveUser가 있습니다.

적용 전

user에 이용 개시일과 이용기간 혹은 정진 플랜이 있다 했을 때,

아래와 같은 코드가 됩니다.

javascript 예시를 보면

(조건식은 개선 후를 참조하면 되므로 넘어가셔도 됩니다.)

const today = new Date(2021, 10, 9);

function isActiveUser(user) {
  if (user != null) {
    if (
      user.startDate <= today &&
      (user.endDate == null || today <= user.endDate)
    ) {
      if (user.stopped) {
        return false;
      } else {
        return true;
      }
    } else {
      return false;
    }
  } else {
    return false;
  }
} // isActiveUser -> 15행
const startDate = new Date(today);
startDate.setMonth(startDate.getMonth() - 1);

const user = {
  name: 'iwa',
  startDate: startDate,
  endDate: null,
  stopped: false,
};

console.log(isActiveUser(user)); // -> true

조건식을 보며, 어떠한 조건이면 시스템을 이용가능한가 알기 어렵습니다.

적용 후

function isActiveUser(user) {
  if (user == null) {
    return false;
  }

  if (today < user.startDate) {
    return false;
  }

  if (user.endDate != null && user.endDate <= today) {
    return false;
  }

  if (user.stopped) {
    return false;
  }

  return true;
} // isActiveUser -> 18행

이러면 조건을 어렵지 않게 읽을 수 있습니다.

(행은 조금 늘었습니다만...)

쓰는 방법 노하우

예시는 진리값으로 반환했지만, 더 복잡한 로직일 때도 같이 사용할 수 있습니다.

이를 적용한다면 이렇게 됩니다

(return 값을 바꾸는 메서드는 쓰지 않았습니다.)

function isActiveUser(user) {
  if (user == null) return false;
  if (today < user.startDate) return false;
  if (terminated(user)) return false;
  if (user.stopped) return false;

  return true;
} // isActiveUser -> 8행

function terminated(user) {
  if (user.endDate == null) return false;
  if (today < user.endDate) return false;

  return true;
} // terminated -> 6행
// 15행 = 8행 + 1행 + 6행

이점

조기 리턴하면 네스트가 적어지는 것 외에도 다양한 이점이 있습니다

or이나 and

조기 리턴하면 네스트가 적어지는 것 외에도 다양한 이점이 있습니다

처리가 가벼워진다:

로직 정리하기 쉬워지는 면도 있지만 남은 로직을 실행하지 전에 로직에서 빠져나올 수 있습니다. 루프를 사용하는 처리 등에는 특히 의미있습니다.

행수가 적어져 보기 좋다:

예시에서는 리팩토링하며 행이 조금 늘었지만, 일반적으로 조기 리턴을 쓰면 행이 감소합니다.

행수로 읽기 편함을 측정하는 것이 좋아 할 수는 없지만, 엄청 긴 스크롤이 필요한 메서드보다 스크롤하지 않아도 읽히는 코드가 좋습니다. 디버그가 간단해지는 것이 효과 중에서도 하나입니다.

테스트 쓰기 쉽다:

복합적인 조건이라면 조합해서 테스트하지 않으면 안되지만 조기 리턴으로 쓴 소스는 뒤에서부터 순서대로 조건을 망라하면 알기 쉽게 쓸 수 있습니다.

조건 망라가 간단하게 테스트 커버리지를 높혀줍니다.(테스트는 정상 처리부터 적은 쪽이 알기 쉽습니다. 언젠가 다루고 싶은 주제입니다.)

조건 추가하기 쉽다:

적절한 부분에 if를 넣는 것이므로 조건 추가가 간단합니다. 반대로 네스트 된 조건식의 적절한 부분을 찾는건 상당히 고된 일이죠.

메서드 목적이 명확해진다:

조기 리턴을 사용할 수 없는 경우 대부분이 메서드가 분할이 제대로 되지 않은 경우입니다.

메서드 분할 방법은 여럿있지만, 조기 리턴을 사용하기 위해서는 반환값이 명확해질 필요가 있습니다.

반환값이 명확하면, 메서드의 목적도 명확해집니다.

코딩 기본 실력 상승:

메서드 분할은 코딩의 기본입니다.

메서드 목적의 명확해지면 적절한 명명을 할 수 있습니다.

또 객체 지향 프로그래밍 이점을 누리기 위해서는 메서드 분할 능력이 필요로합니다.

여기엔 이론이 있을지도 모르지만, 개인적으로는 재귀처리도 조기 리턴을 사용않고 쓰는 것은 어렵습니다.

이론 / 반론

코딩 스타일에 관련한 주제는 반발이 많습니다. 하지만 실제로 하다보면 납득해주는 경우가 대부분입니다.

여기까지 들은 이론/반론을 나열해보겠습니다.

"한 곳에서만 쓰는 메서드를 정의하면 보기 불편하다":

메서드 분할이 싫은 분의 의견입니다.

메서드 분할에 익숙하지 않을 것이라 사려됩니다.

또한 메서드명과 반환값이 구비되어 있지 않은 프로그램을 자주 보신 분이라 생각합니다.

"return이 여럿 있으면 보기 불편하다":

네스트가 깊은 경우는 조기 리턴을 사용하지 않은 것이 괜찮다 봅니다.

네스트가 깊다면 return가 하나든 여럿이든 보기 불편하죠.

여러 곳에 return이 있는 조기 리턴으로 리팩토링해보세요.

제대로 리팩토링(2단계까지 네스트 ㅇㅋ)했다면 전보다 보기 쉬운 코드가 될 것입니다

"조기리턴 하지 않아도 좋게 사전에 인수 체크해야한다":

이런 방법도 괜찮다 생각합니다.

다만 이는 주제와 다른 이야기입니다.

조기 리턴을 작성하고 나서 그것을 사전 체크로 바꿔서 비교한다면 이해하기 쉽겠지요

"이상처리보다 정상처리를 먼저 작성하고 싶어":

굉장히 동감합니다. 다만, 이를 위해 네스트해서 작성하면 조건 결과가 멀어져서 종료 이상처리가 굉장히 읽기 불편해집니다.

이 단점은 정상처리의 읽기 편함 장점을 덮어버리는 경우가 많습니다.

또 결국 if 조건이 정상처리 앞에 남습니다.

메서드 추출을 구사하며 조건을 포함해 1행으로 마무리 짓고 싶습니다.

"돌아가고 있는 코드는 건들면 안 된다":

이것도 아마 다른 주제겠지만, 코드가 잘 돌아가고 있음에도 변경이 있을 때마다 여러 개의 버그를 뱉지않습니까?

조기 리턴과 메서드 추출로 리팩터링하면 잠재 버그를 평탄화해주므로 문제 재발 방지 경험이 여럿 있습니다.

"리소스의 노출을 일으킨다":

이런 경우에는 주의가 필요합니다. 다행히 그런 경우는 대부분 없으므로 조기 리턴 사용합시다.

또 언어에 따라 try ~ final 같이 리소스를 사용하기 위한 좋은 작성 방법도 있습니다.

추상 클래스나 함수 넘기기 등의 테크닉도 존재합니다.

이를 사용하면 조기 리턴을 사용해도 리소스 노출을 일으키지 않습니다.

"코딩 규칙이 정해져있어서...":

어쩔 수 없습니다. 다른 직장에서 다른 경우였지만 저 또한 비슷한 경험이 있습니다.

규칙 결정권자에게 이를 말해도 설득 불가능 했습니다.

(제 경우에는 결정권자가 누구인지, 어떤 단계에 있는지도 불명확했습니다.)

"구조화 프로그래밍에서는 중간 리턴은 금지되어있다":

완전한 오해입니다.

return이 존재하지 않은 언어에서의 문제인데, 서브루틴 마지막 goto를 사용하는건 서브루틴 독립성을 보장하는 테크닉입니다.

현재는 이럴 걱정 아예하지 않아도 됩니다.

맺으면

어찌되었든, 때에 따라 팀마다 정답이 있기마련이므로, 어느 정도는 테크닉의 이해정도로 받으면 좋겠습니다.

마지막으로 저희 회사는 엔지니어, 디자이너 등 여러 포지션을 적극적으로 채용중입니다!

저희 회사 소개 페이지가 있으니 부디 한 번 확인해주세요!

https://mediaengine.notion.site/ba128c5708fc480198f5d8c9440a7062