Exception trong Ruby: raise, rescue, ensure và boundary

Exception biểu diễn control flow bất thường. Không phải mọi failure đều cần exception, nhưng exception đã xảy ra thì đừng rescue chỉ để hệ thống giả vờ khỏe.

Raise exception có nghĩa

1
2
3
class InsufficientBalance < StandardError; end

raise InsufficientBalance, "balance is too low"

Custom exception nên kế thừa StandardError cho lỗi application thông thường.

Rescue hẹp

1
2
3
4
5
6
begin
JSON.parse(payload)
rescue JSON::ParserError => error
logger.warn(error: error.message)
nil
end

Không rescue Exception; nó bao gồm Interrupt, SystemExit, NoMemoryError.

elseensure

1
2
3
4
5
6
7
8
9
begin
result = operation.call
rescue Timeout::Error
retry_later
else
persist(result)
ensure
connection.close
end
  • else chạy khi không exception.
  • ensure luôn chạy để cleanup.

Ưu tiên block API tự cleanup khi có thể.

Retry

retry chạy lại block begin. Luôn giới hạn attempt và chỉ retry lỗi transient:

1
2
3
4
5
6
7
8
attempts ||= 0
begin
client.call
rescue Timeout::Error
attempts += 1
retry if attempts < 3
raise
end

Thêm backoff/jitter cho distributed system. Retry ngay lập tức nhiều lần thường chỉ giúp service đang chết nhận thêm sự quan tâm.

Boundary

Rescue ở nơi có quyết định:

  • adapter translate lỗi thư viện thành domain error;
  • controller map domain error sang HTTP;
  • job quyết định retry/discard;
  • process boundary report error.

Không rescue mọi StandardError ở từng method. Error không xử lý được nên tiếp tục raise tới monitoring.

Exception handling tốt giữ context và invariant. Exception handling tệ đổi stack trace lấy nil, rồi sáu method sau mới nổ ở nơi vô tội.