Tuyên truyền ngoại lệ: Khi nào tôi nên bắt ngoại lệ?


44

Phương thứcA gọi một Phương thứcB mà lần lượt gọi Phương thức.

KHÔNG có xử lý ngoại lệ trong Phương thứcB hoặc Phương thứcC. Nhưng có xử lý ngoại lệ trong Phương thứcA.

Trong Phương thứcC, một ngoại lệ xảy ra.

Bây giờ, ngoại lệ đó đang sủi bọt lên Phương thức, xử lý nó một cách thích hợp.

Điều gì là sai với điều này?

Trong tâm trí của tôi, tại một thời điểm nào đó, người gọi sẽ thực thi Phương thứcB hoặc Phương thức và khi các ngoại lệ xảy ra trong các phương thức đó, những gì sẽ thu được từ việc xử lý các ngoại lệ bên trong các phương thức đó, về cơ bản chỉ là một khối thử / bắt / cuối cùng thay vì chỉ cho phép họ bong bóng lên đến callee?

Tuyên bố hoặc sự đồng thuận xung quanh việc xử lý ngoại lệ là đưa ra khi việc thực thi không thể tiếp tục do điều đó - một ngoại lệ. Tôi hiểu rồi Nhưng tại sao không bắt ngoại lệ xa hơn trên chuỗi thay vì phải thử / bắt khối hoàn toàn.

Tôi hiểu nó khi bạn cần giải phóng tài nguyên. Đó là một vấn đề hoàn toàn khác.


46
Tại sao bạn nghĩ rằng sự đồng thuận là có một chuỗi thông qua các sản phẩm khai thác?
Caleth

Với một IDE tốt và một phong cách mã hóa phù hợp, bạn có thể biết rằng một số ngoại lệ có thể được ném khi một phương thức được gọi. Xử lý nó hoặc cho phép nó được tuyên truyền là quyết định của người gọi. Tôi không thấy bất kỳ vấn đề với điều này.
Hiếu Lê

14
Nếu một phương thức không thể xử lý ngoại lệ và chỉ đơn thuần là điều chỉnh lại nó, tôi sẽ nói đó là mùi mã. Nếu một phương thức không thể xử lý ngoại lệ và không cần phải làm gì khác khi ném ngoại lệ thì không cần phải có try-catchkhối nào cả.
Greg Burghardt

7
"Có chuyện gì với cái này vậy?" : không có gì
Ewan

5
Bắt qua (không bao gồm các ngoại lệ trong các loại khác nhau hoặc bất cứ điều gì tương tự) đánh bại toàn bộ mục đích của các ngoại lệ. Ném ngoại lệ là một cơ chế phức tạp, và nó được xây dựng có chủ ý. Nếu các sản phẩm khai thác là trường hợp sử dụng dự định, thì tất cả những gì bạn cần là triển khai một Result<T>loại (một loại lưu trữ kết quả của một tính toán hoặc một lỗi) và trả về nó từ các hàm ném khác của bạn. Tuyên truyền một lỗi lên ngăn xếp sẽ đòi hỏi phải đọc mọi giá trị trả về, kiểm tra xem đó có phải là lỗi không và trả về lỗi nếu có.
Alexander

Câu trả lời:


139

Theo nguyên tắc chung, đừng bắt ngoại lệ trừ khi bạn biết phải làm gì với chúng. Nếu MethodC ném một ngoại lệ, nhưng Phương thứcB không có cách nào hữu ích để xử lý nó, thì nó sẽ cho phép ngoại lệ đó lan truyền lên Phương thứcA.

Những lý do duy nhất tại sao một phương thức nên có cơ chế bắt và rút lại là:

  • Bạn muốn chuyển đổi một ngoại lệ thành một ngoại lệ khác có ý nghĩa hơn đối với người gọi ở trên.
  • Bạn muốn thêm thông tin vào ngoại lệ.
  • Bạn cần một mệnh đề bắt để dọn sạch các tài nguyên sẽ bị rò rỉ mà không có tài nguyên.

Mặt khác, việc bắt các ngoại lệ ở cấp độ sai có xu hướng dẫn đến mã bị âm thầm thất bại mà không cung cấp bất kỳ phản hồi hữu ích nào cho mã gọi (và cuối cùng là người dùng phần mềm). Việc thay thế bắt một ngoại lệ và sau đó ngay lập tức nghĩ lại là vô nghĩa.


28
@GregBurghardt nếu ngôn ngữ của bạn có gì đó tương tự try ... finally ..., thì hãy sử dụng ngôn ngữ đó, không bắt và
suy nghĩ lại

19
"Bắt một ngoại lệ và sau đó ngay lập tức suy nghĩ lại là vô nghĩa" Tùy thuộc vào ngôn ngữ và cách bạn thực hiện điều này, nó có thể gây hại tích cực cho codebase. Thông thường mọi người cố gắng loại bỏ rất nhiều thông tin về ngoại lệ, chẳng hạn như stacktrace ban đầu. Tôi đã xử lý mã trong đó người gọi nhận được một ngoại lệ hoàn toàn sai lệch về những gì đã xảy ra và ở đâu.
JimmyJames

7
"Đừng bắt ngoại lệ trừ khi bạn biết phải làm gì với chúng". Điều đó nghe có vẻ hợp lý trong cái nhìn đầu tiên, nhưng gây ra vấn đề về sau. Những gì bạn đang làm ở đây là rò rỉ chi tiết triển khai cho người gọi của bạn. Hãy tưởng tượng rằng bạn đang sử dụng một ORM cụ thể trong quá trình triển khai để tải dữ liệu. Nếu bạn không nắm bắt được các ngoại lệ cụ thể của ORM mà chỉ để chúng nổi lên, bạn không thể thay thế lớp dữ liệu của mình mà không phá vỡ tính tương thích với người dùng hiện tại. Đó là một trong những trường hợp rõ ràng hơn, nhưng nó có thể trở nên khá ngấm ngầm và khó nắm bắt.
Voo

11
@Voo Trong ví dụ của bạn, bạn làm biết phải làm gì với nó. Bao bọc nó trong một ngoại lệ được ghi lại cụ thể theo mã của bạn, ví dụ LoadDataExceptionbao gồm các chi tiết ngoại lệ ban đầu theo các tính năng ngôn ngữ của bạn, để các nhà bảo trì trong tương lai có thể thấy nguyên nhân gốc mà không phải đính kèm trình gỡ lỗi và tìm ra cách tái tạo vấn đề.
Colin Young

14
@Voo Bạn dường như đã bỏ lỡ "Bạn muốn chuyển đổi một ngoại lệ sang một ngoại lệ khác có ý nghĩa hơn đối với người gọi ở trên" vì các tình huống bắt / rút lại.
jpmc26

21

Điều gì là sai với điều này?

Hoàn toàn không có gì.

Bây giờ, ngoại lệ đó đang sủi bọt lên Phương thức, xử lý nó một cách thích hợp.

"Xử lý nó một cách thích hợp" là phần quan trọng. Đó là mấu chốt của Xử lý ngoại lệ có cấu trúc.

Nếu mã của bạn có thể làm điều gì đó "hữu ích" với Ngoại lệ, hãy thực hiện. Nếu không thì hãy để yên.

. . . tại sao không bắt ngoại lệ xa hơn trên chuỗi thay vì phải thử / bắt khối hoàn toàn.

Đó chính xác là những gì bạn nên làm. Nếu bạn đang đọc mã có trình xử lý / bộ nhớ lại "hết cỡ", thì có lẽ bạn [có thể] đang đọc một số mã khá kém.

Đáng buồn thay, một số Nhà phát triển chỉ xem các khối bắt là mã "nồi hơi" mà họ ném vào (không có ý định chơi chữ) cho mọi phương thức họ viết, thường là vì họ không thực sự "lấy" Xử lý ngoại lệ và nghĩ rằng họ phải thêm một cái gì đó rằng Ngoại lệ không "thoát" và giết chương trình của họ.

Một phần của khó khăn ở đây là, hầu hết thời gian, vấn đề này thậm chí sẽ không được chú ý, bởi vì Ngoại lệ không bị ném mọi lúc, nhưng khi , chương trình sẽ lãng phí rất nhiều thời gian và Nỗ lực dần dần bỏ chọn ngăn xếp cuộc gọi để đến một nơi thực sự có ích với Exception.


7
Điều tồi tệ hơn nữa là khi ứng dụng bắt được ngoại lệ, và sau đó ghi lại nó (nơi hy vọng nó sẽ không ngồi đó mãi mãi) và cố gắng tiếp tục như bình thường, ngay cả khi nó thực sự không thể.
Solomon Ucko

1
@SolomonUcko: Vâng, nó phụ thuộc. Ví dụ, nếu bạn đang viết một máy chủ RPC đơn giản và một bong bóng ngoại lệ chưa được xử lý cho đến vòng lặp sự kiện chính, thì tùy chọn hợp lý duy nhất của bạn là ghi nhật ký, gửi lỗi RPC đến máy ngang hàng từ xa và tiếp tục xử lý các sự kiện. Một cách khác là giết toàn bộ chương trình, điều này sẽ khiến các hạt SRE của bạn bị hỏng khi máy chủ chết trong quá trình sản xuất.
Kevin

@Kevin Trong trường hợp đó, cần có một mức duy nhất catchở mức cao nhất có thể ghi lại lỗi và trả về lỗi. Không phải catchkhối rắc khắp nơi. Nếu bạn không muốn liệt kê mọi ngoại lệ được kiểm tra có thể có (bằng các ngôn ngữ như Java), chỉ cần bọc nó RuntimeExceptionthay vì đăng nhập vào đó, cố gắng tiếp tục và gặp nhiều lỗi hơn hoặc thậm chí là các lỗ hổng.
Solomon Ucko

8

Bạn phải tạo sự khác biệt giữa Thư viện và Ứng dụng.

Thư viện có thể ném ngoại lệ không bị bắt

Khi bạn thiết kế một thư viện, đến một lúc nào đó bạn phải suy nghĩ về những gì có thể sai. Các tham số có thể nằm trong phạm vi sai hoặc null, tài nguyên bên ngoài có thể không khả dụng, v.v.

Thư viện của bạn thường không có cách nào để đối phó với chúng một cách hợp lý . Giải pháp hợp lý duy nhất là đưa ra một Ngoại lệ phù hợp và để nhà phát triển Ứng dụng giải quyết.

Các ứng dụng phải luôn luôn ở một số điểm ngoại lệ

Khi một ngoại lệ bị bắt, tôi muốn phân loại chúng là Lỗi hoặc Lỗi nghiêm trọng . Lỗi thông thường có nghĩa là một thao tác trong Ứng dụng của tôi không thành công. Chẳng hạn, một tài liệu mở không thể được lưu, vì đích không thể ghi được. Suy nghĩ hợp lý duy nhất để Ứng dụng thực hiện là thông báo cho người dùng rằng thao tác không thể hoàn thành thành công, cung cấp thông tin có thể đọc được cho con người liên quan đến vấn đề và sau đó để người dùng quyết định làm gì tiếp theo.

Một Lỗi Fatal là một lỗi logic ứng dụng chính không thể phục hồi từ. Chẳng hạn, nếu trình điều khiển thiết bị đồ họa gặp sự cố trong trò chơi video, không có cách nào để Ứng dụng "duyên dáng" thông báo cho người dùng. Trong trường hợp này, một tệp nhật ký nên được viết và, nếu có thể, người dùng nên được thông báo bằng cách này hay cách khác.

Ngay cả trong trường hợp nghiêm trọng như vậy, Ứng dụng nên xử lý Ngoại lệ này một cách có ý nghĩa. Điều này có thể bao gồm viết tệp Nhật ký, gửi Báo cáo sự cố, v.v. Không có lý do gì để Ứng dụng không phản hồi Ngoại lệ theo một cách nào đó.


Thật vậy, nếu bạn có một thư viện cho một cái gì đó như thao tác ghi đĩa hoặc, giả sử, thao tác phần cứng khác, bạn có thể kết thúc trong tất cả các loại sự kiện bất ngờ. Điều gì nếu một ổ đĩa cứng bị kéo ra trong khi viết? Những gì một ổ đĩa CD ra trong khi đọc? Điều đó nằm ngoài tầm kiểm soát của bạn và trong khi bạn có thể làm điều gì đó (ví dụ: giả vờ thành công), thì thường là một cách tốt để ném ngoại lệ cho người dùng thư viện và để họ quyết định. Có lẽ một HDDPluggedOutDuringWritingExceptioncó thể được xử lý và không gây tử vong cho ứng dụng. Chương trình có thể quyết định phải làm gì với điều đó.
VLAZ

1
@VLAZ Cái gì gây tử vong so với không gây tử vong là điều mà ứng dụng phải quyết định. Thư viện nên nói những gì đã xảy ra. Ứng dụng phải quyết định cách phản ứng với nó.
MechMK1

0

Có gì sai với mẫu bạn mô tả là phương pháp A sẽ không có cách phân biệt giữa ba kịch bản:

  1. Phương pháp B thất bại trong một dự đoán.

  2. Phương pháp C thất bại theo cách không lường trước được bằng phương pháp B, nhưng trong khi phương pháp B đang thực hiện một thao tác có thể bị từ bỏ một cách an toàn.

  3. Phương pháp C thất bại theo cách không được dự đoán trước bởi phương pháp B, nhưng trong khi phương pháp B đang thực hiện một thao tác đặt mọi thứ ở trạng thái được cho là tạm thời tạm thời mà B không thể dọn dẹp vì thất bại của C.

Cách duy nhất mà phương pháp A sẽ có thể phân biệt các kịch bản đó là nếu ngoại lệ được ném từ B bao gồm đủ thông tin cho mục đích đó hoặc nếu ngăn xếp mở ra cho phương thức B làm cho đối tượng bị bỏ lại ở trạng thái bị vô hiệu rõ ràng . Thật không may, hầu hết các khung ngoại lệ làm cho cả hai mẫu trở nên lúng túng, buộc các lập trình viên phải đưa ra quyết định thiết kế "ít tệ hơn".


2
Kịch bản 2 và 3 là các lỗi trong phương thức B. Phương pháp A không nên cố gắng sửa các lỗi đó.
Sjoerd

@Sjoerd: Phương pháp B được cho là như thế nào để dự đoán tất cả các cách mà phương pháp C có thể thất bại?
supercat

Bằng các mẫu nổi tiếng như thực hiện tất cả các hoạt động có thể ném vào các biến tạm thời, sau đó với các hoạt động không thể ném - ví dụ: hoán đổi - hoán đổi cái cũ với trạng thái mới. Một mô hình khác là xác định các hoạt động có thể được lặp lại một cách an toàn, vì vậy bạn có thể thử lại hoạt động mà không sợ làm hỏng. Có đầy đủ sách về cách viết 'mã an toàn ngoại lệ', vì vậy tôi không thể nói cho bạn mọi thứ ở đây.
Sjoerd

Đây là một điểm tốt cho việc không sử dụng ngoại lệ nào cả (sẽ là một quyết định tuyệt vời imho). Nhưng tôi đoán, nó không thực sự trả lời câu hỏi, vì OP dường như có ý định sử dụng các ngoại lệ ở nơi đầu tiên, và chỉ hỏi nơi bắt nên ở đâu.
cmaster

@Sjoerd Phương pháp B trở nên dễ dàng hơn rất nhiều để lý do nếu ngoại lệ bị cấm bởi ngôn ngữ. Bởi vì trong trường hợp đó, bạn thực sự thấy tất cả các đường dẫn điều khiển qua B và bạn không phải đoán lần thứ hai toán tử nào có thể bị quá tải theo kiểu ném (C ++) để tránh kịch bản 3. Chúng tôi đang trả nhiều tiền về mã sự rõ ràng và an toàn để lười biếng trả lại lỗi bằng cách "chỉ" ném ngoại lệ. Bởi vì, cuối cùng, xử lý lỗi một phần quan trọng của mã.
cmaster
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.