คลาสข้อผิดพลาดที่กำหนดเองของ 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ที่ขยายออกไปStandardErrorและจับทุกอย่างรวมถึง Ctrl + C rescue MyCustomError => eฉันทำ
Ryan Taylor

1
@RyanTaylor ฉันแก้ไขคำถามของฉันเพื่อแนวทางที่เหมาะสมยิ่งขึ้น
MarioDS

คำตอบ:


122

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ดูเหตุใดรูปแบบที่ไม่ถูกต้องในการ `Rescue Exception => e` ใน Ruby .


ฉันจะยอมรับคำตอบของคุณเพราะคุณแสดงไวยากรณ์ทั้งหมดให้ฉันดู ขอบคุณ!
MarioDS

1
ที่นี่เราทำrescue Exceptionแต่ทำไมไม่rescue MyCustomError?
ผบ.

FYI ถ้าอาร์กิวเมนต์แรกอ็อบเจกต์เป็นตัวเลือกและraise MyCustomError, "a message"ไม่มีnew"ข้อความ" จะไม่ถูกตั้งค่า
hiroshi

มีวิธีรับข้อความที่ยกขึ้นในคลาสข้อยกเว้นที่กำหนดเองของเราหรือไม่?
CyberMew

@CyberMew หมายความว่าไง? เธออยากทำอะไรล่ะ?
Stefan

10

ระบุสิ่งที่เอกสารหลักของทับทิมExceptionซึ่งข้อผิดพลาดอื่น ๆ ทั้งหมดได้รับการสืบทอดมาระบุ#message

ส่งคืนผลลัพธ์ของการเรียกใช้ข้อยกเว้น to_s โดยปกติจะส่งคืนข้อความหรือชื่อของข้อยกเว้น ด้วยการจัดหาเมธอด to_str ข้อยกเว้นตกลงที่จะใช้ในกรณีที่คาดว่าจะใช้สตริง

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

ฉันจะเลือกกำหนดนิยามใหม่ to_s / to_strหรือตัวเริ่มต้นใหม่ นี่คือตัวอย่างที่เราต้องการทราบโดยส่วนใหญ่มนุษย์สามารถอ่านได้เมื่อบริการภายนอกไม่สามารถทำบางสิ่งได้

หมายเหตุ: กลยุทธ์ที่สองด้านล่างใช้วิธีการร้อยเชือกแบบรางเช่นdemodualizeซึ่งอาจมีความซับซ้อนเล็กน้อยและอาจไม่ฉลาดที่จะทำในข้อยกเว้น คุณยังสามารถเพิ่มอาร์กิวเมนต์ให้กับลายเซ็นของวิธีการได้หากต้องการ

การลบล้างกลยุทธ์ #to_s ไม่ใช่ #to_str มันทำงานแตกต่างกัน

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 Strategy

นี่เป็นกลยุทธ์ที่ใกล้เคียงที่สุดกับการใช้งานที่ฉันเคยใช้ในราง ตามที่ระบุไว้ข้างต้นก็ใช้demodualize, underscoreและhumanize 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

เครื่องมือสาธิต

นี่คือการสาธิตเพื่อแสดงการช่วยเหลือและการส่งข้อความของการใช้งานข้างต้น คลาสที่เพิ่มข้อยกเว้นคือ API ปลอมไปยัง Cloudinary เพียงทิ้งหนึ่งในกลยุทธ์ข้างต้นลงในคอนโซลรางของคุณตามด้วยสิ่งนี้

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

ฉันอยากทำอะไรคล้าย ๆ กัน ฉันต้องการส่งวัตถุไปที่ # ใหม่และตั้งค่าข้อความตามการประมวลผลของวัตถุที่ส่งผ่าน งานต่อไปนี้

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 คุณสามารถส่งข้อความเป็นอาร์กิวเมนต์เพิ่มเติมและจัดเก็บอะไรก็ได้ที่คุณต้องการ ส่วนสำคัญดูเหมือนจะลบล้าง # ข้อความ

โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.