Computer >> 컴퓨터 >  >> 프로그래밍 >> Ruby

루비 Exception vs StandardError: 차이점은 무엇일까?

"루비에서는 절대 Exception을 rescue하지 마세요!"

한 번쯤 들어본 적 있으신가요? 아주 좋은 조언이지만, 이미 내용을 알고 있는 사람이 아니라면 다소 혼란스러울 수 있습니다. 이 문장이 정확히 무엇을 의미하는지 하나씩 살펴보겠습니다.

루비에서는 다음과 같이 예외를 처리(rescue)할 수 있다는 점은 이미 잘 알고 계실 겁니다:

begin
  do_something()
rescue => e
  puts e # e는 에러 정보를 담고 있는 예외 객체입니다.
end

또한 에러 클래스 이름을 지정해서 특정 에러만 골라서 처리할 수도 있습니다.

begin
  do_something()
rescue ActiveRecord::RecordNotFound => e
  puts e # RecordNotFound 예외 또는 이를 상속받는 클래스만 처리합니다.
end

루비의 모든 예외 타입은 사실 하나의 클래스입니다. 위 예제에서 ActiveRecord::RecordNotFound 역시 특정 규약을 따르는 클래스 이름일 뿐입니다.

여기서 중요한 점은 RecordNotFound를 rescue하면 해당 클래스를 상속받는 모든 예외까지 함께 처리된다는 것입니다.

Exception을 rescue하면 안 되는 이유

Exception을 rescue하는 것이 문제가 되는 이유는, 실제로 Exception을 상속받는 모든 예외를 처리해버리기 때문입니다. 그리고 그 범위는... 전부입니다!

이것이 문제가 되는 이유는 루비 내부적으로 사용되는 예외들이 존재하기 때문입니다. 이들은 여러분의 애플리케이션과는 아무런 관련이 없으며, 이를 삼켜버리면 심각한 부작용이 발생할 수 있습니다.

대표적인 몇 가지를 살펴보겠습니다:

  • SignalException::Interrupt - 이 예외를 rescue하면 control-c로 프로그램을 종료할 수 없게 됩니다.

  • ScriptError::SyntaxError - 문법 오류를 삼켜버리면 puts("Forgot something)처럼 잘못된 코드가 조용히 실패하게 됩니다.

  • NoMemoryError - 프로그램이 RAM을 모두 사용한 후에도 계속 실행되면 어떻게 될까요? 저도 알고 싶지 않네요.

begin
  do_something()
rescue Exception => e
  # 이렇게 하지 마세요. 모든 예외를 삼켜버립니다. 아무것도 통과하지 못합니다.
end

아마 시스템 레벨의 예외까지 삼키고 싶지는 않으실 겁니다. 여러분이 원하는 것은 애플리케이션 레벨의 에러, 즉 여러분의 코드로 인해 발생한 예외만 잡는 것입니다.

다행히도 이를 손쉽게 해결하는 방법이 있습니다.

대신 StandardError를 rescue하세요

여러분이 신경 써야 할 모든 예외는 StandardError를 상속받습니다. 개발자에게 익숙한 친구들입니다:

  • NoMethodError - 존재하지 않는 메서드를 호출하려 할 때 발생합니다.

  • TypeError - 1 + "" 같은 잘못된 연산으로 인해 발생합니다.

  • RuntimeError - 좋은 옛날 친구 RuntimeError를 잊으셨나요?

이런 종류의 에러를 처리하려면 StandardError를 rescue하면 됩니다. 다음과 같이 명시적으로 작성할 수 있습니다:

begin
  do_something()
rescue StandardError => e
  # 애플리케이션의 예외만 처리됩니다. SyntaxError 같은 예외는 그대로 통과합니다.
end

하지만 루비는 이 과정을 더욱 편리하게 만들어 두었습니다.

예외 클래스를 아예 지정하지 않으면, 루비는 자동으로 StandardError를 의미한다고 간주합니다. 따라서 아래 코드는 위 코드와 완전히 동일하게 동작합니다:

begin
  do_something()
rescue => e
  # StandardError를 rescue하는 것과 동일합니다.
end

커스텀 예외는 반드시 StandardError를 상속받아야 합니다

그렇다면 직접 커스텀 예외 클래스를 만들 때는 어떻게 해야 할까요?

항상 StandardError를 상속받아야 하며, 절대 Exception을 상속받으면 안 됩니다. Exception을 상속받으면 rescue의 기대 동작이 깨지기 때문입니다. 다른 개발자들은 모든 애플리케이션 레벨 에러를 처리하고 있다고 믿지만, 여러분의 커스텀 예외만 그냥 지나쳐 버리는 상황이 생깁니다.

class SomethingBad < StandardError
end

raise SomethingBad

루비의 예외 클래스 트리

루비의 예외는 클래스 계층 구조로 구현되어 있으므로, 전체 구조를 한눈에 확인해 보면 이해에 큰 도움이 됩니다. 아래는 루비 표준 라이브러리에 포함된 예외 클래스 목록입니다. Rails 같은 서드파티 젬(gem)들이 이 구조에 추가적인 예외 클래스들을 더하기도 하지만, 결국 모두 이 목록에 있는 클래스 중 하나를 상속받게 됩니다.

Exception
 NoMemoryError
 ScriptError
   LoadError
   NotImplementedError
   SyntaxError
 SignalException
   Interrupt
 StandardError
   ArgumentError
   IOError
     EOFError
   IndexError
   LocalJumpError
   NameError
     NoMethodError
   RangeError
     FloatDomainError
   RegexpError
   RuntimeError
   SecurityError
   SystemCallError
   SystemStackError
   ThreadError
   TypeError
   ZeroDivisionError
 SystemExit
 fatal