Thường hữu ích lấy exception gần nhất, ngay khi code không kiểm soát vòng đời exception đó. Giả sử muốn thêm crash detection cơ bản cho app. Muốn log thêm info về bất kỳ crash nào do uncaught exception gây ra.
Bước đầu: thêm handler chạy khi app exit. Rất dễ qua method `at_exit` của Ruby kernel.
at_exit { puts "the app exited" }
Nhưng làm sao biết exit callback do exception invoke? Ruby cung cấp biến global bí ẩn `$!`. Chứa exception gần nhất raise ở đâu đó trong call stack hiện tại.
Dùng `$!` detect exit do exception cực dễ:
at_exit do
save_error_to_log($!) if $!
end
Hạn chế của $!
Sub malheureusement, `$!` chỉ work nếu exception xảy ra trong call stack hiện tại. Nếu rescue exception, rồi truy cập `$!` bên ngoài rescue clause, nhận `nil`.
begin
raise "x"
rescue
puts $! # => RuntimeError
end
puts $! # => nil
Nghĩa là `$!` vô dụng trong shell như IRB. Thường trong IRB, chạy method gặp exception. Muốn lấy exception object đó. Nhưng `$!` không work.
irb(main):001:0> 1/0
ZeroDivisionError: divided by 0
from (irb):1:in `/'
irb(main):002:0> $!
=> nil
Vượt qua $! với PRY
PRY vượt qua hạn chế `$!` bằng biến local riêng `_ex_`. Biến này chứa uncaught exception gần nhất.
[1] pry(main)> raise "hi"
RuntimeError: hi
from (pry):1:in `__pry__'
[2] pry(main)> _ex_
=> #
Lý do PRY làm được: không thực sự có uncaught exception bên trong PRY/IRB. Shell tự catch exception và hiển thị error message đẹp.
Copy relevant bits của PRY source. Code evaluate command wrap trong begin/rescue/end. Khi rescuable exception xảy ra, PRY lưu exception vào `self.last_exception` sau gán cho `_ex_`.
# Excerpted from PRY source at https://github.com/pry/pry/blob/623306966bfa86890ac182bc8375ec9699abe90d/lib/pry/pry_instance.rb#L273
begin
if !process_command_safely(line)
@eval_string << "#{line.chomp}\n" if !line.empty? || !@eval_string.empty?
end
rescue RescuableException => e
self.last_exception = e
result = e
Pry.critical_section do
show_result(result)
end
return
end
Require English
Tìm biến như `$!` hơi khó nhìn? May mắn Ruby có module "English" cung cấp version tiếng Anh cho nhiều global variable trông như "robot chửi thề".
Synonym cho `$!` là `$ERROR_INFO`. Dùng ở đâu thường dùng `$!`.
require "English"
begin
raise "x"
rescue
puts $ERROR_INFO # => RuntimeError
end
Mặc dù hầu hết English equivalent khác không liên quan chủ đề bài này, đưa vào cho vui. English variable bên trái, original bên phải.
| $ERROR_INFO | $! |
| $ERROR_POSITION | $@ |
| $FS | $; |
| $FIELD_SEPARATOR | $; |
| $OFS | $, |
| $OUTPUT_FIELD_SEPARATOR | $, |
| $RS | $/ |
| $INPUT_RECORD_SEPARATOR | $/ |
| $ORS | $\ |
| $OUTPUT_RECORD_SEPARATOR | $\ |
| $INPUT_LINE_NUMBER | $. |
| $NR | $. |
| $LAST_READ_LINE | $_ |
| $DEFAULT_OUTPUT | $> |
| $DEFAULT_INPUT | $< |
| $PID | $$ |
| $PROCESS_ID | $$ |
| $CHILD_STATUS | $? |
| $LAST_MATCH_INFO | $~ |
| $IGNORECASE | $= |
| $ARGV | $* |
| $MATCH | $& |
| $PREMATCH | $` |
| $POSTMATCH | $' |
| $LAST_PAREN_MATCH | $+ |