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ỏ beginend đ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/