Ruby QuickRef của Ryan Davis nói (không cần giải thích):
Đừng giải cứu Ngoại lệ. KHÔNG BAO GIỜ. hoặc tôi sẽ đâm bạn.
Tại sao không? Điều gì là đúng để làm gì?
Ruby QuickRef của Ryan Davis nói (không cần giải thích):
Đừng giải cứu Ngoại lệ. KHÔNG BAO GIỜ. hoặc tôi sẽ đâm bạn.
Tại sao không? Điều gì là đúng để làm gì?
Câu trả lời:
TL; DR : Sử dụng StandardError
thay thế cho việc bắt ngoại lệ chung. Khi ngoại lệ ban đầu được nâng lại (ví dụ: khi giải cứu để chỉ ghi nhật ký ngoại lệ), việc giải cứu Exception
có thể là ổn.
Exception
là gốc rễ của hệ thống phân cấp ngoại lệ Ruby , vì vậy khi bạn rescue Exception
đã giải thoát khỏi tất cả mọi thứ , bao gồm cả các lớp con như SyntaxError
, LoadError
, và Interrupt
.
Việc giải cứu Interrupt
ngăn người dùng sử dụng CTRLCđể thoát khỏi chương trình.
Việc giải cứu SignalException
ngăn chương trình phản hồi chính xác với tín hiệu. Nó sẽ không thành công trừ khi kill -9
.
Giải cứu SyntaxError
có nghĩa là eval
thất bại sẽ âm thầm như vậy.
Tất cả những thứ này có thể được hiển thị bằng cách chạy chương trình này và cố gắng CTRLChoặc kill
nó:
loop do
begin
sleep 1
eval "djsakru3924r9eiuorwju3498 += 5u84fior8u8t4ruyf8ihiure"
rescue Exception
puts "I refuse to fail or be stopped!"
end
end
Giải cứu từ Exception
thậm chí không phải là mặc định. Đang làm
begin
# iceberg!
rescue
# lifeboats
end
không giải cứu từ Exception
, nó giải cứu từ StandardError
. Nói chung, bạn nên chỉ định một cái gì đó cụ thể hơn mặc định StandardError
, nhưng việc giải cứu khỏi việc Exception
mở rộng phạm vi thay vì thu hẹp phạm vi và có thể có kết quả thảm khốc và khiến việc săn lỗi trở nên vô cùng khó khăn.
Nếu bạn có một tình huống mà bạn muốn giải cứu StandardError
và bạn cần một biến có ngoại lệ, bạn có thể sử dụng biểu mẫu này:
begin
# iceberg!
rescue => e
# lifeboats
end
tương đương với:
begin
# iceberg!
rescue StandardError => e
# lifeboats
end
Một trong số ít các trường hợp phổ biến mà việc giải cứu Exception
nó là vì mục đích ghi nhật ký / báo cáo, trong trường hợp đó bạn nên ngay lập tức nêu lại ngoại lệ:
begin
# iceberg?
rescue Exception => e
# do some logging
raise # not enough lifeboats ;)
end
Throwable
trong java
ADAPTER_ERRORS = [::ActiveRecord::StatementInvalid, PGError, Mysql::Error, Mysql2::Error, ::ActiveRecord::JDBCError, SQLite3::Exception]
và sau đórescue *ADAPTER_ERRORS => e
Nguyên tắc thực sự là: Đừng vứt bỏ ngoại lệ. Tính khách quan của tác giả trích dẫn của bạn là nghi vấn, bằng chứng là nó kết thúc bằng
hoặc tôi sẽ đâm bạn
Tất nhiên, lưu ý rằng các tín hiệu (theo mặc định) đưa ra các ngoại lệ và thông thường các quy trình chạy dài bị chấm dứt thông qua tín hiệu, do đó, việc bắt Ngoại lệ và không chấm dứt ngoại lệ tín hiệu sẽ khiến chương trình của bạn rất khó dừng lại. Vì vậy, đừng làm điều này:
#! /usr/bin/ruby
while true do
begin
line = STDIN.gets
# heavy processing
rescue Exception => e
puts "caught exception #{e}! ohnoes!"
end
end
Không, thực sự, đừng làm điều đó. Đừng chạy nó để xem nó có hoạt động không.
Tuy nhiên, giả sử bạn có một máy chủ luồng và bạn muốn tất cả các ngoại lệ không:
thread.abort_on_exception = true
). Sau đó, điều này là hoàn toàn chấp nhận được trong luồng xử lý kết nối của bạn:
begin
# do stuff
rescue Exception => e
myLogger.error("uncaught #{e} exception while handling connection: #{e.message}")
myLogger.error("Stack trace: #{backtrace.map {|l| " #{l}\n"}.join}")
end
Ở trên có một biến thể của trình xử lý ngoại lệ mặc định của Ruby, với ưu điểm là nó cũng không giết chết chương trình của bạn. Rails thực hiện điều này trong trình xử lý yêu cầu của nó.
Ngoại lệ tín hiệu được đưa ra trong chủ đề chính. Chủ đề nền sẽ không có được chúng, vì vậy không có lý do gì để cố bắt chúng ở đó.
Điều này đặc biệt hữu ích trong môi trường sản xuất, nơi bạn không muốn chương trình của mình đơn giản dừng lại mỗi khi có sự cố. Sau đó, bạn có thể lấy các ngăn xếp ngăn xếp trong nhật ký của bạn và thêm vào mã của bạn để xử lý ngoại lệ cụ thể tiếp tục xuống chuỗi cuộc gọi và theo cách duyên dáng hơn.
Cũng lưu ý rằng có một thành ngữ Ruby khác cũng có tác dụng tương tự:
a = do_something rescue "something else"
Trong dòng này, nếu do_something
đưa ra một ngoại lệ, nó bị Ruby bắt, vứt đi và a
được chỉ định "something else"
.
Nói chung, đừng làm điều đó, trừ những trường hợp đặc biệt mà bạn biết bạn không cần phải lo lắng. Một ví dụ:
debugger rescue nil
Các debugger
chức năng là một cách khá tốt đẹp để thiết lập một breakpoint trong mã của bạn, nhưng nếu chạy bên ngoài một trình gỡ lỗi, và Rails, nó đặt ra một ngoại lệ. Bây giờ về mặt lý thuyết, bạn không nên để mã gỡ lỗi nằm trong chương trình của mình (pff! Không ai làm thế!) Nhưng bạn có thể muốn giữ nó ở đó một lúc vì một số lý do, nhưng không tiếp tục chạy trình gỡ lỗi của bạn.
Ghi chú:
Nếu bạn đã chạy chương trình của người khác bắt các ngoại lệ tín hiệu và bỏ qua chúng, (giả sử mã ở trên) thì:
pgrep ruby
, hoặc ps | grep ruby
, tìm kiếm bộ vi phạm chương trình vi phạm của bạn, rồi chạy kill -9 <PID>
. Nếu bạn đang làm việc với chương trình của người khác, vì bất kỳ lý do gì, bị ảnh hưởng bởi các khối ngoại lệ bỏ qua này, thì việc đặt chương trình này ở đầu dòng chính là một khả năng đồng bộ:
%W/INT QUIT TERM/.each { |sig| trap sig,"SYSTEM_DEFAULT" }
Điều này khiến chương trình phản hồi các tín hiệu kết thúc bình thường bằng cách chấm dứt ngay lập tức, bỏ qua các trình xử lý ngoại lệ, không dọn dẹp . Vì vậy, nó có thể gây mất dữ liệu hoặc tương tự. Hãy cẩn thận!
Nếu bạn cần làm điều này:
begin
do_something
rescue Exception => e
critical_cleanup
raise
end
bạn thực sự có thể làm điều này:
begin
do_something
ensure
critical_cleanup
end
Trong trường hợp thứ hai, critical cleanup
sẽ được gọi mỗi lần, cho dù có ném ngoại lệ hay không.
kill -9
.
ensure
sẽ chạy bất kể có ngoại lệ nào được nêu ra hay không, trong khi ý rescue
chí chỉ chạy nếu một ngoại lệ được nêu ra.
Đừng rescue Exception => e
(và không nêu lại ngoại lệ) - hoặc bạn có thể lái xe khỏi cầu.
Giả sử bạn đang ở trong xe (chạy Ruby). Gần đây, bạn đã cài đặt một tay lái mới với hệ thống nâng cấp không dây (sử dụng eval
), nhưng bạn không biết một trong những lập trình viên đã nhầm lẫn về cú pháp.
Bạn đang ở trên một cây cầu, và nhận ra rằng bạn đang đi một chút về phía lan can, vì vậy bạn rẽ trái.
def turn_left
self.turn left:
end
Giáo sư! Điều đó có lẽ không tốt ™, may mắn thay, Ruby tăng a SyntaxError
.
Xe nên dừng ngay - phải không?
Không.
begin
#...
eval self.steering_wheel
#...
rescue Exception => e
self.beep
self.log "Caught #{e}.", :warn
self.log "Logged Error - Continuing Process.", :info
end
bíp bíp
Cảnh báo: Bị bắt ngoại lệ SyntaxError.
Thông tin: Lỗi đăng nhập - Quá trình tiếp tục.
Bạn nhận thấy một cái gì đó là sai, và bạn slam trên phá vỡ tình trạng khẩn cấp ( ^C
: Interrupt
)
bíp bíp
Cảnh báo: Bị bắt ngoại lệ.
Thông tin: Lỗi đăng nhập - Quá trình tiếp tục.
Vâng - điều đó không giúp được gì nhiều. Bạn khá gần đường ray, vì vậy bạn đặt xe vào công viên ( kill
ing SignalException
:).
bíp bíp
Cảnh báo: Bắt tín hiệu ngoại lệ.
Thông tin: Lỗi đăng nhập - Quá trình tiếp tục.
Vào giây cuối cùng, bạn rút chìa khóa ( kill -9
) và xe dừng lại, bạn đâm thẳng vào vô lăng (túi khí không thể phồng lên vì bạn đã không dừng chương trình một cách duyên dáng - bạn đã chấm dứt chương trình) và máy tính ở phía sau xe của bạn đâm vào chỗ ngồi phía trước nó. Một lon Coke đầy một nửa tràn ra giấy tờ. Các cửa hàng tạp hóa ở phía sau bị nghiền nát, và hầu hết được bao phủ trong lòng đỏ trứng và sữa. Chiếc xe cần sửa chữa và làm sạch nghiêm trọng. (Mất dữ liệu)
Hy vọng bạn có bảo hiểm (Sao lưu). Ồ vâng - vì túi khí không phồng lên, bạn có thể bị tổn thương (bị đuổi việc, v.v.).
Nhưng chờ đã! Cóhơnlý do tại sao bạn có thể muốn sử dụng rescue Exception => e
!
Giả sử bạn là chiếc xe đó và bạn muốn chắc chắn rằng túi khí sẽ phồng lên nếu chiếc xe vượt quá đà dừng an toàn.
begin
# do driving stuff
rescue Exception => e
self.airbags.inflate if self.exceeding_safe_stopping_momentum?
raise
end
Đây là ngoại lệ cho quy tắc: Bạn chỉ có thể bắt Exception
nếu bạn nâng lại ngoại lệ . Vì vậy, một quy tắc tốt hơn là không bao giờ nuốt Exception
, và luôn nêu lại lỗi.
Nhưng việc thêm giải cứu vừa dễ quên trong một ngôn ngữ như Ruby, vừa đưa ra tuyên bố giải cứu ngay trước khi nêu lại một vấn đề cảm thấy hơi KHÔNG KHÔ. Và bạn không muốn quên raise
câu nói. Và nếu bạn làm, chúc may mắn tìm thấy lỗi đó.
Rất may, Ruby thật tuyệt vời, bạn chỉ cần sử dụng ensure
từ khóa, đảm bảo mã sẽ chạy. Các ensure
từ khóa sẽ chạy mã không có vấn đề gì - nếu một ngoại lệ được ném ra, nếu người ta không là ngoại lệ duy nhất là nếu thế giới kết thúc (hoặc các sự kiện không khác).
begin
# do driving stuff
ensure
self.airbags.inflate if self.exceeding_safe_stopping_momentum?
end
Bùng nổ! Và mã đó nên chạy bằng mọi cách. Lý do duy nhất bạn nên sử dụng rescue Exception => e
là nếu bạn cần quyền truy cập vào ngoại lệ hoặc nếu bạn chỉ muốn mã chạy trên một ngoại lệ. Và nhớ nâng lại lỗi. Mỗi lần.
Lưu ý: Như @Niall đã chỉ ra, đảm bảo luôn chạy. Điều này là tốt bởi vì đôi khi chương trình của bạn có thể nói dối bạn và không ném ngoại lệ, ngay cả khi vấn đề xảy ra. Với các nhiệm vụ quan trọng, như bơm hơi túi khí, bạn cần chắc chắn rằng nó xảy ra bất kể điều gì. Bởi vì điều này, kiểm tra mỗi khi xe dừng lại, liệu có ngoại lệ được ném hay không, là một ý tưởng tốt. Mặc dù bơm hơi là một nhiệm vụ không phổ biến trong hầu hết các bối cảnh lập trình, nhưng điều này thực sự khá phổ biến với hầu hết các nhiệm vụ dọn dẹp.
ensure
thay thế rescue Exception
là sai lệch - ví dụ ngụ ý rằng chúng tương đương nhau, nhưng như đã nêu ensure
sẽ xảy ra cho dù có Ngoại lệ hay không, vì vậy bây giờ túi khí của bạn sẽ phồng lên vì bạn đã vượt quá 5mph mặc dù không có gì sai.
Bởi vì điều này nắm bắt tất cả các ngoại lệ. Không chắc là chương trình của bạn có thể phục hồi từ bất kỳ ai trong số họ.
Bạn chỉ nên xử lý các trường hợp ngoại lệ mà bạn biết cách phục hồi. Nếu bạn không lường trước được một loại ngoại lệ nào đó, đừng xử lý nó, hãy nói to (viết chi tiết vào nhật ký), sau đó chẩn đoán nhật ký và sửa mã.
Nuốt ngoại lệ là xấu, đừng làm điều này.
Đó là một trường hợp cụ thể của quy tắc mà bạn không nên bắt gặp bất kỳ ngoại lệ nào mà bạn không biết cách xử lý. Nếu bạn không biết cách xử lý nó, tốt hơn hết là hãy để một phần khác của hệ thống nắm bắt và xử lý nó.
Tôi vừa đọc một bài đăng blog tuyệt vời về nó tại honeybadger.io :
Tại sao bạn không nên giải cứu Ngoại lệ
Vấn đề với việc giải cứu Ngoại lệ là nó thực sự giải cứu mọi ngoại lệ thừa hưởng từ Ngoại lệ. Đó là .... tất cả bọn họ!
Đó là một vấn đề vì có một số trường hợp ngoại lệ được Ruby sử dụng nội bộ. Họ không có bất cứ điều gì để làm với ứng dụng của bạn và nuốt chúng sẽ khiến những điều tồi tệ xảy ra.
Dưới đây là một vài trong số những người lớn:
SignalException :: Interrupt - Nếu bạn cứu điều này, bạn không thể thoát khỏi ứng dụng của mình bằng cách nhấn control-c.
ScriptError :: SyntaxError - Nuốt lỗi cú pháp có nghĩa là những thứ như put ("Quên một cái gì đó) sẽ âm thầm thất bại.
NoMemoryError - Bạn muốn biết điều gì sẽ xảy ra khi chương trình của bạn tiếp tục chạy sau khi nó sử dụng hết RAM? Tôi cũng không.
begin do_something() rescue Exception => e # Don't do this. This will swallow every single exception. Nothing gets past it. end
Tôi đoán rằng bạn không thực sự muốn nuốt bất kỳ ngoại lệ nào ở cấp độ hệ thống này. Bạn chỉ muốn nắm bắt tất cả các lỗi cấp độ ứng dụng của bạn. Các ngoại lệ gây ra mã CỦA BẠN.
May mắn thay, có một cách dễ dàng để làm điều này.