Thói quen là người vú nuôi của sai lầm. Habit is the nursery of errors.
Victor Hugo
1. Khi nào nên xử lý exception.
Nguyên tắc: Ngoại lệ không nên được dự đoán biết trước (Exceptions should not be expected)
Một hàm tạo user cơ bản được viết như sau:
def create
@user = User.new params[:user]
if @user.save
redirect_to user_path(@user)
else
flash[:notice] = 'Unable to create user'
render :action => :new
end
end
Nhưng có người lại viết nó một cách ngắn gọn:
def create
@user = User.new params[:user]
@user.save!
redirect_to user_path(@user)
rescue ActiveRecord::RecordNotSaved
flash[:notice] = 'Unable to create user'
render :action => :new
end
Lưu ý: @user.save sẽ trả về true hoặc false, còn @user.save! sẽ trả về exception nếu không thành công
Không nên viết hàm dùng rescue như trên vì ta đã dự đoán được trước khi nào thì user.save không thành công, chỉ nên dùng rescue cho những trường hợp ngoại lệ mà ta không dự đoán trước được như:
- Mất kết nối tới DB
- Tràn bộ nhớ
- Lỗi IO hoặc socket
Khi viết một hàm nhận input của người dùng, bạn đã dự đoán được trước input đó có hợp lệ hay không, vì thế bạn không nên xử lý nó bằng exception.
Exception chỉ nên được xử lý đối với những trường hợp không thể dự đoán trước được.
2. Xử lý exception hiệu qủa
Xét ví dụ:
def self.send_message(message)
begin
response = Net::HTTP.post_form(URI.parse(URL),
:message => message)
case response
when Net::HTTPOK
true # success response
when Net::HTTPClientError,
Net::HTTPInternalServerError
false # non-success response
end
rescue Timeout::Error => error
HoptoadNotifier.notify error
false # non-success response
end
end
Ở vd trên, rescue Timeout::Error chỉ xảy ra đối với câu lệnh Net::HTTP.post_form(), khi đã có response trả về rồi thì khối case response ở phía dưới sẽ không bao giờ trả về ngoại lệ.
Refactor lại đoạn code trên như sau:
def self.send_message(message)
begin
response = Net::HTTP.post_form(URI.parse(URL),
:message => message)
rescue Timeout::Error => error
HoptoadNotifier.notify error
false # non-success response
end
case response
when Net::HTTPOK
true # success response
when Net::HTTPClientError,
Net::HTTPInternalServerError
false # non-success response
end
end
Ta đã xử lý được vấn đề trên, nhưng với đoạn code trên khi ngoại lệ xảy ra thì khối lệnh case response vẫn bị thực thi.
Trong trường hợp này ta nên dùng từ khóa else:
def self.send_message(message)
begin
response = Net::HTTP.post_form(URI.parse(URL),
:message => message)
rescue Timeout::Error => error
HoptoadNotifier.notify error
false # non-success response
else
case response
when Net::HTTPOK
true # success response
when Net::HTTPClientError,
Net::HTTPInternalServerError
false # non-success response
end
end
end
Refactor lại lần nữa, bỏ begin và end đi vì hảm self.send_message(message) chỉ có khối này:
def self.send_message(message)
response = Net::HTTP.post_form(URI.parse(URL),
:message => message)
rescue Timeout::Error => error
HoptoadNotifier.notify error
false # non-success response
else
case response
when Net::HTTPOK
true # success response
when Net::HTTPClientError,
Net::HTTPInternalServerError
false # non-success response
end
end
Nguồn tham khảo:
https://robots.thoughtbot.com/save-bang-your-head-active-record-will-drive-you-mad
http://blog.carbonfive.com/2010/12/07/better-error-handling-in-ruby-with-rescue-else/
