Computer >> คอมพิวเตอร์ >  >> การเขียนโปรแกรม >> Ruby

คู่มือเริ่มต้นสำหรับข้อยกเว้นใน Ruby

เมื่อวันก่อนฉันกำลังค้นหาคำแนะนำเกี่ยวกับข้อยกเว้น Ruby ที่เขียนขึ้นสำหรับผู้เริ่มต้น - ผู้ที่รู้ไวยากรณ์พื้นฐานของ Ruby แต่ไม่ค่อยแน่ใจว่าข้อยกเว้นคืออะไรหรือเหตุใดจึงมีประโยชน์ หาไม่เจอ เลยตัดสินใจไปเอง ฉันหวังว่าคุณพบว่ามีประโยชน์. หากมีประเด็นใดที่ทำให้สับสน โปรดทวีตหาฉันที่ @StarrHorne :)

ข้อยกเว้นคืออะไร

ข้อยกเว้นคือวิธีการจัดการกับเหตุการณ์ที่ไม่คาดคิดของ Ruby

หากคุณเคยพิมพ์ผิดในโค้ดของคุณ ทำให้โปรแกรมทำงานผิดพลาดโดยมีข้อความเช่น SyntaxError หรือ NoMethodError แล้วคุณจะได้เห็นข้อยกเว้นในการดำเนินการ

เมื่อคุณแจ้งข้อยกเว้นใน Ruby โลกจะหยุดทำงานและโปรแกรมของคุณก็เริ่มปิดตัวลง หากไม่มีอะไรหยุดกระบวนการ ในที่สุดโปรแกรมของคุณก็จะออกพร้อมกับข้อความแสดงข้อผิดพลาด

นี่คือตัวอย่าง ในโค้ดด้านล่าง เราพยายามหารด้วยศูนย์ เป็นไปไม่ได้ ดังนั้น Ruby จึงยกข้อยกเว้นที่เรียกว่า ZeroDivisionError . โปรแกรมปิดและพิมพ์ข้อความแสดงข้อผิดพลาด

1 / 0
# Program crashes and outputs: "ZeroDivisionError: divided by 0"

โปรแกรมหยุดทำงานมักจะทำให้ผู้ใช้ของเราโกรธ ปกติแล้วเราต้องการหยุดกระบวนการปิดระบบนี้ และตอบสนองต่อข้อผิดพลาดอย่างชาญฉลาด

ข้อยกเว้นนี้เรียกว่า "การช่วยเหลือ" "การจัดการ" หรือ "การจับ" พวกเขาทั้งหมดหมายถึงสิ่งเดียวกัน นี่คือวิธีที่คุณทำใน Ruby:

begin
  # Any exceptions in here... 
  1/0
rescue
  # ...will cause this code to run
  puts "Got an exception, but I'm responding intelligently!"
  do_something_intelligent()
end

# This program does not crash.
# Outputs: "Got an exception, but I'm responding intelligently!"

ข้อยกเว้นยังคงเกิดขึ้น แต่ก็ไม่ได้ทำให้โปรแกรมขัดข้องเพราะ "ได้รับการช่วยเหลือ" แทนที่จะออก Ruby จะรันโค้ดในบล็อกกู้ภัย ซึ่งจะพิมพ์ข้อความออกมา

นี่เป็นสิ่งที่ดี แต่มีข้อจำกัดใหญ่อยู่อย่างหนึ่ง มันบอกเราว่า "มีบางอย่างผิดพลาด" โดยไม่แจ้งให้เราทราบ อะไร ผิดพลาด

ข้อมูลทั้งหมดเกี่ยวกับสิ่งที่ผิดพลาดจะอยู่ในวัตถุข้อยกเว้น

วัตถุยกเว้น

วัตถุยกเว้นเป็นวัตถุทับทิมปกติ พวกเขาเก็บข้อมูลทั้งหมดเกี่ยวกับ "เกิดอะไรขึ้น" สำหรับข้อยกเว้นที่คุณเพิ่งได้รับการช่วยเหลือ

ในการรับวัตถุยกเว้น คุณจะต้องใช้รูปแบบการช่วยเหลือที่แตกต่างกันเล็กน้อย

# Rescues all errors, an puts the exception object in `e`
rescue => e

# Rescues only ZeroDivisionError and puts the exception object in `e`
rescue ZeroDivisionError => e

ในตัวอย่างที่สองข้างต้น ZeroDivisionError เป็นคลาสของวัตถุใน e . ข้อยกเว้น "ประเภท" ทั้งหมดที่เราพูดถึงนั้นเป็นเพียงชื่อคลาสเท่านั้น

ออบเจ็กต์ข้อยกเว้นยังมีข้อมูลการดีบักที่เป็นประโยชน์อีกด้วย มาดูวัตถุยกเว้นสำหรับ ZeroDivisionError . ของเรา .

begin
  # Any exceptions in here... 
  1/0
rescue ZeroDivisionError => e
  puts "Exception Class: #{ e.class.name }"
  puts "Exception Message: #{ e.message }"
  puts "Exception Backtrace: #{ e.backtrace }"
end

# Outputs:
# Exception Class: ZeroDivisionError
# Exception Message: divided by 0
# Exception Backtrace: ...backtrace as an array...

เช่นเดียวกับข้อยกเว้น Ruby ส่วนใหญ่ มันมีข้อความและ backtrace พร้อมกับชื่อคลาส

เพิ่มข้อยกเว้นของคุณเอง

จนถึงตอนนี้ เราเพิ่งพูดถึงการช่วยเหลือข้อยกเว้นเท่านั้น คุณยังสามารถเรียกใช้ข้อยกเว้นของคุณเองได้ กระบวนการนี้เรียกว่า "การเพิ่ม" คุณทำได้โดยการเรียก raise กระบวนการ.

เมื่อคุณยกข้อยกเว้นของคุณเอง คุณจะต้องเลือกประเภทของข้อยกเว้นที่จะใช้ คุณยังได้รับการตั้งค่าข้อความแสดงข้อผิดพลาด

นี่คือตัวอย่าง:

begin
  # raises an ArgumentError with the message "you messed up!"
  raise ArgumentError.new("You messed up!")
rescue ArgumentError => e  
  puts e.message
end

# Outputs: You messed up! 

อย่างที่คุณเห็น เรากำลังสร้างวัตถุข้อผิดพลาดใหม่ (ArgumentError ) ด้วยข้อความที่กำหนดเอง ("คุณทำพลาด!") แล้วส่งต่อไปยัง raise วิธีการ

นี่คือ Ruby raise สามารถเรียกได้หลายวิธี:

# This is my favorite because it's so explicit
raise RuntimeError.new("You messed up!")

# ...produces the same result
raise RuntimeError, "You messed up!"

# ...produces the same result. But you can only raise 
# RuntimeErrors this way
raise "You messed up!"

การกำหนดข้อยกเว้นที่กำหนดเอง

ข้อยกเว้นในตัวของ Ruby นั้นยอดเยี่ยม แต่ไม่ครอบคลุมทุกกรณีการใช้งานที่เป็นไปได้

จะเกิดอะไรขึ้นหากคุณกำลังสร้างระบบผู้ใช้และต้องการแจ้งข้อยกเว้นเมื่อผู้ใช้พยายามเข้าถึงส่วนนอกขอบเขตของไซต์ ไม่มีข้อยกเว้นมาตรฐานของ Ruby ที่เหมาะสม ดังนั้นทางออกที่ดีที่สุดของคุณคือการสร้างข้อยกเว้นรูปแบบใหม่

หากต้องการสร้างข้อยกเว้นที่กำหนดเอง เพียงสร้างคลาสใหม่ที่สืบทอดจาก StandardError .

class PermissionDeniedError < StandardError

end

raise PermissionDeniedError.new()

นี่เป็นเพียงคลาส Ruby ปกติ นั่นหมายความว่าคุณสามารถเพิ่มเมธอดและข้อมูลได้เหมือนกับคลาสอื่นๆ มาเพิ่มแอตทริบิวต์ที่เรียกว่า "action":

class PermissionDeniedError < StandardError

  attr_reader :action

  def initialize(message, action)
    # Call the parent's constructor to set the message
    super(message)

    # Store the action in an instance variable
    @action = action
  end

end

# Then, when the user tries to delete something they don't
# have permission to delete, you might do something like this:
raise PermissionDeniedError.new("Permission Denied", :delete)

ลำดับชั้นของคลาส

เราเพิ่งสร้างข้อยกเว้นที่กำหนดเองโดยการจัดคลาสย่อย StandardError ซึ่งตัวเองเป็นคลาสย่อย Exception .

อันที่จริง หากคุณดูลำดับชั้นของคลาสของข้อยกเว้นใน Ruby คุณจะพบว่ามันนำไปสู่ ​​Exception ในที่สุด . ที่นี่ฉันจะพิสูจน์ให้คุณเห็น นี่เป็นข้อยกเว้นส่วนใหญ่ของ Ruby ซึ่งแสดงตามลำดับชั้น:

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

คุณไม่จำเป็นต้องจำสิ่งเหล่านี้ทั้งหมดแน่นอน ฉันกำลังแสดงให้คุณเห็นเพราะแนวคิดเรื่องลำดับชั้นนี้สำคัญมากสำหรับเหตุผลเฉพาะ

การช่วยชีวิตข้อผิดพลาดของคลาสเฉพาะจะช่วยแก้ไขข้อผิดพลาดของคลาสย่อยด้วย

มาลองกันใหม่...

เมื่อคุณ rescue StandardError คุณไม่เพียงแต่กู้คืนข้อยกเว้นด้วยคลาส StandardError แต่ลูกๆ ของมันด้วย หากคุณดูแผนภูมิ คุณจะเห็นว่าเยอะมาก:ArgumentError , IOError , ฯลฯ

หากคุณต้อง rescue Exception คุณจะช่วยเหลือทุกข้อยกเว้นซึ่งจะเป็นความคิดที่แย่มาก

การช่วยเหลือข้อยกเว้นทั้งหมด (ทางที่ไม่ดี)

ถ้าอยากโดนตะคอก ให้ไปที่ stack overflow แล้วโพสต์โค้ดที่หน้าตาแบบนี้:

// Don't do this 
begin
  do_something()
rescue Exception => e
  ...
end

รหัสด้านบนจะช่วยเหลือทุกข้อยกเว้น อย่าทำ! มันจะทำลายโปรแกรมของคุณด้วยวิธีแปลกๆ

นั่นเป็นเพราะ Ruby ใช้ข้อยกเว้นสำหรับสิ่งอื่นที่ไม่ใช่ข้อผิดพลาด นอกจากนี้ยังใช้เพื่อจัดการข้อความจากระบบปฏิบัติการที่เรียกว่า "สัญญาณ" หากคุณเคยกด "ctrl-c" เพื่อออกจากโปรแกรม แสดงว่าคุณได้ใช้สัญญาณ การระงับข้อยกเว้นทั้งหมด เท่ากับคุณระงับสัญญาณเหล่านั้นด้วย

นอกจากนี้ยังมีข้อยกเว้นบางประเภท เช่น ข้อผิดพลาดทางไวยากรณ์ ที่ควรจะทำให้โปรแกรมของคุณหยุดทำงาน หากคุณระงับข้อความเหล่านี้ คุณจะไม่มีทางรู้เลยว่าคุณพิมพ์ผิดหรือผิดพลาดอื่นๆ เมื่อใด

การช่วยเหลือข้อผิดพลาดทั้งหมด (อย่างถูกวิธี)

ย้อนกลับไปดูแผนภูมิลำดับชั้นของชั้นเรียน แล้วคุณจะเห็นว่าข้อผิดพลาดทั้งหมดที่คุณต้องการช่วยคือลูกของ StandardError

นั่นหมายความว่า หากคุณต้องการกู้ "ข้อผิดพลาดทั้งหมด" คุณควรกู้คืน StandardError .

begin
  do_something()
rescue StandardError => e
  # Only your app's exceptions are swallowed. Things like SyntaxErrror are left alone. 
end

ที่จริงแล้ว ถ้าคุณไม่ระบุคลาสข้อยกเว้น Ruby จะถือว่าคุณหมายถึง StandardError

begin
  do_something()
rescue => e
  # This is the same as rescuing StandardError
end

การช่วยเหลือข้อผิดพลาดเฉพาะ (แนวทางที่ดีที่สุด)

เมื่อคุณรู้วิธีแก้ไขข้อผิดพลาดทั้งหมดแล้ว คุณควรรู้ว่านี่เป็นความคิดที่ไม่ดี มีกลิ่นของรหัส ถือว่าเป็นอันตราย ฯลฯ

โดยปกติแล้วจะเป็นสัญญาณว่าคุณขี้เกียจเกินกว่าจะคิดออกว่าข้อยกเว้นใดจำเป็นต้องได้รับการช่วยเหลือ และพวกเขามักจะกลับมาหลอกหลอนคุณเสมอ

ดังนั้นจงใช้เวลาและทำมันให้ถูกต้อง ช่วยเหลือข้อยกเว้นเฉพาะ

begin
  do_something()
rescue Errno::ETIMEDOUT => e
  // This will only rescue Errno::ETIMEDOUT exceptions
end

คุณสามารถกู้คืนข้อยกเว้นได้หลายประเภทในบล็อกกู้ภัยเดียวกัน ดังนั้นจึงไม่มีข้อแก้ตัว :)

begin
  do_something()
rescue Errno::ETIMEDOUT, Errno::ECONNREFUSED => e
end