Sẽ thử / Cuối cùng (không có Catch) bong bóng ngoại lệ?


115

Tôi gần như tích cực rằng câu trả lời là CÓ. Nếu tôi sử dụng khối Thử cuối cùng nhưng không sử dụng khối Bắt thì mọi trường hợp ngoại lệ SILL bong bóng. Chính xác?

Bất kỳ suy nghĩ về thực hành nói chung?

Seth

Câu trả lời:


130

Vâng, nó hoàn toàn sẽ. Tất nhiên, giả sử finallykhối của bạn không ném ngoại lệ, trong trường hợp đó sẽ "thay thế" một cách hiệu quả cái ban đầu được ném.


13
@David: Bạn không thể quay lại từ một khối cuối cùng trong C #.
Jon Skeet

3
Tài liệu msDN cũng xác nhận câu trả lời này: Ngoài ra, bạn có thể bắt ngoại lệ có thể được ném trong khối thử của câu lệnh thử cuối cùng lên cao hơn trong ngăn xếp cuộc gọi. Nghĩa là, bạn có thể bắt ngoại lệ trong phương thức gọi phương thức có chứa câu lệnh try-cuối cùng hoặc trong phương thức gọi phương thức đó hoặc trong bất kỳ phương thức nào trong ngăn xếp cuộc gọi. Nếu ngoại lệ không bị bắt, việc thực thi khối cuối cùng phụ thuộc vào việc hệ điều hành có chọn kích hoạt hoạt động thư giãn ngoại lệ hay không.
băng thông rộng

60

Bất kỳ suy nghĩ về thực hành nói chung?

Đúng. Hãy cẩn thận . Khi khối cuối cùng của bạn đang chạy, hoàn toàn có thể nó đang chạy vì một ngoại lệ không mong muốn, chưa được xử lý đã bị ném . Điều đó có nghĩa là một cái gì đó bị phá vỡ , và một cái gì đó hoàn toàn bất ngờ có thể xảy ra.

Trong tình huống đó, có thể nói rằng bạn không nên chạy mã trong các khối cuối cùng. Mã trong khối cuối cùng có thể được xây dựng để giả định rằng các hệ thống con mà nó phụ thuộc vào là lành mạnh, trong khi thực tế chúng có thể bị phá vỡ sâu sắc. Mã trong khối cuối cùng có thể làm cho mọi thứ tồi tệ hơn.

Ví dụ, tôi thường thấy loại điều này:

DisableAccessToTheResource();
try
{
    DoSomethingToTheResource();
}
finally
{
    EnableAccessToTheResource();
}

Tác giả của mã này đang nghĩ rằng "Tôi đang tạo ra một sự đột biến tạm thời đối với tình trạng của thế giới; tôi cần khôi phục lại trạng thái như trước khi tôi được gọi". Nhưng hãy nghĩ về tất cả những cách điều này có thể đi sai.

Đầu tiên, quyền truy cập vào tài nguyên có thể đã bị vô hiệu hóa bởi người gọi; trong trường hợp đó, mã này kích hoạt lại nó, có thể là sớm.

Thứ hai, nếu DoS SomethingToTheResource ném ra một ngoại lệ, thì có phải làm gì để cho phép truy cập vào tài nguyên không ??? Mã quản lý tài nguyên bị phá vỡ bất ngờ . Mã này cho biết, có hiệu lực "nếu mã quản lý bị hỏng, hãy đảm bảo rằng mã khác có thể gọi mã bị hỏng đó càng sớm càng tốt, để nó cũng có thể thất bại khủng khiếp ." Đây dường như là một ý tưởng tồi.

Thứ ba, nếu DoS SomethingToTheResource ném ra một ngoại lệ, thì làm sao chúng ta biết rằng EnableAccessToTheResource cũng sẽ không ném ngoại lệ? Dù có tệ hại gì đi nữa thì việc sử dụng tài nguyên cũng có thể ảnh hưởng đến mã dọn dẹp, trong trường hợp đó, ngoại lệ ban đầu sẽ bị mất và vấn đề sẽ khó chẩn đoán hơn.

Tôi có xu hướng viết mã như thế này mà không sử dụng các khối thử cuối cùng:

bool wasDisabled = IsAccessDisabled();
if (!wasDisabled)
    DisableAccessToTheResource();
DoSomethingToTheResource();
if (!wasDisabled)
    EnableAccessToTheResource();

Bây giờ nhà nước không bị đột biến trừ khi nó cần. Bây giờ trạng thái của người gọi không bị rối tung. Và bây giờ, nếu DoS SomethingToTheResource không thành công, thì chúng tôi không kích hoạt lại quyền truy cập. Chúng tôi giả định rằng một cái gì đó bị phá vỡ sâu sắc và không có nguy cơ làm cho tình hình tồi tệ hơn bằng cách cố gắng tiếp tục chạy mã. Hãy để người gọi giải quyết vấn đề, nếu họ có thể.

Vì vậy, khi nào là một ý tưởng tốt để chạy một khối cuối cùng? Đầu tiên, khi ngoại lệ được mong đợi. Ví dụ: bạn có thể mong đợi rằng một nỗ lực để khóa một tệp có thể thất bại, bởi vì người khác đã khóa nó. Trong trường hợp đó, có ý nghĩa để bắt ngoại lệ và báo cáo cho người dùng. Trong trường hợp đó, sự không chắc chắn về những gì bị hỏng được giảm đi; bạn không có khả năng làm cho mọi thứ tồi tệ hơn bằng cách dọn dẹp.

Thứ hai, khi tài nguyên bạn đang dọn dẹp là tài nguyên hệ thống khan hiếm. Ví dụ, sẽ rất hợp lý khi đóng một tệp xử lý trong một khối cuối cùng. ("Sử dụng" tất nhiên chỉ là một cách khác để viết khối thử cuối cùng.) Nội dung của tệp có thể bị hỏng, nhưng hiện tại bạn không thể làm gì về điều đó. Xử lý tập tin cuối cùng sẽ được đóng lại, vì vậy nó cũng có thể sớm hơn thay vì muộn hơn.


7
Tuy nhiên, nhiều ví dụ về cách chúng ta thực sự chưa có hành động cùng nhau như một ngành công nghiệp khi xử lý lỗi. Không phải tôi có bất cứ điều gì tốt hơn để đề xuất hơn ngoại lệ, nhưng tôi hy vọng tương lai nắm giữ một cái gì đó có nhiều khả năng dẫn đến hành động đúng đắn được thực hiện một cách hợp lý dễ dàng.
Jon Skeet

Trong vb.net, mã trong khối Cuối cùng có thể biết ngoại lệ nào đang chờ xử lý. Nếu người ta thiết lập một hệ thống phân cấp ngoại lệ để phân biệt "không làm điều đó; trạng thái không ổn" với "trạng thái bị phá vỡ", người ta có thể có một khối Cuối cùng chỉ chạy nếu ngoại lệ đang chờ xử lý không phải là một trong những trường hợp xấu. Tôi muốn có các thói quen xử lý / dọn dẹp bắt ngoại lệ và ném DisposeerFailedException (thuộc danh mục "xấu" và bao gồm ngoại lệ ban đầu là Nội bộ ngoại lệ). Tôi muốn thấy một giao diện iDis DùngEx tiêu chuẩn với Dispose (Ex as Exception) để tạo điều kiện cho điều đó.
supercat

Mặc dù tôi đánh giá cao rằng có những trường hợp ngoại lệ có thể xảy ra trong khi một đối tượng ở trạng thái xấu và cố gắng dọn dẹp sẽ khiến mọi việc tồi tệ hơn, tôi nghĩ những vấn đề đó nên được xử lý trong mã dọn dẹp, có thể với sự giúp đỡ của các đại biểu. Ví dụ, trình bao bọc khóa có thể hiển thị đại biểu chức năng "Check IfSafeToUnlock (Ex as Exception)" có thể được đặt vào những thời điểm khi đối tượng bị khóa ở trạng thái không hợp lệ và bị xóa. Trước khi phát hành khóa, trình bao bọc có thể kiểm tra đại biểu; nếu không trống, nó có thể chạy nó và giải phóng khóa chỉ khi nó trả về đúng.
supercat

Có một trình bao bọc sử dụng một đại biểu như vậy sẽ cho phép mã thao tác trạng thái đối tượng để chọn hành động dọn dẹp thích hợp nếu nó bị gián đoạn. Những hành động như vậy có thể bao gồm sửa chữa cấu trúc dữ liệu và trả về True, đưa ra một ngoại lệ "nghiêm trọng hơn" khác bao bọc bản gốc hoặc để ngoại lệ gốc thấm qua trong khi trả về Sai.
supercat

2
Eric, cảm ơn câu trả lời tuyệt vời của bạn. Tôi đoán tôi nên đã hỏi hai câu hỏi vì cả bạn và Jon đều đúng. Trong mọi trường hợp, bạn được nâng cấp. Cảm ơn bạn đã chú ý đến câu hỏi.
Seth Spearman
Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.