TL; DR
Tiền đề
- Các ngoại lệ thời gian chạy nên được ném khi lỗi không thể phục hồi: khi lỗi nằm trong mã và không phụ thuộc vào trạng thái bên ngoài (do đó, việc khôi phục sẽ sửa mã).
- Các ngoại lệ được kiểm tra nên được ném khi mã chính xác, nhưng trạng thái bên ngoài không như mong đợi: không tìm thấy kết nối mạng, không tìm thấy tệp hoặc bị hỏng, v.v.
Phần kết luận
Chúng tôi có thể lấy lại một ngoại lệ được kiểm tra dưới dạng ngoại lệ thời gian chạy nếu mã lan truyền hoặc mã giao diện giả định rằng việc triển khai cơ bản phụ thuộc vào trạng thái bên ngoài, khi nó rõ ràng là không.
Phần này thảo luận về chủ đề khi một trong hai trường hợp ngoại lệ nên được ném. Bạn có thể bỏ qua thanh ngang tiếp theo nếu bạn chỉ muốn đọc một lời giải thích chi tiết hơn cho kết luận.
Khi nào thì thích hợp để ném ngoại lệ thời gian chạy? Bạn ném ngoại lệ thời gian chạy khi rõ ràng mã không chính xác và việc khôi phục đó là phù hợp bằng cách sửa đổi mã.
Ví dụ, thích hợp để ném ngoại lệ thời gian chạy cho các mục sau:
float nan = 1/0;
Điều này sẽ ném một phân chia bởi ngoại lệ thời gian chạy bằng không. Điều này là thích hợp vì mã bị lỗi.
Hoặc, ví dụ, đây là một phần của hàm HashMap
tạo:
public HashMap(int initialCapacity, float loadFactor) {
if (initialCapacity < 0)
throw new IllegalArgumentException("Illegal initial capacity: " + initialCapacity);
if (initialCapacity > MAXIMUM_CAPACITY)
initialCapacity = MAXIMUM_CAPACITY;
if (loadFactor <= 0 || Float.isNaN(loadFactor))
throw new IllegalArgumentException("Illegal load factor: " +
loadFactor);
// more irrelevant code...
}
Để khắc phục dung lượng hoặc hệ số tải ban đầu, bạn nên chỉnh sửa mã để đảm bảo rằng các giá trị chính xác đang được truyền vào. Nó không phụ thuộc vào một số máy chủ ở xa, trên trạng thái hiện tại của đĩa, một tập tin, hoặc một chương trình khác Hàm tạo đó được gọi với các đối số không hợp lệ phụ thuộc vào tính chính xác của mã gọi, có thể là một phép tính sai dẫn đến các tham số không hợp lệ hoặc luồng không phù hợp bị lỗi.
Khi nào thì thích hợp để ném một ngoại lệ được kiểm tra? Bạn ném một ngoại lệ được kiểm tra khi sự cố có thể phục hồi mà không thay đổi mã. Hoặc để đặt nó theo các thuật ngữ khác nhau, bạn ném một ngoại lệ được kiểm tra khi lỗi liên quan đến trạng thái trong khi mã là chính xác.
Bây giờ từ "phục hồi" có thể là khó khăn ở đây. Điều đó có thể có nghĩa là bạn tìm một cách khác để đạt được mục tiêu: Chẳng hạn, nếu máy chủ không phản hồi thì bạn nên thử máy chủ tiếp theo. Nếu loại phục hồi đó có thể phù hợp với trường hợp của bạn thì đó là điều tuyệt vời, nhưng đó không phải là điều duy nhất có nghĩa là phục hồi - phục hồi có thể chỉ đơn giản là hiển thị hộp thoại báo lỗi cho người dùng giải thích điều gì đã xảy ra hoặc nếu đó là ứng dụng máy chủ thì đó có thể là gửi email đến quản trị viên, hoặc thậm chí chỉ đơn thuần là ghi lại lỗi một cách thích hợp và chính xác.
Hãy lấy ví dụ đã được đề cập trong câu trả lời của mrmuggles:
public void dataAccessCode(){
try{
..some code that throws SQLException
}catch(SQLException ex){
throw new RuntimeException(ex);
}
}
Đây không phải là cách chính xác để xử lý ngoại lệ được kiểm tra. Các đơn thuần không đủ năng lực để xử lý các ngoại lệ trong này phạm vi của phương pháp không có nghĩa là các ứng dụng nên bị rơi. Thay vào đó, nó là thích hợp để tuyên truyền nó đến một phạm vi cao hơn như vậy:
public Data dataAccessCode() throws SQLException {
// some code that communicates with the database
}
Cho phép khả năng phục hồi của người gọi:
public void loadDataAndShowUi() {
try {
Data data = dataAccessCode();
showUiForData(data);
} catch(SQLException e) {
// Recover by showing an error alert dialog
showCantLoadDataErrorDialog();
}
}
Các ngoại lệ được kiểm tra là một công cụ phân tích tĩnh, chúng làm rõ cho lập trình viên những gì có thể sai trong một cuộc gọi nhất định mà không yêu cầu họ tìm hiểu việc thực hiện hoặc trải qua quá trình thử và sai. Điều này giúp dễ dàng đảm bảo rằng không có phần nào của luồng lỗi sẽ bị bỏ qua. Lấy lại một ngoại lệ được kiểm tra như một ngoại lệ thời gian chạy đang hoạt động chống lại tính năng phân tích tĩnh tiết kiệm lao động này.
Một điều đáng nói nữa là lớp gọi có bối cảnh tốt hơn về sơ đồ lớn hơn của những thứ như đã được chứng minh ở trên. Có thể có nhiều nguyên nhân cho điều đó dataAccessCode
sẽ được gọi, lý do cụ thể cho cuộc gọi chỉ hiển thị đối với người gọi - do đó có thể đưa ra quyết định tốt hơn khi khôi phục chính xác khi thất bại.
Bây giờ chúng ta đã có sự khác biệt rõ ràng này, chúng ta có thể tiến hành suy luận khi chấp nhận lại một ngoại lệ được kiểm tra như một ngoại lệ thời gian chạy.
Đưa ra ở trên, khi nào thì thích hợp để lấy lại một ngoại lệ được kiểm tra là RuntimeException? Khi mã bạn đang sử dụng giả định sự phụ thuộc vào trạng thái bên ngoài, nhưng bạn có thể khẳng định rõ ràng rằng nó không phụ thuộc vào trạng thái bên ngoài.
Hãy xem xét những điều sau đây:
StringReader sr = new StringReader("{\"test\":\"test\"}");
try {
doesSomethingWithReader(sr); // calls #read, so propagates IOException
} catch (IOException e) {
throw new IllegalStateException(e);
}
Trong ví dụ này, mã đang lan truyền IOException
vì API của Reader
được thiết kế để truy cập trạng thái bên ngoài, tuy nhiên chúng tôi biết rằng StringReader
việc triển khai không truy cập trạng thái bên ngoài. Ở phạm vi này, nơi chúng tôi chắc chắn có thể khẳng định rằng các phần liên quan đến cuộc gọi không truy cập IO hoặc bất kỳ trạng thái bên ngoài nào khác, chúng tôi có thể lấy lại ngoại lệ một cách an toàn như một ngoại lệ trong thời gian chạy mà không gây ngạc nhiên cho các đồng nghiệp không biết về việc triển khai của chúng tôi (và có thể giả sử rằng mã truy cập IO sẽ ném một IOException
).
Lý do để kiểm tra nghiêm ngặt các ngoại lệ phụ thuộc trạng thái bên ngoài là chúng không có tính xác định (không giống như các ngoại lệ phụ thuộc logic, có thể dự đoán sẽ được sao chép mỗi lần cho một phiên bản của mã). Ví dụ: nếu bạn cố chia cho 0, bạn sẽ luôn tạo ra một ngoại lệ. Nếu bạn không chia cho 0, bạn sẽ không bao giờ tạo ra ngoại lệ và bạn không phải xử lý trường hợp ngoại lệ đó, vì điều đó sẽ không bao giờ xảy ra. Tuy nhiên, trong trường hợp truy cập một tệp, thành công một lần không có nghĩa là bạn sẽ thành công vào lần tới - người dùng có thể đã thay đổi quyền, một quy trình khác có thể đã xóa hoặc sửa đổi nó. Vì vậy, bạn luôn phải xử lý trường hợp đặc biệt đó, hoặc bạn có thể có một lỗi.