Ném một ngoại lệ vào cuối cùng


27

Các máy phân tích mã tĩnh như Fortify "phàn nàn" khi một ngoại lệ có thể được ném vào bên trong một finallykhối, nói rằng Using a throw statement inside a finally block breaks the logical progression through the try-catch-finally. Thông thường tôi đồng ý với điều này. Nhưng gần đây tôi đã bắt gặp mã này:

SomeFileWriter writer = null; 
try { 
     //init the writer
     //write into the file
} catch (...) {
     //exception handling
} finally {
     if (writer!= null) writer.close();  
}

Bây giờ nếu writerkhông thể đóng đúng cách, writer.close()phương thức sẽ đưa ra một ngoại lệ. Một ngoại lệ nên được ném vì (rất có thể) tệp không được lưu sau khi viết.

Tôi có thể khai báo một biến phụ, đặt nó nếu có lỗi đóng writervà ném ngoại lệ sau khối cuối cùng. Nhưng mã này hoạt động tốt và tôi không chắc có nên thay đổi hay không.

Những hạn chế của việc ném một ngoại lệ trong finallykhối là gì?


4
Nếu đây là Java và bạn có thể sử dụng Java 7, hãy kiểm tra xem các khối ARM có thể giải quyết vấn đề của bạn không.
Landei

@Landei, điều này giải quyết nó, nhưng tiếc là chúng tôi không sử dụng Java 7.
superM

Tôi sẽ nói rằng mã bạn đã hiển thị không phải là "Sử dụng câu lệnh ném bên trong khối cuối cùng" và như vậy tiến trình logic là tốt.
Mike

@Mike, tôi đã sử dụng bản tóm tắt tiêu chuẩn mà Fortify thể hiện, nhưng trực tiếp hoặc gián tiếp cuối cùng cũng có một ngoại lệ.
superM

Thật không may, khối tài nguyên thử cũng bị Fortify phát hiện là ngoại lệ được ném vào bên trong cuối cùng .. nó quá thông minh, chết tiệt .. vẫn không biết làm cách nào để vượt qua nó, có vẻ như lý do cho việc thử tài nguyên là để đảm bảo đóng tài nguyên cuối cùng, và bây giờ mỗi tuyên bố như vậy được Fortify báo cáo là mối đe dọa an ninh ..
mmona

Câu trả lời:


18

Về cơ bản, finallycác mệnh đề ở đó để đảm bảo giải phóng tài nguyên thích hợp. Tuy nhiên, nếu một ngoại lệ được ném vào bên trong khối cuối cùng, sự đảm bảo đó sẽ biến mất. Tệ hơn, nếu khối mã chính của bạn ném ngoại lệ, ngoại lệ được nêu trong finallykhối sẽ ẩn nó. Nó sẽ giống như lỗi là do cuộc gọi đến close, không phải vì lý do thực sự.

Một số người theo mô hình khó chịu của các trình xử lý ngoại lệ lồng nhau, nuốt bất kỳ ngoại lệ nào được ném vào finallykhối.

SomeFileWriter writer = null; 
try { 
     //init the writer
     //write into the file
} finally {
    if (writer!= null) {
        try {
            writer.close();
        } catch (...) {
        }
    }
}

Trong các phiên bản Java cũ hơn, bạn có thể "đơn giản hóa" mã này bằng cách gói tài nguyên trong các lớp thực hiện việc dọn dẹp "an toàn" này cho bạn. Một người bạn tốt của tôi tạo ra một danh sách các loại ẩn danh, mỗi loại cung cấp logic để làm sạch tài nguyên của họ. Sau đó, mã của anh ta chỉ đơn giản là lặp lại danh sách và gọi phương thức xử lý trong finallykhối.


1
+1 Mặc dù tôi nằm trong số những người sử dụng mã khó chịu đó. Nhưng với lý do của tôi, tôi sẽ nói rằng tôi không làm điều này luôn, chỉ khi ngoại lệ không quan trọng.
superM

2
Tôi thấy mình cũng làm như vậy. Đôi khi, việc tạo toàn bộ trình quản lý tài nguyên (ẩn danh hoặc không) làm mất đi mục đích của mã.
Công viên Travis

+1 từ tôi cũng vậy; về cơ bản bạn đã nói chính xác những gì tôi sẽ làm nhưng tốt hơn. Đáng chú ý là một số triển khai Luồng trong Java thực sự không thể ném Ngoại lệ vào close (), nhưng giao diện khai báo nó vì một số trong số chúng thực hiện. Vì vậy, trong một số trường hợp, bạn có thể thêm một khối bắt mà sẽ không bao giờ thực sự cần thiết.
vaughandroid

1
Có gì khó chịu khi sử dụng khối thử / bắt trong khối cuối cùng? Tôi thà làm điều đó hơn là làm phồng tất cả mã của mình bằng cách phải bọc tất cả các kết nối và luồng của tôi, v.v. hoặc bất cứ điều gì) gây ra một ngoại lệ - đó là điều bạn thực sự có thể muốn biết hoặc làm điều gì đó về ...
ban-geengineering

10

Những gì Travis park nói là sự thật rằng các ngoại lệ trong finallykhối sẽ tiêu thụ bất kỳ giá trị trả về hoặc ngoại lệ nào từ các try...catchkhối.

Tuy nhiên, nếu bạn đang sử dụng Java 7, vấn đề có thể được giải quyết bằng cách sử dụng khối tài nguyên dùng thử . Theo các tài liệu, miễn là tài nguyên của bạn thực hiện java.lang.AutoCloseable(hầu hết các nhà văn / người đọc thư viện làm bây giờ), khối tài nguyên dùng thử sẽ đóng lại cho bạn. Lợi ích bổ sung ở đây là bất kỳ ngoại lệ nào xảy ra trong khi đóng nó sẽ bị loại bỏ, cho phép giá trị trả về ban đầu hoặc ngoại lệ vượt qua.

Từ

FileWriter writer = null;
try {
  writer = new FileWriter("myFile.txt");
  writer.write("hello");
} catch(...) {
  // return/throw new exception
} finally {
  writer.close(); // an exception would consume the catch block's return/exception
}

Đến

try (FileWriter writer = new FileWriter("myFile.txt")) {
  writer.write("hello");
} catch(...) {
  // return/throw new exception, always gets returned even if writer fails to close
}

http://docs.oracle.com/javase/tutorial/essential/exceptions/tryResourceClose.html


1
Điều này đôi khi hữu ích. Nhưng trong trường hợp khi tôi thực sự cần phải ném một ngoại lệ khi tập tin không thể được đóng lại, phương pháp này sẽ che giấu các vấn đề.
superM

1
@superM Trên thực tế, try-with-resource không ẩn ngoại lệ close(). "Bất kỳ ngoại lệ nào xảy ra trong khi đóng nó sẽ bị loại bỏ, cho phép giá trị trả về ban đầu hoặc ngoại lệ vượt qua" - điều này đơn giản là không đúng . Một ngoại lệ từ close()phương thức sẽ bị loại bỏ chỉ khi một ngoại lệ khác sẽ được ném từ khối try / Catch. Vì vậy, nếu trykhối không ném bất kỳ ngoại lệ nào, ngoại lệ từ close()phương thức sẽ được ném (và giá trị trả về sẽ không được trả về). Nó thậm chí có thể được bắt từ catchkhối hiện tại .
Ruslan Stelmachenko

0

Tôi nghĩ rằng đây là một cái gì đó mà bạn sẽ cần phải giải quyết trên từng trường hợp. Trong một số trường hợp, điều mà bộ phân tích nói là chính xác ở chỗ mã bạn có không tuyệt vời và cần suy nghĩ lại. Nhưng có thể có những trường hợp khác khi ném hoặc thậm chí ném lại có thể là điều tốt nhất. Đó không phải là thứ bạn có thể ủy thác.


0

Đây sẽ là một câu trả lời mang tính khái niệm về lý do tại sao những cảnh báo này có thể tồn tại ngay cả với các phương pháp thử tài nguyên. Thật không may, đó không phải là loại giải pháp dễ dàng mà bạn có thể hy vọng đạt được.

Khôi phục lỗi không thể

finally mô hình một luồng kiểm soát sau giao dịch được thực hiện bất kể giao dịch thành công hay thất bại.

Trong trường hợp thất bại, finallynắm bắt logic được thực thi ở giữa khi khôi phục từ một lỗi, trước khi nó được khôi phục hoàn toàn (trước khi chúng ta catchđến đích).

Hãy tưởng tượng vấn đề khái niệm mà nó đưa ra để gặp lỗi ở giữa khi khôi phục từ một lỗi.

Hãy tưởng tượng một máy chủ cơ sở dữ liệu nơi chúng tôi đang cố gắng thực hiện một giao dịch và nó đã thất bại nửa chừng (giả sử máy chủ hết bộ nhớ ở giữa). Bây giờ máy chủ muốn khôi phục giao dịch đến một điểm như không có gì xảy ra. Tuy nhiên, hãy tưởng tượng nó gặp phải một lỗi khác trong quá trình quay trở lại. Bây giờ chúng tôi cuối cùng đã có một giao dịch nửa cam kết với cơ sở dữ liệu - tính nguyên tử và bản chất không thể chia tách của giao dịch hiện đã bị phá vỡ và tính toàn vẹn của cơ sở dữ liệu sẽ bị xâm phạm.

Vấn đề khái niệm này tồn tại trong bất kỳ ngôn ngữ nào liên quan đến lỗi cho dù đó là C với việc truyền mã lỗi thủ công hay C ++ với các ngoại lệ và hàm hủy hoặc Java có ngoại lệ và finally.

finally không thể thất bại trong các ngôn ngữ cung cấp nó theo cùng một cách phá hủy không thể thất bại trong C ++ trong quá trình gặp ngoại lệ.

Cách duy nhất để tránh vấn đề khó khăn và khái niệm này là đảm bảo rằng quá trình khôi phục giao dịch và giải phóng tài nguyên ở giữa không thể gặp phải ngoại lệ / lỗi đệ quy.

Vì vậy, thiết kế an toàn duy nhất ở đây là một thiết kế writer.close()không thể thất bại. Thường có những cách trong thiết kế để tránh các tình huống trong đó những thứ như vậy có thể thất bại ở giữa phục hồi, khiến nó không thể.

Thật không may, cách duy nhất - phục hồi lỗi không thể thất bại. Cách dễ nhất để đảm bảo điều này là làm cho các loại chức năng "giải phóng tài nguyên" và "tác dụng phụ ngược" không có khả năng thất bại. Thật không dễ dàng - phục hồi lỗi thích hợp là khó và cũng không may khó kiểm tra. Nhưng cách để đạt được nó là đảm bảo rằng bất kỳ chức năng nào "phá hủy", "đóng", "tắt máy", "quay lại", v.v. không thể gặp phải lỗi bên ngoài trong quy trình, vì các chức năng đó thường sẽ cần phải được gọi ở giữa phục hồi từ một lỗi hiện có.

Ví dụ: Ghi nhật ký

Giả sử bạn muốn đăng nhập nội dung trong một finallykhối. Điều này thường sẽ là một vấn đề lớn trừ khi đăng nhập không thể thất bại . Ghi nhật ký gần như chắc chắn có thể thất bại, vì nó có thể muốn nối thêm dữ liệu vào tệp và điều đó có thể dễ dàng tìm thấy nhiều lý do để thất bại.

Vì vậy, giải pháp ở đây là làm cho nó để bất kỳ chức năng ghi nhật ký nào được sử dụng trong finallycác khối không thể ném tới người gọi (nó có thể thất bại, nhưng nó sẽ không ném). Làm thế nào chúng ta có thể làm điều đó? Nếu ngôn ngữ của bạn cho phép ném trong bối cảnh cuối cùng với điều kiện là có một khối thử / bắt được lồng nhau, đó sẽ là một cách để tránh ném vào người gọi bằng cách nuốt các ngoại lệ và biến chúng thành mã lỗi, ví dụ: Có thể ghi nhật ký riêng quá trình hoặc luồng có thể bị lỗi riêng và bên ngoài ngăn xếp lỗi khôi phục lỗi hiện có. Miễn là bạn có thể giao tiếp với quá trình đó mà không có khả năng gặp phải lỗi, điều đó cũng sẽ là ngoại lệ, vì vấn đề an toàn chỉ tồn tại trong kịch bản này nếu chúng ta đệ quy từ trong cùng một luồng.

Trong trường hợp này, chúng ta có thể thoát khỏi lỗi đăng nhập với điều kiện là nó không ném vì không đăng nhập và không làm gì cả trên thế giới (nó không rò rỉ bất kỳ tài nguyên nào hoặc không quay lại tác dụng phụ, ví dụ).

Dù sao, tôi chắc chắn rằng bạn đã có thể bắt đầu tưởng tượng mức độ khó khăn đến mức nào để thực sự tạo ra một phần mềm ngoại lệ - an toàn. Có thể không cần thiết phải tìm kiếm điều này ở mức độ đầy đủ nhất trong tất cả trừ phần mềm quan trọng nhất. Nhưng đáng lưu ý về cách thực sự đạt được sự an toàn ngoại lệ, vì ngay cả các tác giả thư viện có mục đích rất chung cũng thường dò dẫm ở đây và phá hỏng toàn bộ ngoại lệ - an toàn cho ứng dụng của bạn khi sử dụng thư viện.

Một sốFileWriter

Nếu SomeFileWritercó thể ném vào bên trong close, thì tôi sẽ nói rằng nó thường không tương thích với xử lý ngoại lệ, trừ khi bạn không bao giờ cố gắng đóng nó trong bối cảnh liên quan đến việc khôi phục từ một ngoại lệ hiện có. Nếu mã cho nó nằm ngoài tầm kiểm soát của bạn, chúng tôi có thể là SOL nhưng đáng để thông báo cho các tác giả về vấn đề an toàn ngoại lệ trắng trợn này. Nếu nó nằm trong tầm kiểm soát của bạn, khuyến nghị chính của tôi là đảm bảo rằng việc đóng nó không thể xảy ra bằng bất kỳ phương tiện nào cần thiết.

Hãy tưởng tượng nếu một hệ điều hành thực sự có thể không đóng tệp. Bây giờ bất kỳ chương trình nào cố gắng đóng một tệp khi tắt sẽ không tắt được . Chúng ta phải làm gì bây giờ, chỉ cần mở ứng dụng và trong tình trạng lấp lửng (có thể là không), chỉ cần rò rỉ tài nguyên tệp và bỏ qua vấn đề (có thể ổn nếu nó không quá quan trọng)? Thiết kế an toàn nhất: làm cho nó không thể đóng tệp.

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.