Computer >> 컴퓨터 >  >> 프로그램 작성 >> Ruby

Ruby에서 예외가 발생했을 때 다시 시도하는 방법

모든 오류가 치명적인 것은 아닙니다. 일부는 다시 시도해야 함을 나타냅니다. 다행히 Ruby는 "다시 시도"하기 쉽게 해주는 몇 가지 흥미로운 메커니즘을 제공합니다. 이 게시물에서는 이러한 메커니즘과 실제 세계에서 어떻게 작동하는지 살펴보겠습니다.

retry 소개

좋아 - 이것은 일종의 명백한 것이지만 그것이 존재한다는 것을 아는 경우에만 가능합니다. 개인적으로 나는 유쾌한 "재시도" 키워드에 대해 배우기 전에 Ruby 경력에 푹 빠져 있었습니다.

재시도는 Ruby의 예외 구조 시스템에 내장되어 있습니다. 아주 간단합니다. 복구 블록에서 "재시도"를 사용하면 복구된 코드 섹션이 다시 실행됩니다. 예를 들어 보겠습니다.

begin
  retries ||= 0
  puts "try ##{ retries }"
  raise "the roof"
rescue
  retry if (retries += 1) < 3
end

# ... outputs the following:
# try #0
# try #1
# try #2

여기서 주의할 사항이 몇 가지 있습니다.

  • 재시도가 호출되면 모두 시작과 구조 사이의 코드가 다시 실행됩니다. 확실히 그렇습니다 아님 "중단된 부분부터 다시 시작" 또는 이와 유사한 것입니다.

  • 재시도를 제한하는 메커니즘을 제공하지 않으면 무한 루프가 발생합니다.

  • 시작 및 복구 블록의 코드는 상위 범위의 동일한 재시도 변수에 액세스할 수 있습니다.

retry 문제

재시도는 훌륭하지만 몇 가지 제한 사항이 있습니다. 가장 중요한 것은 전체 시작 블록이 다시 실행된다는 것입니다. 그러나 때로는 그것이 이상적이지 않습니다.

예를 들어 단일 메서드 호출을 사용하여 Twitter, Facebook 및 기타 여러 사이트에 상태 업데이트를 게시할 수 있는 gem을 사용하고 있다고 상상해 보십시오. 다음과 같이 보일 수 있습니다.

SocialMedia.post_to_all("Zomg! I just ate the biggest hamburger")

# ...posts to Twitter API
# ...posts to Facebook API
# ...etc

API 중 하나가 응답하지 않으면 gem은 SocialMedia::TimeoutError를 발생시키고 중단합니다. 이 예외를 포착하고 다시 시도하면 재시도가 처음부터 다시 시작되기 때문에 중복 게시물이 생성됩니다.

begin
  SocialMedia.post_to_all("Zomg! I just ate the biggest hamburger")
rescue SocialMedia::TimeoutError
  retry
end

# ...posts to Twitter API
# facebook error
# ...posts to Twitter API
# facebook error
# ...posts to Twitter API
# and so on

우리가 gem에게 "페이스북을 건너뛰고 게시할 API 목록을 계속 아래로 내려가세요"라고 말할 수 있다면 좋지 않을까요?

다행스럽게도 Ruby를 사용하면 바로 이 작업을 수행할 수 있습니다.

참고:물론 이 문제에 대한 진정한 해결책은 소셜 미디어 라이브러리를 다시 설계하는 것입니다. 하지만 이것은 제가 보여드릴 기술의 유일한 사용 사례와는 거리가 멉니다.

구조 계속

계속하는 것은 사람들을 놀라게 하는 경향이 있습니다. 그러나 그것은 자주 사용되지 않고 약간 이상해 보이기 때문입니다. 하지만 기본 사항을 이해하면 정말 간단합니다.

연속은 비디오 게임에서처럼 코드의 '저장 지점'과 같습니다. 나가서 다른 작업을 수행한 다음 저장 지점으로 다시 이동할 수 있습니다. 그러면 모든 것이 원래대로 유지됩니다.

...그렇습니다. 완벽한 비유는 아니지만 일종의 효과가 있습니다. 몇 가지 코드를 살펴보겠습니다.

require "continuation"
counter = 0
continuation = callcc { |c| c } # define our savepoint
puts(counter += 1)
continuation.call(continuation) if counter < 5 # jump back to our savepoint

몇 가지 이상한 점을 눈치채셨을 수도 있습니다. 그것들을 살펴봅시다:

  • callcc 메소드를 사용하여 Continuation 객체를 생성합니다. 이에 대한 명확한 OO 구문은 없습니다.

  • 처음 continuation 변수가 할당되면 callcc의 반환 값으로 설정됩니다. 의 블록. 그렇기 때문에 블록이 있어야 합니다.

  • 저장점으로 돌아갈 때마다 continuation 변수는 call을 전달하는 인수에 할당됩니다. 방법. 그래서 이상한 모양의 continuation.call(continuation)을 사용합니다. 구문.

예외에 계속 추가

계속을 사용하여 skip를 추가할 것입니다. 모든 예외에 대한 방법. 아래 예는 작동 방식을 보여줍니다. 예외를 구할 때마다 skip를 호출할 수 있어야 합니다. , 예외를 발생시킨 코드가 발생하지 않은 것처럼 작동합니다.

begin
  raise "the roof"
  puts "The exception was ignored"
rescue => e
  e.skip
end

# ...outputs "The exception was ignored"

이렇게 하려면 몇 가지 죄를 범해야 합니다. Exception 그냥 수업입니다. 즉, skip를 추가하기 위해 돈을 벌 수 있습니다. 방법.

class Exception
  attr_accessor :continuation
  def skip
    continuation.call
  end
end

이제 continuation을 설정해야 합니다. 모든 예외에 대한 속성. raise 재정의할 수 있는 방법일 뿐입니다.

BTW, 아래 코드는 Advi의 뛰어난 슬라이드 데크인 Things You Didn't know about Exceptions에서 거의 그대로 가져온 것입니다. 이보다 더 나은 구현 방법을 생각할 수 없었습니다.

require 'continuation'
module StoreContinuationOnRaise
  def raise(*args)
    callcc do |continuation|
      begin
        super
      rescue Exception => e
        e.continuation = continuation
        super(e)
      end
    end
  end
end

class Object
  include StoreContinuationOnRaise
end

이제 skip를 호출할 수 있습니다. 모든 예외에 대한 메서드이며 예외가 발생하지 않은 것과 같습니다.