Catching Exception with Begin/Rescue In Ruby
Bài đăng này đã không được cập nhật trong 4 năm
Xin chào mn, chắc hẳn đối với mọi ae Dev như chúng ta thì không quá xa lạ khi nge đến Exception rồi nhỉ. Phạm vi bài viết này mình không giới thiệu chi tiết về Exception là gì, hay cấu trúc nó như thế nào mà mình chỉ tập trung vào vấn đề xử lí Exception đó như thế nào trong chương trình của chúng ta thôi nhé. Let's go thôi nào
Tìm hiểu về Begin/Rescue
Gỉa sử mình có method tên là divide
như sau:
def divide x, y
x / y
end
Trông đoạn code thật ngon lành và ngắn gọn nhỉ, thử một vài tham số x
, y
xem nó hoạt động thế nào nhé
> puts divide 10, 5
2
Đúng như ta nghĩ, code chạy ngon lành rồi . Thử thêm một số case khác xem nhé
> puts divide 10, 0
ZeroDivisionError (divided by 0)
> puts divide 10, "a"
TypeError (String can't be coerced into Integer)
Toang rồi các bạn ơi . Coding nó không đơn giản như ta nghĩ nhỉ, cũng giống như chơi một ván cờ vậy, người chơi cờ giỏi thường trước khi đánh một nước cờ thì họ phải tính toán rất nhiều nước cờ mà đối thủ có thể đánh sau đó(không hẳn là tất cả các nước nhé) và họ luôn suy nghĩ cho mình hướng đi tương ứng với từng trường hợp đó. Gần đây có bộ phim khá nổi đó là Phi vụ triệu đô
, nhân vật professor
trong bộ phim rất thông minh và để có được một vụ trộm hoàn hảo thì a này phải tính đến các trường xảy ra ngoài ý muốn có thể trong plan của mình và đưa ra phương án giải quyết cho mỗi trường hợp cụ thể cũng như đoạn code của mình ở trên vậy mình chỉ nghĩ đến một ngữ cảnh qúa tuyệt vời mà chưa tính toán đến các trường hợp ngoại lệ -> Code toang . Ngoài ra chúng ta cũng không nên quá tin tưởng input đầu vào quá nhé(ở đoạn code trên là x
, y
).
Chúng ta sửa lại đoạn code trên sử dụng khối begin... end
để bao phủ những rủi ro của hàm divide
có thể có và mệnh đề rescue
để xử lí lỗi như ghi log, thông báo message cho hệ thống. Let's go!
def divide x, y
begin
x / y
rescue
puts "Lỗi mất rồi!"
return nil
end
end
Chúng ta thử lại đoạn code lỗi ở trên xem thế nào nhé:
> puts divide 10, 0
Lỗi mất rồi!
=> nil
> puts divide 10, "a"
Lỗi mất rồi!
=> nil
Wow, tạm ổn hơn rồi
Xử lí message error
- Để thuận tiện cho việc
debugger
thì message thông báo lỗi phải thực sự cụ thể và chi tiết nhất có thể.
def divide x, y
begin
x / y
rescue => e
puts "Đã có một %s (%s)" % [e.class, e.message]
puts e.backtrace
end
end
Thử lại xem nhé:
> divide 10, 0
Đã có một ZeroDivisionError (divided by 0)
(irb):39:in `/'
(irb):39:in `divide'
(irb):45:in `irb_binding'
/Users/admin/.rbenv/versions/2.5.5/lib/ruby/2.5.0/irb/workspace.rb:85:in `eval'
/Users/admin/.rbenv/versions/2.5.5/lib/ruby/2.5.0/irb/workspace.rb:85:in `evaluate'
/Users/admin/.rbenv/versions/2.5.5/lib/ruby/2.5.0/irb/context.rb:380:in `evaluate'
/Users/admin/.rbenv/versions/2.5.5/lib/ruby/2.5.0/irb.rb:491:in `block (2 levels) in eval_input'
/Users/admin/.rbenv/versions/2.5.5/lib/ruby/2.5.0/irb.rb:623:in `signal_status'
/Users/admin/.rbenv/versions/2.5.5/lib/ruby/2.5.0/irb.rb:488:in `block in eval_input'
/Users/admin/.rbenv/versions/2.5.5/lib/ruby/2.5.0/irb/ruby-lex.rb:246:in `block (2 levels) in each_top_level_statement'
/Users/admin/.rbenv/versions/2.5.5/lib/ruby/2.5.0/irb/ruby-lex.rb:232:in `loop'
/Users/admin/.rbenv/versions/2.5.5/lib/ruby/2.5.0/irb/ruby-lex.rb:232:in `block in each_top_level_statement'
/Users/admin/.rbenv/versions/2.5.5/lib/ruby/2.5.0/irb/ruby-lex.rb:231:in `catch'
/Users/admin/.rbenv/versions/2.5.5/lib/ruby/2.5.0/irb/ruby-lex.rb:231:in `each_top_level_statement'
/Users/admin/.rbenv/versions/2.5.5/lib/ruby/2.5.0/irb.rb:487:in `eval_input'
/Users/admin/.rbenv/versions/2.5.5/lib/ruby/2.5.0/irb.rb:428:in `block in run'
/Users/admin/.rbenv/versions/2.5.5/lib/ruby/2.5.0/irb.rb:427:in `catch'
/Users/admin/.rbenv/versions/2.5.5/lib/ruby/2.5.0/irb.rb:427:in `run'
/Users/admin/.rbenv/versions/2.5.5/lib/ruby/2.5.0/irb.rb:383:in `start'
/Users/admin/.rbenv/versions/2.5.5/bin/irb:11:in `<main>'
=> nil
> divide 10, "a"
Đã có một TypeError (String can't be coerced into Integer)
(irb):39:in `/'
(irb):39:in `divide'
(irb):46:in `irb_binding'
/Users/admin/.rbenv/versions/2.5.5/lib/ruby/2.5.0/irb/workspace.rb:85:in `eval'
/Users/admin/.rbenv/versions/2.5.5/lib/ruby/2.5.0/irb/workspace.rb:85:in `evaluate'
/Users/admin/.rbenv/versions/2.5.5/lib/ruby/2.5.0/irb/context.rb:380:in `evaluate'
/Users/admin/.rbenv/versions/2.5.5/lib/ruby/2.5.0/irb.rb:491:in `block (2 levels) in eval_input'
/Users/admin/.rbenv/versions/2.5.5/lib/ruby/2.5.0/irb.rb:623:in `signal_status'
/Users/admin/.rbenv/versions/2.5.5/lib/ruby/2.5.0/irb.rb:488:in `block in eval_input'
/Users/admin/.rbenv/versions/2.5.5/lib/ruby/2.5.0/irb/ruby-lex.rb:246:in `block (2 levels) in each_top_level_statement'
/Users/admin/.rbenv/versions/2.5.5/lib/ruby/2.5.0/irb/ruby-lex.rb:232:in `loop'
/Users/admin/.rbenv/versions/2.5.5/lib/ruby/2.5.0/irb/ruby-lex.rb:232:in `block in each_top_level_statement'
/Users/admin/.rbenv/versions/2.5.5/lib/ruby/2.5.0/irb/ruby-lex.rb:231:in `catch'
/Users/admin/.rbenv/versions/2.5.5/lib/ruby/2.5.0/irb/ruby-lex.rb:231:in `each_top_level_statement'
/Users/admin/.rbenv/versions/2.5.5/lib/ruby/2.5.0/irb.rb:487:in `eval_input'
/Users/admin/.rbenv/versions/2.5.5/lib/ruby/2.5.0/irb.rb:428:in `block in run'
/Users/admin/.rbenv/versions/2.5.5/lib/ruby/2.5.0/irb.rb:427:in `catch'
/Users/admin/.rbenv/versions/2.5.5/lib/ruby/2.5.0/irb.rb:427:in `run'
/Users/admin/.rbenv/versions/2.5.5/lib/ruby/2.5.0/irb.rb:383:in `start'
/Users/admin/.rbenv/versions/2.5.5/bin/irb:11:in `<main>'
=> nil
Sau nhìu lần thay áo thì cũng thấy ổn rồi ae nhỉ .
- Trong một số trường hợp, mỗi Exception phải có một message error riêng biệt không chung format như đoạn code ta vừa xử lí ở trên. Để làm được việc này thì bạn phải biết được tên từng Exception, bạn có thể tham khảo ở đây.
def divide x, y
begin
x / y
rescue ZeroDivisionError
puts "Không thể chia cho 0!"
return nil
rescue TypeError
puts "Divide chỉ hoạt động với numbers!"
return nil
end
end
Thử run lại xem thế nào nhé:
> divide 10, 0
Không thể chia cho 0!
> divide 10, "a"
Divide chỉ hoạt động với numbers!
Retrying
Trong mệnh đề rescue
bạn có thể sử dụng retry
để chạy lại đoạn code trong block begin... end
lần nữa
def divide x, y
begin
puts "Bắt đầu chia..."
x / y
rescue ZeroDivisionError
puts "Không thể chia cho 0!"
y = 1
retry
rescue TypeError
puts "Divide chỉ hoạt động với numbers!"
return nil
end
end
Kết quả:
> divide 10, 0
Bắt đầu chia... # first time
Không thể chia cho 0!
Bắt đầu chia... # second time
=> 10
=> Lưu ý là trong ruby retry
nó chạy vô hạn nhé, nên hãy cẩn thận không là code của bạn chạy hoài đấy
Else
- Mệnh đề
else
được thực thi không có bất kìException
nào xảy ra cả.
def divide x, y
begin
x / y
rescue ZeroDivisionError
puts "Không thể chia cho 0!"
rescue TypeError
puts "Divide chỉ hoạt động với numbers!"
else
puts "Chúc mừng bạn đã thành công!"
end
end
Thử chạy xem nhé:
> divide 10, 3
Chúc mừng bạn đã thành công!
=> nil
- Nếu trong block
begin
có thực thireturn
thì sẽ không chạy vàoelse
mặc dù không có bất kìException
này xảy ra cả nhé
def divide x, y
begin
return x / y # return and exit when don't have any exception raised
rescue ZeroDivisionError
puts "Không thể chia cho 0!"
rescue TypeError
puts "Divide chỉ hoạt động với numbers!"
else
puts "Chúc mừng bạn đã thành công!"
end
end
Thử lại nhé:
> divide 10, 1
=> 10
Ensure
Đặc quyền hơn else một tí là ensure
luôn luôn chạy.
def divide x, y
begin
puts "ád"
return x / y
rescue ZeroDivisionError
puts "Không thể chia cho 0!"
rescue TypeError
puts "Divide chỉ hoạt động với numbers!"
ensure
puts "Ensure luôn luôn chạy"
return "het"
end
end
> divide 10, 1
Ensure luôn luôn chạy
=> 10
> divide 10, 0
Không thể chia cho 0!
Ensure luôn luôn chạy
=> nil
> divide 10, nil
Divide chỉ hoạt động với numbers!
Ensure luôn luôn chạy
=> nil
=> ensure
luôn thực thi trước return
trong begin
or rescue
, nếu ở block ensure
có return
thì nó sẽ overrived lại return
trong begin
or rescue
.
Kết bài
Qua bài viết mình hy vọng các bạn có thể biết thêm các xử lí một Exception
trong ruby như thế nào, nếu có cách nào khác thì hãy comment vào bên dưới để mọi người cùng biết nhé.
Cảm ơn mọi người đã đọc bài viết của mình nhé.
Happy coding!
All rights reserved