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

การออกจากโปรแกรม Ruby อย่างสง่างาม: แนวทางปฏิบัติที่ดีที่สุดสำหรับการจบโปรแกรม

ในฐานะนักพัฒนา เราใช้เวลามากกับการทำให้โปรแกรมรัน จนลืมว่าโปรแกรมจบอย่างไร และมันสำคัญ! เมื่อโปรแกรมพฤติกรรมถูกต้องตอนจบ จะทำให้จัดการง่ายขึ้น และทำงานร่วมกับเครื่องมือ devops มาตรฐานได้ดีขึ้น

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

# ออกได้โดยเรียกเมธอด
exit
exit!
abort("She cannot take any more of this, Captain!")
# ...หรือโดยไม่จับ exception
raise("Destroyed...")
fail
# ...หรือปล่อยให้โปรแกรมจบตามปกติ :)

รหัสออก (Exit codes) และคุณ

ทุกโปรแกรมบน Linux และ OSX คืนค่าสถานะออก (exit status code) เมื่อรันเสร็จ คุณไม่เห็นรหัสนี้โดยปกติ OS ใช้เบื้องหลังเพื่อรู้ว่าโปรแกรมจบปกติหรือมีข้อผิดพลาด

หากใช้ bash สามารถดูรหัสออกของโปรแกรมที่เพิ่งรันจากตัวแปรสภาพแวดล้อม $? ตัวอย่างแสดงรหัสออกสำหรับความล้มเหลว แล้วตามด้วยความสำเร็จ

% ls this-file-doesnt-exist
ls: this-file-doesnt-exist: No such file or directory
blog% echo $?
1
% ls .
bm.rb
% echo $?
0

โดยทั่วไป กฎสำหรับระบบ Unix-based คือ 0 หมายถึงสำเร็จ ค่าอื่นหมายถึงล้มเหลว

หากเคยใช้ "ตัวดำเนินการ" && ใน bash คุณใช้รหัสสถานะแล้ว ตัวอย่าง: บอก bash ให้รันโปรแกรม B ก็ต่อเมื่อโปรแกรม A สำเร็จ

% a && b

สามารถใช้เทคนิคนี้หลายแบบ เช่น ต้องการ precompile assets แล้วอัปโหลดไป CDN

blog% rake assets:precompile && rake cdn:upload_assets

ระบุสถานะออกด้วย Ruby

Ruby ดูแลเรื่องสถานะออกมากให้เรา หากโปรแกรมจบปกติ จะคืนสถานะ "สำเร็จ" หากล้มเหลวจาก exception ที่ไม่ได้จับ จะให้สถานะ "ล้มเหลว" ดูรหัสออกสำหรับสคริปต์ที่ raise exception

% ruby err.rb
err.rb:1:in `
': goodbye (RuntimeError) tmp% echo $? 1

แต่ถ้าไม่ต้องการ uncaught exceptions? ต้องการจบอย่างสะอาดแต่คืนรหัส "ล้มเหลว"? หรืออยากคืนรหัสกำหนดเองพร้อมข้อความผิดพลาด?

ทำได้ง่าย: ส่งอาร์กิวเมนต์ให้ฟังก์ชัน exit อาร์กิวเมนต์เป็น boolean หรือ integer หาก boolean: true = สำเร็จ หาก integer: 0 = สำเร็จ

exit(true)  # ออกด้วยรหัส "สำเร็จ"
exit(0)     # ออกด้วยรหัส "สำเร็จ"
exit(false) # ออกด้วยรหัส "ล้มเหลว"
exit(1)     # ออกด้วยรหัส "ล้มเหลว"
exit(436)   # รหัสข้อผิดพลาดกำหนดเอง

Exit ทำงานยังไงเบื้องหลัง

เมธอด exit เพียงแค่ raise SystemExit exception หากไม่จับ โปรแกรมจะ abort เหมือน uncaught exception อื่นๆ น่าสนใจแต่มีผลลัพธ์บางอย่าง

หากคุณกลืนทุก SystemExit exceptions คุณจะทำลายเมธอด exit ตัวอย่างด้านล่างแสดงนี้

begin
  exit 100
rescue SystemExit => e
  puts "Tried to exit with status #{ e.status }"
end
puts "...but it never exited, because we swallowed the exception"
# Outputs:
# Tried to exit with status 100
# ...but it never exited, because we swallowed the exception

นี่เป็นอีกเหตุผลอย่า rescue Exception เพราะ SystemExit สืบทอดมาจาก Exception การกลืน Exception จะทำให้ exit เสีย

begin
  exit
rescue Exception # never do this
  puts "I just broke the exit function!"
end

ข้อความผิดพลาดที่มนุษย์อ่านง่าย

รหัสออกดีสำหรับเครื่อง แต่มนุษย์ชอบข้อความอธิบาย OS มีสตรีมเอาต์พุทเฉพาะสำหรับข้อความผิดพลาด นั่นคือ STDERR

เขียน STDERR ได้เหมือน IO object อื่น ตัวอย่าง: เขียนข้อความผิดพลาดและออกด้วยสถานะ "ข้อผิดพลาด"

STDERR.puts("ABORTED! You forgot to BAR the BAZ")
exit(false)

เป็น Ruby ก็มีวิธีสั้นกว่า: ใช้เมธอด abort

# เขียนข้อความไป STDERR และออกด้วยรหัสสถานะข้อผิดพลาด
abort("ABORTED! You forgot to BAR the BAZ")

Callbacks ผ่าน at_exit

Ruby ให้ลงทะเบียน handler เรียกตอนโปรแกรมจบ สามารถมีหลายตัว เรียกในลำดับกลับกับที่ลงทะเบียน ดูตัวอย่าง:

at_exit do
  puts "handler 1"
end
at_exit do
  puts "handler 2"
end
# Outputs "handler2\nhandler1" on exit

Exit handler สามารถ override รหัสออกได้ โดยเรียก exit ภายใน handler แม้ดูเหมือน infinite loop แต่ไม่ใช่ :)

at_exit do
  exit 100
end
exit 0
# โปรแกรมนี้ออกด้วยรหัส 100

หากอยากออกโดยไม่เรียก callbacks ใช้ exit! แต่ระวัง: อาจทำปัญหาหาก gems หรือโค้ดบุคคลที่สามพึ่งพา callbacks นั้น

ข้อสังเกตน่าสนใจ

หลายไลบรารีใช้ at_exit อย่างสร้างสรรค์ (บางคนบอกว่าแฮ็ก) เช่น Sinatra ใช้ at_exit hook เพื่อบูทเว็บแอป ทำให้แน่ใจว่าโค้ดทั้งหมดโหลดเสร็จก่อนเริ่มเซิร์ฟเวอร์:

module Sinatra
  class Application < Base
    ...
    at_exit { Application.run! if $!.nil? && Application.run? }
  end
end

มีบทความเกี่ยวกับนี้ที่ Arkency blog: Are we abusing at_exit?