Ruby 사용자 정의 오류 클래스 : 메시지 속성 상속


95

사용자 지정 예외 클래스에 대한 정보를 많이 찾을 수없는 것 같습니다.

내가 아는 것

사용자 정의 오류 클래스를 선언하고에서 상속 StandardError하도록 할 수 있으므로 rescued 가 될 수 있습니다 .

class MyCustomError < StandardError
end

이렇게하면 다음을 사용하여 올릴 수 있습니다.

raise MyCustomError, "A message"

나중에 구조 할 때 메시지를받습니다.

rescue MyCustomError => e
  puts e.message # => "A message"

내가 모르는 것

예외에 몇 가지 사용자 지정 필드를 제공하고 싶지만 message부모 클래스에서 특성 을 상속하고 싶습니다 . 예외 클래스의 인스턴스 변수가 아닌 이 주제 를 읽었 @message으므로 상속이 작동하지 않을까 걱정됩니다.

누구든지 이것에 대해 더 자세한 정보를 줄 수 있습니까? object속성이 있는 사용자 지정 오류 클래스를 어떻게 구현 합니까? 다음이 맞습니까?

class MyCustomError < StandardError
  attr_reader :object
  def initialize(message, object)
    super(message)
    @object = object
  end
end

그리고:

raise MyCustomError.new(anObject), "A message"

얻으려면 :

rescue MyCustomError => e
  puts e.message # => "A message"
  puts e.object # => anObject

작동 할 것인가, 작동한다면 이것이 올바른 일을하는 방법인가?


3
하지 마십시오 rescue Exception => e. rescue => e에서 확장되는 기본값보다 광범위하며 StandardErrorCtrl + C를 포함한 모든 것을 포착합니다. 나는 할 것이다 rescue MyCustomError => e.
Ryan Taylor

1
@RyanTaylor 나는 더 적절한 접근 방식을 위해 내 질문을 편집했습니다.
MarioDS 2014

답변:


121

raise 이미 메시지를 설정하므로 생성자에 전달할 필요가 없습니다.

class MyCustomError < StandardError
  attr_reader :object

  def initialize(object)
    @object = object
  end
end

begin
  raise MyCustomError.new("an object"), "a message"
rescue MyCustomError => e
  puts e.message # => "a message"
  puts e.object # => "an object"
end

나는 대체 한 rescue Exception으로 rescue MyCustomError, 참조 는`구조 예외 => e` 루비에 나쁜 스타일 왜? .


전체 구문을 보여 주셨 기 때문에 답변을 수락하겠습니다. 감사!
MarioDS 2013

1
여기서 우리는하는데 rescue Exception왜 안 rescue MyCustomError되는가?
Dfr

참고로 첫 번째 인수 인 object가 옵션이고가 raise MyCustomError, "a message"없으면 new"a message"가 설정되지 않습니다.
hiroshi

어떻게 든 사용자 지정 예외 클래스에서 발생한 메시지를 얻을 수있는 방법이 있습니까?
CyberMew

@CyberMew 무슨 뜻이야? 뭐하고 싶어?
Stefan

10

Exception다른 모든 오류가 상속 하는의 루비 코어 문서 에서 다음과 같이 설명합니다.#message

예외를 호출 한 결과를 반환합니다. 일반적으로 이것은 예외의 메시지 또는 이름을 반환합니다. to_str 메소드를 제공하면 예외가 문자열이 예상되는 곳에 사용되는 데 동의합니다.

http://ruby-doc.org/core-1.9.3/Exception.html#method-i-message

재정의 to_s/ to_str또는 이니셜 라이저를 선택합니다. 다음은 외부 서비스가 어떤 작업을 수행하지 못했을 때 대부분 사람이 읽을 수있는 방식으로 알고 싶은 예입니다.

참고 : 아래의 두 번째 전략 demodualize은 약간 복잡 할 수 있으므로 예외에서 수행하는 것이 현명하지 않을 수있는 과 같은 rails pretty string 메서드를 사용합니다 . 필요한 경우 메서드 서명에 더 많은 인수를 추가 할 수도 있습니다.

#to_str이 아닌 #to_s 전략을 재정의하면 다르게 작동합니다.

module ExternalService

  class FailedCRUDError < ::StandardError
    def to_s
      'failed to crud with external service'
    end
  end

  class FailedToCreateError < FailedCRUDError; end
  class FailedToReadError < FailedCRUDError; end
  class FailedToUpdateError < FailedCRUDError; end
  class FailedToDeleteError < FailedCRUDError; end
end

콘솔 출력

begin; raise ExternalService::FailedToCreateError; rescue => e; e.message; end
# => "failed to crud with external service"

begin; raise ExternalService::FailedToCreateError, 'custom message'; rescue => e; e.message; end
# => "failed to crud with external service"

begin; raise ExternalService::FailedToCreateError.new('custom message'); rescue => e; e.message; end
# => "failed to crud with external service"

raise ExternalService::FailedToCreateError
# ExternalService::FailedToCreateError: failed to crud with external service

#initialize 전략 재정의

이것은 레일에서 사용한 구현과 가장 가까운 전략입니다. 전술 한 바와 같이, 그 용도 demodualize, underscorehumanize ActiveSupport방법. 그러나 이것은 이전 전략 에서처럼 쉽게 제거 할 수 있습니다.

module ExternalService
  class FailedCRUDError < ::StandardError
    def initialize(service_model=nil)
      super("#{self.class.name.demodulize.underscore.humanize} using #{service_model.class}")
    end
  end

  class FailedToCreateError < FailedCRUDError; end
  class FailedToReadError < FailedCRUDError; end
  class FailedToUpdateError < FailedCRUDError; end
  class FailedToDeleteError < FailedCRUDError; end
end

콘솔 출력

begin; raise ExternalService::FailedToCreateError; rescue => e; e.message; end
# => "Failed to create error using NilClass"

begin; raise ExternalService::FailedToCreateError, Object.new; rescue => e; e.message; end
# => "Failed to create error using Object"

begin; raise ExternalService::FailedToCreateError.new(Object.new); rescue => e; e.message; end
# => "Failed to create error using Object"

raise ExternalService::FailedCRUDError
# ExternalService::FailedCRUDError: Failed crud error using NilClass

raise ExternalService::FailedCRUDError.new(Object.new)
# RuntimeError: ExternalService::FailedCRUDError using Object

데모 도구

이것은 위 구현의 구조 및 메시징을 보여주는 데모입니다. 예외를 발생시키는 클래스는 Cloudinary에 대한 가짜 API입니다. 위의 전략 중 하나를 Rails 콘솔에 덤프 한 다음이를 따릅니다.

require 'rails' # only needed for second strategy 

module ExternalService
  class FailedCRUDError < ::StandardError
    def initialize(service_model=nil)
      @service_model = service_model
      super("#{self.class.name.demodulize.underscore.humanize} using #{@service_model.class}")
    end
  end

  class FailedToCreateError < FailedCRUDError; end
  class FailedToReadError < FailedCRUDError; end
  class FailedToUpdateError < FailedCRUDError; end
  class FailedToDeleteError < FailedCRUDError; end
end

# Stub service representing 3rd party cloud storage
class Cloudinary

  def initialize(*error_args)
    @error_args = error_args.flatten
  end

  def create_read_update_or_delete
    begin
      try_and_fail
    rescue ExternalService::FailedCRUDError => e
      e.message
    end
  end

  private def try_and_fail
    raise *@error_args
  end
end

errors_map = [
  # Without an arg
  ExternalService::FailedCRUDError,
  ExternalService::FailedToCreateError,
  ExternalService::FailedToReadError,
  ExternalService::FailedToUpdateError,
  ExternalService::FailedToDeleteError,
  # Instantiated without an arg
  ExternalService::FailedCRUDError.new,
  ExternalService::FailedToCreateError.new,
  ExternalService::FailedToReadError.new,
  ExternalService::FailedToUpdateError.new,
  ExternalService::FailedToDeleteError.new,
  # With an arg
  [ExternalService::FailedCRUDError, Object.new],
  [ExternalService::FailedToCreateError, Object.new],
  [ExternalService::FailedToReadError, Object.new],
  [ExternalService::FailedToUpdateError, Object.new],
  [ExternalService::FailedToDeleteError, Object.new],
  # Instantiated with an arg
  ExternalService::FailedCRUDError.new(Object.new),
  ExternalService::FailedToCreateError.new(Object.new),
  ExternalService::FailedToReadError.new(Object.new),
  ExternalService::FailedToUpdateError.new(Object.new),
  ExternalService::FailedToDeleteError.new(Object.new),
].inject({}) do |errors, args|
  begin 
    errors.merge!( args => Cloudinary.new(args).create_read_update_or_delete)
  rescue => e
    binding.pry
  end
end

if defined?(pp) || require('pp')
  pp errors_map
else
  errors_map.each{ |set| puts set.inspect }
end

6

당신의 생각은 옳지 만 당신이 그것을 부르는 방식은 잘못되었습니다. 그것은해야한다

raise MyCustomError.new(an_object, "A message")

좋아, 나는 당신이 준 메시지가 raise키워드 또는 다른 것에 대한 두 번째 매개 변수라고 생각했습니다 .
MarioDS

initialize두 개의 인수를 사용하도록 재정의 했습니다. new인수를에 전달합니다 initialize.
sawa

또는 괄호를 생략 할 수 있습니다.
sawa

나는 그 부분을 이해하지만 내 질문에서 링크 한 주제의 포스터는 다음과 같이합니다 raise(BillRowError.new(:roamingcalls, @index), "Roaming Calls field missing"). 그래서 그는 raise두 개의 매개 변수 를 가지고 호출합니다 : 새로운 BillRowError객체와 그의 메시지. 나는 단지 구문에 의해 혼란스러워합니다 ... 다른 튜토리얼에서는 항상 다음과 같이 보입니다 :raise Error, message
MarioDS

1
문제는 얼마나 많은 인수를 전달 하느냐가 아닙니다 raise. 그것은 매우 유연합니다. 문제는 당신 initialize이 두 가지 주장을 받아들이고 하나만 주도록 정의했다는 것 입니다. 귀하의 예를보십시오. BillRowError.new(:roamingcalls, @index)두 개의 인수가 주어집니다.
sawa

5

비슷한 일을하고 싶었습니다. 객체를 #new에 전달하고 전달 된 객체의 일부 처리를 기반으로 메시지를 설정하고 싶었습니다. 다음은 작동합니다.

class FooError < StandardError
  attr_accessor :message # this is critical!
  def initialize(stuff)
    @message = stuff.reverse
  end
end

begin
  raise FooError.new("!dlroW olleH")
rescue FooError => e
  puts e.message #=> Hello World!
end

선언 attr_accessor :message하지 않으면 작동하지 않습니다. OP의 문제를 해결하기 위해 메시지를 추가 인수로 전달하고 원하는 것을 저장할 수도 있습니다. 중요한 부분은 #message보다 우선하는 것으로 보입니다.

당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.