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

Ruby의 begin과 rescue 키워드로 예외(Exception) 처리하기

Ruby 개발을 하다 보면 에러 메시지를 자주 마주하게 됩니다. 기술적인 용어로 이를 예외(exception)라고 부릅니다.

예외는 크게 두 가지로 나눌 수 있습니다. 파일이 어떤 때는 존재하고 어떤 때는 없는 경우처럼 예상 가능한 예외, 그리고 API가 일시적으로 제한되는 등 예측하기 어려운 예외입니다.

이번 글에서는 이러한 예상 가능한 에러를 관리하는 방법을 알아보겠습니다.

어떻게요?

Ruby의 beginrescue, 바로 에러 상황을 처리하기 위해 사용하는 두 가지 핵심 키워드를 소개합니다.

예외 처리란 무엇인가?

먼저 한 가지 이해해야 할 것이 있습니다.

Ruby 프로그램은 실행 중 여러 지점에서 에러를 일으킬 수 있습니다.

대표적인 예시는 다음과 같습니다:

  • 존재하지 않는 파일을 읽으려고 시도할 때
  • 숫자를 0으로 나눌 때
  • 사용 중인 웹 서버의 SSL 인증서가 만료되었을 때

에러가 발생하면 Ruby는 곧바로 프로그램이 종료되지 않습니다!

에러로부터 복구할 기회가 주어집니다. 이것을 바로 "예외 처리(exception handling)"라고 부릅니다.

Ruby는 코드에서 에러 복구를 구현할 수 있도록 몇 가지 키워드를 제공하며, 그중 대표적인 것이 beginrescue입니다.

Ruby 예외 처리 기본 방법

그럼 예외는 어떻게 처리할까요?

예외를 발생시키는 코드를 begin / rescue 블록으로 감싸면 됩니다.

동작 방식은 다음과 같습니다.

첫 번째 영역(begin)에는 실행할 코드, 즉 예외가 발생할 가능성이 있는 코드를 작성합니다.

예제:

begin
  IO.sysopen('/dev/null')
rescue
  # ...
end

여기서는 sysopen으로 파일을 열려고 시도합니다. 파일을 열 수 없다면 예외가 발생합니다.

바로 이럴 때 rescue 키워드가 빛을 발합니다!

rescue를 사용하면 예외가 발생했을 때 어떤 동작을 할지 직접 지정할 수 있습니다. 즉, 실패 상황에 대한 대응을 개발자가 통제할 수 있게 되는 것입니다.

예제:

begin
  IO.sysopen('/dev/null')
rescue
  puts "Can't open IO device."
end

일반적으로는 에러를 로그로 남기거나, 기본값(default value)을 제공하는 식으로 대응합니다.

주의하세요. 절대로 에러를 무시하지 마세요.

여러 예외를 한 번에 처리하기

rescue에는 선택적 인자(optional argument)를 전달할 수 있다는 점을 알아두세요.

이 인자가 무엇이냐면, 바로 처리하고자 하는 예외 클래스(exception class)입니다.

어떤 예외 클래스를 지정할지는 실행하는 코드에 따라 달라집니다.

IO 작업의 경우:

  • 파일이 없을 때는 Errno::ENOENT
  • 권한 오류일 때는 Errno::EACCES

가장 좋은 점은 무엇일까요?

하나의 begin/rescue 블록에서 여러 예외를 각각 다르게 처리할 수 있습니다.

이렇게요:

begin
  IO.sysopen('/dev/null')
rescue Errno::ENOENT
  puts "File not found."
rescue Errno::EACCES
  puts "Insufficient permissions, not allowed to open file."
end

반대로 여러 예외에 대해 동일한 동작을 수행하고 싶다면...

이렇게 작성할 수 있습니다:

begin
  IO.sysopen('/dev/null')
rescue Errno::ENOENT, Errno::EACCES
  puts "There was an error opening the file."
end

계속해서 더 알아보겠습니다!

메서드와 블록 안에서 예외 처리하기

항상 begin 키워드를 사용해야 하는 것은 아닙니다.

begin을 생략할 수 있는 경우가 있습니다.

어디서냐고요?

바로 메서드와 블록 내부입니다.

메서드 예제:

def get_null_device
  IO.sysopen('/dev/null')
rescue Errno::ENOENT
  puts "Can't open IO device."
end

메서드 정의 자체가 begin의 역할을 해주기 때문에 생략할 수 있습니다.

블록에서도 마찬가지로 적용됩니다.

블록 예제:

["a.txt", "b.txt", "c.txt"].map do |f|
  IO.sysopen(f)
rescue Errno::ENOENT
  puts "Can't open IO device: #{f}."
end

begin 없이 rescue를 사용하는 방법이 하나 더 있습니다.

어떻게 동작하는지 살펴보겠습니다.

인라인 rescue와 그 위험성

rescue는 한 줄(inline) 형태로도 사용할 수 있습니다.

드물지만 특정 상황에서는 이런 형태의 예외 처리가 유용할 수 있습니다.

예제:

["a.txt", "b.txt", "c.txt"].select { |f| File.open(f) rescue nil }.map(&:size)

이 코드는 실제로 존재하는 파일만 열고, 존재하지 않는 파일은 무시합니다.

그 결과, 존재하는 파일들의 크기만 얻을 수 있습니다.

예외가 발생하지 않도록 말이죠.

왜 이런 방식을 쓸까요?

코드를 한 줄로 깔끔하게 유지할 수 있기 때문입니다. 하지만 장점은 그게 전부입니다.

사실 이 형태의 rescue에는 숨겨진 위험(hidden danger)이 있습니다. StandardError를 상속하는 거의 모든 예외를 잡아버리기 때문입니다.

왜 좋지 않을까요?

광범위한 예외를 한꺼번에 처리하기보다는, 필요한 구체적인 예외만 처리하는 것이 모범 사례이기 때문입니다.

그렇게 해야 에러가 스스로에게 숨겨지는 것을 막을 수 있습니다!

숨겨진 에러는 온갖 기묘한 동작과 디버깅하기 어려운 문제로 이어질 수 있습니다.

마무리 정리

이번 글에서는 Ruby에서 발생하는 에러, 기본적인 예외 처리 방법, 그리고 begin / rescue 키워드의 사용법에 대해 알아보았습니다.

  • begin 블록에 예외가 발생할 수 있는 코드를 작성한다
  • rescue로 특정 예외 클래스를 지정해 대응 로직을 작성한다
  • 메서드와 블록 안에서는 begin을 생략할 수 있다
  • 인라인 rescue는 편리하지만 광범위한 예외를 잡아 위험할 수 있다

이 글이 도움이 되었다면 주변에 공유해 주세요 🙂

읽어주셔서 감사합니다!