Ruby QuickRefของ Ryan Davis พูดว่า (ไม่มีคำอธิบาย):
อย่าช่วยให้มีข้อยกเว้น EVER หรือฉันจะแทงคุณ
ทำไมจะไม่ล่ะ? สิ่งที่ถูกต้องทำคืออะไร?
Ruby QuickRefของ Ryan Davis พูดว่า (ไม่มีคำอธิบาย):
อย่าช่วยให้มีข้อยกเว้น EVER หรือฉันจะแทงคุณ
ทำไมจะไม่ล่ะ? สิ่งที่ถูกต้องทำคืออะไร?
คำตอบ:
TL; DR : ใช้StandardError
แทนการจับข้อยกเว้นทั่วไป เมื่อมีการยกข้อยกเว้นดั้งเดิมขึ้นมาใหม่ (เช่นเมื่อการช่วยชีวิตเพื่อบันทึกข้อยกเว้นเท่านั้น) การช่วยชีวิตException
อาจไม่เป็นไร
Exception
เป็นรากของลำดับชั้นยกเว้นทับทิมดังนั้นเมื่อคุณrescue Exception
คุณช่วยเหลือจากทุกอย่างรวมทั้ง subclasses เช่นSyntaxError
, และLoadError
Interrupt
การช่วยชีวิตInterrupt
ทำให้ผู้ใช้ไม่CTRLCสามารถออกจากโปรแกรมได้
การช่วยชีวิตSignalException
ป้องกันไม่ให้โปรแกรมตอบสนองต่อสัญญาณอย่างถูกต้อง มันจะไม่สามารถยกเว้นkill -9
ได้
การช่วยชีวิตSyntaxError
หมายความว่าสิ่งeval
ที่ล้มเหลวจะทำอย่างเงียบ ๆ
สิ่งเหล่านี้สามารถแสดงได้โดยการเรียกใช้โปรแกรมนี้และลองCTRLCหรือkill
:
loop do
begin
sleep 1
eval "djsakru3924r9eiuorwju3498 += 5u84fior8u8t4ruyf8ihiure"
rescue Exception
puts "I refuse to fail or be stopped!"
end
end
การช่วยชีวิตException
ไม่ได้เป็นค่าเริ่มต้น การทำ
begin
# iceberg!
rescue
# lifeboats
end
ไม่ช่วยเหลือจากมันบ้างจากException
StandardError
โดยทั่วไปคุณควรระบุสิ่งที่เฉพาะเจาะจงมากกว่าค่าเริ่มต้นStandardError
แต่การช่วยชีวิตจากขอบเขตให้Exception
กว้างขึ้นแทนที่จะ จำกัด ให้แคบลงและอาจมีผลร้ายและทำให้การล่าบั๊กยากมาก
หากคุณมีสถานการณ์ที่คุณต้องการช่วยเหลือStandardError
และคุณต้องการตัวแปรที่มีข้อยกเว้นคุณสามารถใช้แบบฟอร์มนี้:
begin
# iceberg!
rescue => e
# lifeboats
end
ซึ่งเทียบเท่ากับ:
begin
# iceberg!
rescue StandardError => e
# lifeboats
end
หนึ่งในสองสามกรณีทั่วไปที่มีสติเพื่อช่วยเหลือException
คือเพื่อบันทึก / รายงานซึ่งในกรณีนี้คุณควรยกข้อยกเว้นขึ้นใหม่ทันที:
begin
# iceberg?
rescue Exception => e
# do some logging
raise # not enough lifeboats ;)
end
Throwable
ใน java
ADAPTER_ERRORS = [::ActiveRecord::StatementInvalid, PGError, Mysql::Error, Mysql2::Error, ::ActiveRecord::JDBCError, SQLite3::Exception]
แล้วrescue *ADAPTER_ERRORS => e
จริงกฎ: ห้ามทิ้งข้อยกเว้น ความเที่ยงธรรมของผู้เขียนคำพูดของคุณนั้นเป็นที่น่าสงสัยตามที่เห็นได้จากความจริงที่ลงท้ายด้วย
หรือฉันจะแทงคุณ
แน่นอนให้ระวังว่าสัญญาณ (โดยค่าเริ่มต้น) การโยนข้อยกเว้นและกระบวนการที่ใช้เวลานานจะถูกยกเลิกผ่านสัญญาณดังนั้นการจับยกเว้นและไม่ยุติการยกเว้นสัญญาณจะทำให้โปรแกรมของคุณหยุดยาก ดังนั้นอย่าทำสิ่งนี้:
#! /usr/bin/ruby
while true do
begin
line = STDIN.gets
# heavy processing
rescue Exception => e
puts "caught exception #{e}! ohnoes!"
end
end
ไม่ทำไม่ได้จริงๆ อย่าเรียกใช้เพื่อดูว่าใช้งานได้หรือไม่
อย่างไรก็ตามสมมติว่าคุณมีเซิร์ฟเวอร์ที่มีเธรดและคุณต้องการให้ข้อยกเว้นทั้งหมดไม่ใช่:
thread.abort_on_exception = true
) จากนั้นเป็นที่ยอมรับอย่างสมบูรณ์ในเธรดการจัดการการเชื่อมต่อ:
begin
# do stuff
rescue Exception => e
myLogger.error("uncaught #{e} exception while handling connection: #{e.message}")
myLogger.error("Stack trace: #{backtrace.map {|l| " #{l}\n"}.join}")
end
ผลงานด้านบนเป็นรูปแบบของตัวจัดการข้อยกเว้นเริ่มต้นของรูบี้ด้วยข้อดีที่มันไม่ได้ทำให้โปรแกรมของคุณทำงาน Rails ทำสิ่งนี้ในตัวจัดการคำขอ
ข้อยกเว้นสัญญาณถูกยกขึ้นในเธรดหลัก เธรดพื้นหลังจะไม่ได้รับดังนั้นจึงไม่มีประเด็นในการพยายามจับพวกเขาที่นั่น
สิ่งนี้มีประโยชน์อย่างยิ่งในสภาพแวดล้อมการใช้งานจริงซึ่งคุณไม่ต้องการให้โปรแกรมหยุดทำงานเมื่อมีสิ่งผิดปกติเกิดขึ้น จากนั้นคุณสามารถใช้สแต็กดัมพ์ในบันทึกของคุณและเพิ่มลงในโค้ดของคุณเพื่อจัดการกับข้อยกเว้นเฉพาะเพิ่มเติมลงในสายการโทร
โปรดทราบว่ามีสำนวน Ruby อื่นที่มีผลเหมือนกันมาก:
a = do_something rescue "something else"
ในบรรทัดนี้ถ้าdo_something
ยกข้อยกเว้นก็จะถูกจับโดยทับทิมโยนออกไปและได้รับมอบหมายa
"something else"
โดยทั่วไปแล้วอย่าทำอย่างนั้นยกเว้นในกรณีพิเศษที่คุณรู้ว่าคุณไม่ต้องกังวล ตัวอย่างหนึ่ง:
debugger rescue nil
debugger
ฟังก์ชั่นเป็นวิธีที่ค่อนข้างดีในการตั้งเบรกพอยต์ในรหัสของคุณ แต่ถ้าทำงานนอกดีบักและทางรถไฟมันจะเพิ่มข้อยกเว้น ทีนี้ในทางทฤษฎีคุณไม่ควรปล่อยให้ debug code วางอยู่ในโปรแกรมของคุณ (pff! ไม่มีใครทำอย่างนั้น!) แต่คุณอาจต้องการเก็บมันไว้ชั่วขณะหนึ่งด้วยเหตุผลบางอย่าง แต่ไม่สามารถเรียกใช้ดีบักเกอร์ของคุณได้
บันทึก:
หากคุณเรียกใช้โปรแกรมของบุคคลอื่นที่จับสัญญาณข้อยกเว้นและละเว้นพวกเขา (พูดรหัสด้านบน) จากนั้น:
pgrep ruby
หรือps | grep ruby
ดูสำหรับโปรแกรมการกระทำผิดกฎหมายของ PID kill -9 <PID>
และเรียกใช้แล้ว หากคุณกำลังทำงานกับโปรแกรมของคนอื่นซึ่งไม่ว่าด้วยเหตุผลใดก็ตามที่มีการใช้บล็อกที่ไม่ใช้ข้อยกเว้นเหล่านี้ให้วางที่ด้านบนสุดของการฉีดยาเป็นวิธีแก้ปัญหาหนึ่งที่เป็นไปได้:
%W/INT QUIT TERM/.each { |sig| trap sig,"SYSTEM_DEFAULT" }
นี้ทำให้โปรแกรมที่จะตอบสนองต่อสัญญาณการเลิกจ้างตามปกติโดยทันทียุติผ่านไสข้อยกเว้น ที่ไม่มีการทำความสะอาด ดังนั้นอาจทำให้ข้อมูลสูญหายหรือคล้ายกัน ระวัง!
หากคุณต้องการทำสิ่งนี้:
begin
do_something
rescue Exception => e
critical_cleanup
raise
end
คุณสามารถทำสิ่งนี้ได้จริง:
begin
do_something
ensure
critical_cleanup
end
ในกรณีที่สองcritical cleanup
จะถูกเรียกทุกครั้งไม่ว่าจะมีข้อยกเว้นหรือไม่
kill -9
ได้
ensure
จะทำงานโดยไม่คำนึงว่ามีข้อยกเว้นเกิดขึ้นหรือไม่ในขณะที่rescue
จะทำงานเฉพาะในกรณีที่มีข้อยกเว้นเกิดขึ้น
อย่าrescue Exception => e
(ยกเว้นการเพิ่มข้อยกเว้นใหม่) - หรือคุณอาจขับรถออกจากสะพาน
สมมติว่าคุณอยู่ในรถ (ใช้ทับทิม) คุณเพิ่งติดตั้งพวงมาลัยใหม่พร้อมระบบอัปเกรดแบบ over-the-air (ซึ่งใช้eval
) แต่คุณไม่รู้ว่าโปรแกรมเมอร์คนใดคนหนึ่งสับสนกับไวยากรณ์
คุณอยู่บนสะพานและตระหนักว่าคุณกำลังมุ่งหน้าไปยังทางแยกดังนั้นคุณเลี้ยวซ้าย
def turn_left
self.turn left:
end
โอ๊ะ! นั่นอาจเป็นสิ่งที่ไม่ดี ™โชคดีที่ทับทิมยกตัวSyntaxError
ขึ้น
รถควรหยุดทันที - ใช่ไหม?
Nope
begin
#...
eval self.steering_wheel
#...
rescue Exception => e
self.beep
self.log "Caught #{e}.", :warn
self.log "Logged Error - Continuing Process.", :info
end
เสียงบี๊บ
คำเตือน: Caught SyntaxError Exception
ข้อมูล: ข้อผิดพลาดการบันทึก - กระบวนการต่อเนื่อง
คุณสังเกตเห็นบางสิ่งบางอย่างที่ไม่ถูกต้องและคุณได้ชัยชนะในการแบ่งฉุกเฉิน ( ^C
: Interrupt
)
เสียงบี๊บ
คำเตือน: การดักจับการขัดจังหวะ
ข้อมูล: ข้อผิดพลาดการบันทึก - กระบวนการต่อเนื่อง
ใช่ - นั่นไม่ได้ช่วยอะไรมาก คุณสวยใกล้กับทางรถไฟเพื่อให้คุณใส่รถในสวนสาธารณะ ( kill
ไอเอ็นจี: SignalException
)
เสียงบี๊บ
คำเตือน: Caught SignalException Exception
ข้อมูล: ข้อผิดพลาดการบันทึก - กระบวนการต่อเนื่อง
ในวินาทีสุดท้ายคุณดึงกุญแจ ( kill -9
) และรถหยุดคุณกระแทกไปที่พวงมาลัย (ถุงลมนิรภัยไม่สามารถพองขึ้นได้เพราะคุณไม่ได้หยุดโปรแกรมอย่างสง่างาม - คุณยกเลิกมัน) และคอมพิวเตอร์ ที่ด้านหลังของรถของคุณกระแทกเข้ากับที่นั่งด้านหน้ามัน กระป๋องโค้กครึ่งกระป๋องหกล้นเหนือกระดาษ ร้านขายของชำด้านหลังถูกบดขยี้และส่วนใหญ่จะอยู่ในไข่แดงและนม รถต้องการการซ่อมแซมและทำความสะอาดที่รุนแรง (การสูญเสียข้อมูล)
หวังว่าคุณจะมีประกัน (สำรอง) โอ้ใช่ - เพราะถุงลมนิรภัยไม่พองตัวคุณอาจบาดเจ็บ (โดนไล่ออก ฯลฯ )
แต่เดี๋ยวก่อน! มีมากกว่าเหตุผลที่คุณอาจต้องการใช้rescue Exception => e
!
สมมติว่าคุณเป็นรถคันนั้นและคุณต้องการตรวจสอบให้แน่ใจว่าถุงลมนิรภัยพองตัวหากรถเกินแรงหยุดที่ปลอดภัย
begin
# do driving stuff
rescue Exception => e
self.airbags.inflate if self.exceeding_safe_stopping_momentum?
raise
end
ต่อไปนี้เป็นข้อยกเว้น: คุณสามารถจับเท่านั้นถ้าคุณเพิ่มอีกข้อยกเว้นException
ดังนั้นกฎที่ดีกว่าคือไม่เคยกลืนกินException
และยกข้อผิดพลาดขึ้นมาใหม่
แต่การเพิ่มการช่วยเหลือนั้นทั้งง่ายที่จะลืมในภาษาเช่น Ruby และใส่คำสั่งการช่วยเหลือทันทีก่อนที่จะหยิบยกปัญหาขึ้นมาใหม่ และคุณไม่ต้องการที่จะลืมraise
คำสั่ง และถ้าคุณทำเช่นนั้นโชคดีที่พยายามค้นหาข้อผิดพลาดนั้น
โชคดีที่ทับทิมยอดเยี่ยมคุณสามารถใช้ensure
คำหลักซึ่งทำให้แน่ใจว่ารหัสทำงาน ensure
คำหลักที่จะเรียกใช้รหัสไม่ว่าสิ่งที่ - ถ้ายกเว้นจะโยนถ้าใครไม่ได้เป็นข้อยกเว้นเพียงอย่างเดียวถ้าโลกปลาย (หรือเหตุการณ์ที่ไม่น่าที่อื่น ๆ )
begin
# do driving stuff
ensure
self.airbags.inflate if self.exceeding_safe_stopping_momentum?
end
บูม! และรหัสนั้นควรจะทำงานต่อไป เหตุผลเดียวที่คุณควรใช้rescue Exception => e
คือถ้าคุณต้องการเข้าถึงข้อยกเว้นหรือหากคุณต้องการให้โค้ดทำงานบนข้อยกเว้น และอย่าลืมเพิ่มข้อผิดพลาดอีกครั้ง ทุกเวลา.
หมายเหตุ: ตามที่ @Niall ชี้ให้เห็นว่าทำงานอยู่เสมอ นี่เป็นสิ่งที่ดีเพราะบางครั้งโปรแกรมของคุณอาจโกหกคุณและไม่ส่งข้อยกเว้นแม้ว่าจะเกิดปัญหาก็ตาม ด้วยภารกิจที่สำคัญเช่นถุงลมนิรภัยที่พองตัวคุณต้องทำให้แน่ใจว่ามันจะเกิดขึ้นไม่ว่าจะเกิดอะไรขึ้น ด้วยเหตุนี้การตรวจสอบทุกครั้งที่รถหยุดไม่ว่าจะเกิดข้อยกเว้นหรือไม่ก็ตามเป็นความคิดที่ดี ถึงแม้ว่าถุงลมนิรภัยที่พองตัวจะเป็นงานที่ผิดปกติในบริบทการเขียนโปรแกรมส่วนใหญ่ แต่นี่เป็นเรื่องธรรมดาสำหรับงานล้างข้อมูลส่วนใหญ่
ensure
ทางเลือกที่rescue Exception
จะทำให้เข้าใจผิด - ตัวอย่างบ่งบอกว่าพวกเขาเทียบเท่า แต่ตามที่ระบุไว้ensure
จะเกิดขึ้นไม่ว่าจะมีข้อยกเว้นหรือไม่ดังนั้นตอนนี้ถุงลมนิรภัยของคุณจะพองเพราะคุณไป 5 ไมล์ต่อชั่วโมง
เพราะสิ่งนี้จับข้อยกเว้นทั้งหมด ก็ไม่น่าที่โปรแกรมของคุณสามารถกู้คืนจากใด ๆของพวกเขา
คุณควรจัดการข้อยกเว้นเฉพาะที่คุณรู้วิธีการกู้คืนจาก หากคุณไม่คาดว่าจะมีข้อยกเว้นบางประเภทอย่าจัดการมันพังเสียงดัง (เขียนรายละเอียดลงในบันทึก) จากนั้นจึงวิเคราะห์บันทึกและรหัสแก้ไข
การกลืนข้อยกเว้นไม่ดีอย่าทำเช่นนี้
นั่นเป็นกรณีเฉพาะของกฎที่คุณไม่ควรจับใด ๆยกเว้นคุณไม่ทราบวิธีการจัดการ หากคุณไม่ทราบวิธีจัดการกับมันจะดีกว่าเสมอที่จะให้ส่วนอื่น ๆ ของระบบจับและจัดการได้
ฉันเพิ่งอ่านโพสต์บล็อกที่ยอดเยี่ยมเกี่ยวกับมันที่honeybadger.io :
ทำไมคุณไม่ควรช่วยเหลือการยกเว้น
ปัญหาเกี่ยวกับการช่วยเหลือ Exception คือว่ามันช่วยจริงทุกข้อยกเว้นที่สืบทอดจาก Exception นั่นคือ .... พวกเขาทั้งหมด!
นั่นเป็นปัญหาเพราะมีข้อยกเว้นบางอย่างที่ทับทิมใช้ภายใน พวกเขาไม่มีอะไรเกี่ยวข้องกับแอพของคุณและการกลืนพวกมันจะทำให้สิ่งเลวร้ายเกิดขึ้น
นี่คือบางส่วนที่มีขนาดใหญ่:
SignalException :: Interrupt - หากคุณช่วยเหลือสิ่งนี้คุณจะไม่สามารถออกจากแอพได้โดยกดปุ่ม control-c
ScriptError :: SyntaxError - ข้อผิดพลาดทางไวยากรณ์การกลืนหมายความว่าสิ่งต่าง ๆ ที่ทำให้ ("ลืมอะไรบางอย่าง") จะล้มเหลวอย่างเงียบ ๆ
NoMemoryError - อยากรู้ว่าจะเกิดอะไรขึ้นเมื่อโปรแกรมของคุณทำงานต่อไปหลังจากที่ใช้แรมหมดแล้ว? ฉันก็ไม่เหมือนกัน.
begin do_something() rescue Exception => e # Don't do this. This will swallow every single exception. Nothing gets past it. end
ฉันเดาว่าคุณไม่ต้องการกลืนข้อยกเว้นระดับระบบใด ๆ เหล่านี้ คุณต้องการจับข้อผิดพลาดระดับแอปพลิเคชันทั้งหมดของคุณเท่านั้น ข้อยกเว้นทำให้รหัสของคุณ
โชคดีที่มีวิธีง่ายๆในการทำสิ่งนี้