Abseil Tip 76 absl::Status 사용하기

Tip of the Week #76: absl::Status 사용하기

작성자: Titus Winters
최초 작성일: 2014년 5월 4일
업데이트: 2020년 2월 6일
원문 링크: abseil.io/tips/76


absl::Status를 사용해야 하는 이유와 사용 시 유의점

absl::Status를 언제, 어떻게 사용해야 하는지 궁금한 분들을 위해 몇 가지 이유와 고려 사항을 소개합니다.


의도를 전달하고 호출자에게 에러 처리를 강제하기

Status를 사용하면 호출자가 에러 가능성을 처리하도록 강제할 수 있습니다.
2013년 6월 이후, Status 객체를 반환하는 함수는 단순히 무시될 수 없습니다.
다음 코드는 컴파일 오류를 발생시킵니다:

absl::Status Foo();

void CallFoo1() {
  Foo();  // 오류 발생
}

반면, 아래 코드는 문제가 없습니다:

void CallFoo2() {
  Foo().IgnoreError();
}

void CallFoo3() {
  if (!status.ok()) std::abort();
}

void CallFoo4() {
  absl::Status status = Foo();
  if (!status.ok()) LOG(ERROR) << status;
}

IgnoreError()는 허용될까요?

Status를 무시하지 못하게 하는 컴파일러 검사를 도입했으면서, 왜 IgnoreError()를 제공할까요?
이유는 코드 리뷰를 쉽게 하기 위해서입니다.

예를 들어 CallFoo2()를 보면, 코드 리뷰어는 “이 함수는 에러가 발생할 수 있지만, 작성자가 이를 무시해도 괜찮다고 판단했네. 과연 괜찮을까?”라는 의문을 갖게 됩니다.
반면, CallFoo1()에서는 이런 의문이 생기지 않으므로 실수로 에러를 무시할 가능성이 더 높아집니다.


호출자가 에러를 처리할 수 있도록 유연성을 제공하기

Status는 작성한 코드에서 에러를 처리하는 적절한 방법이 명확하지 않을 때 유용합니다. 대신, Status를 반환하고 호출자가 더 적절한 맥락에서 에러를 처리할 수 있도록 합니다.

예시: 로깅

로깅을 사용하는 경우, 성능에 영향을 줄 수 있습니다. 예를 들어, 인프라 코드를 작성할 때, 함수가 빠르게 반복 호출되는 상황에서는 단순한 LOG(INFO) 호출조차 과도한 비용이 될 수 있습니다.
또는, 호출자가 특정 호출의 성공 여부에 크게 신경 쓰지 않을 수도 있으며, 과도한 로그는 방해가 될 수 있습니다.

Status를 반환하는 함수는 실패 이유를 설명하기 위해 LOG를 사용하지 않아도 됩니다. 대신 실패 코드와 에러 메시지를 반환하고, 호출자가 상황에 따라 적절히 대응하도록 맡길 수 있습니다.


absl::Status는 예외(Exception)를 다시 구현한 것일까요?

Google의 스타일 가이드는 예외 사용을 금지하며, 이는 가장 자주 언급되는 규칙 중 하나입니다.
겉으로 보기에는 absl::Status가 단순한 예외 대체 기법처럼 보일 수 있습니다. 하지만 absl::Status는 예외와 몇 가지 중요한 차이점이 있습니다:

  1. 명시적 처리 요구
    absl::Status는 예외처럼 스택을 따라 암묵적으로 전달되지 않습니다.
    대신, 엔지니어가 에러를 어떻게 처리할지 명시적으로 결정하도록 강제합니다.

  2. 컴파일러 검증
    absl::Status를 반환하면, 에러 처리 방식을 컴파일 가능한 코드로 문서화합니다.

  3. 성능
    absl::Status를 사용한 에러 반환은 예외를 던지고 잡는 것보다 압도적으로 빠릅니다.

이러한 특징은 코드를 작성할 때는 다소 번거로워 보일 수 있지만, 코드를 읽는 모든 사람과 Google 전체에는 순 이익이 됩니다.


결론

에러 처리는 실수하기 쉬운 영역입니다. 특히 에러는 본질적으로 엣지 케이스에서 발생하기 때문입니다.
Status 같은 유틸리티는 API 경계를 넘나드는 일관된 에러 처리를 가능하게 하며, 프로젝트와 프로세스, 그리고 언어 전반에서 에러 처리와 관련된 문제를 줄이는 데 기여합니다.

실패 가능성을 표현해야 하는 인터페이스를 설계할 때, 특별한 이유가 없다면 Status를 사용하세요.