เมื่อวันก่อนฉันกำลังค้นหาคำแนะนำเกี่ยวกับข้อยกเว้น 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