Làm thế nào để viết một thông điệp ngoại lệ tốt


101

Tôi hiện đang thực hiện đánh giá mã và một trong những điều tôi nhận thấy là số trường hợp ngoại lệ trong đó thông báo ngoại lệ dường như nhắc lại nơi xảy ra ngoại lệ. ví dụ

throw new Exception("BulletListControl: CreateChildControls failed.");

Tất cả ba mục trong thông báo này tôi có thể tìm ra từ phần còn lại của ngoại lệ. Tôi biết lớp và phương thức từ dấu vết ngăn xếp và tôi biết nó thất bại (vì tôi có một ngoại lệ).

Nó khiến tôi suy nghĩ về những gì tôi đã gửi tin nhắn ngoại lệ. Trước tiên tôi tạo ra một lớp ngoại lệ, nếu người ta không tồn tại, vì lý do chung (ví dụ như PropertyNotFoundException- các vì sao ), và sau đó khi tôi vứt nó được thông báo chỉ ra những gì đã xảy ra (ví dụ: "Không thể tìm thấy bất động sản 'IDontExist' trên Node 1234 "- cái gì ). Nơi là trong StackTrace. Khi nào có thể kết thúc trong nhật ký (nếu có). Các cách là dành cho các nhà phát triển để làm việc ra (và sửa chữa)

Bạn có bất cứ lời khuyên khác để ném ngoại lệ? Cụ thể liên quan đến việc tạo các loại mới và thông báo ngoại lệ.


4
Đây có phải là cho các tệp nhật ký hoặc để trình bày cho người dùng?
Jon Hopkins

5
Chỉ để gỡ lỗi. Họ có thể kết thúc trong một bản ghi. Họ sẽ không được trình bày cho người dùng. Tôi không phải là người thích trình bày các thông báo ngoại lệ cho người dùng.
Colin Mackay

Câu trả lời:


72

Tôi sẽ hướng câu trả lời của mình nhiều hơn đến những gì xảy ra sau một ngoại lệ: nó tốt cho cái gì và phần mềm nên ứng xử như thế nào, người dùng của bạn nên làm gì với ngoại lệ đó? Một kỹ thuật tuyệt vời tôi đã bắt đầu trong sự nghiệp của mình là luôn báo cáo các vấn đề và lỗi trong 3 phần: bối cảnh, vấn đề & giải pháp. Việc sử dụng dendsline này thay đổi rất nhiều việc xử lý lỗi và làm cho phần mềm tốt hơn rất nhiều cho các nhà khai thác sử dụng.

Dưới đây là một vài ví dụ.

Context: Saving connection pooling configuration changes to disk.
Problem: Write permission denied on file '/xxx/yyy'.
Solution: Grant write permission to the file.

Trong trường hợp này, người vận hành biết chính xác phải làm gì và tập tin nào phải bị ảnh hưởng. Họ cũng biết rằng những thay đổi tổng hợp kết nối đã không thực hiện và nên được lặp lại.

Context: Sending email to 'abc@xyz.com' regarding 'Blah'.
Problem: SMTP connection refused by server 'mail.xyz.com'.
Solution: Contact the mail server administrator to report a service problem.  The email will be sent later. You may want to tell 'abc@xyz.com' about this problem.

Tôi viết các hệ thống phía máy chủ và các nhà khai thác của tôi nói chung là những người am hiểu công nghệ hỗ trợ dòng đầu tiên. Tôi sẽ viết các thông báo khác nhau cho phần mềm máy tính để bàn có đối tượng khác nhau nhưng bao gồm cùng một thông tin.

Một số điều tuyệt vời xảy ra nếu một người sử dụng kỹ thuật này. Nhà phát triển phần mềm thường thích hợp nhất để biết cách giải quyết các vấn đề trong mã của họ, do đó, giải pháp mã hóa theo cách này khi bạn viết mã có lợi cho những người dùng cuối gặp bất lợi khi tìm giải pháp vì họ thường thiếu thông tin về chính xác những gì phần mềm đã làm. Bất cứ ai đã từng đọc một thông báo lỗi của Oracle đều biết ý tôi là gì.

Điều tuyệt vời thứ hai xuất hiện trong đầu bạn là khi bạn thấy mình đang cố gắng mô tả một giải pháp trong trường hợp ngoại lệ của bạn và bạn đang viết "Kiểm tra X và nếu A thì B khác C". Đây là một dấu hiệu rất rõ ràng và rõ ràng rằng ngoại lệ của bạn đang được kiểm tra sai vị trí. Bạn lập trình viên có khả năng so sánh mọi thứ trong mã để các câu lệnh "nếu" nên được chạy trong mã, tại sao lại liên quan đến người dùng trong một cái gì đó có thể được tự động hóa? Có thể từ mã sâu hơn và ai đó đã thực hiện điều lười biếng và ném IOException từ bất kỳ phương thức nào và bắt lỗi tiềm ẩn từ tất cả chúng trong một khối mã gọi không thể mô tả đầy đủ những gì đã sai, cụ thể là gìbối cảnh là và làm thế nào để khắc phục nó. Điều này khuyến khích bạn viết các lỗi hạt nhỏ hơn, bắt và xử lý chúng ở đúng vị trí trong mã của bạn để bạn có thể nói rõ các bước mà người vận hành nên thực hiện.

Tại một công ty, chúng tôi đã có các nhà khai thác xuất sắc hàng đầu, những người hiểu rõ về phần mềm và giữ "cuốn sách chạy" của riêng họ, điều đó đã làm tăng các giải pháp báo cáo và đề xuất lỗi của chúng tôi. Để nhận ra điều này, phần mềm đã bắt đầu bao gồm các liên kết wiki đến sách chạy trong các trường hợp ngoại lệ để có giải thích cơ bản cũng như liên kết đến các cuộc thảo luận và quan sát nâng cao hơn của các nhà khai thác theo thời gian.

Nếu bạn đã có dendsline để thử kỹ thuật này, nó sẽ trở nên rõ ràng hơn nhiều về những gì bạn nên đặt tên ngoại lệ của mình trong mã khi tạo riêng của bạn. NonRecoverableConfigurationReadFailedException trở thành một chút tốc ký cho những gì bạn sắp mô tả đầy đủ hơn cho nhà điều hành. Tôi thích được dài dòng và tôi nghĩ rằng sẽ dễ dàng hơn cho nhà phát triển tiếp theo chạm vào mã của tôi để giải thích.


1
+1 Đây là một hệ thống tốt. Điều quan trọng hơn: Chắc chắn thông tin có được, hoặc sử dụng các từ ngắn?
Michael K

3
+1 cho tôi thích giải pháp bao gồm, bối cảnh, vấn đề, giải pháp
WebDev

1
Kỹ thuật này rất hữu ích. Tôi chắc chắn sẽ sử dụng nó.
Kid Diamond

Bối cảnh là dữ liệu không cần thiết. Nó đã có mặt trong dấu vết ngăn xếp. Có giải pháp là tốt hơn nhưng không phải lúc nào cũng có thể / hữu ích. Hầu hết các vấn đề là loại ứng dụng dừng một cách duyên dáng hoặc bỏ qua các hoạt động đang chờ xử lý và quay lại vòng lặp thực thi chính của ứng dụng với hy vọng lần sau bạn thành công ... Tên của lớp ngoại lệ sẽ là giải pháp rõ ràng, FileNotFoundhoặc ConnectExceptionbạn biết phải làm gì))
gavenkoa

2
@ThomasFlinkow Trong các ví dụ của bạn sẽ có init (), exec () và cleanup () trong stack stack. Với lược đồ đặt tên tốt trong thư viện và API sạch / dễ hiểu, bạn không cần giải thích chuỗi. Và thất bại nhanh chóng, không mang trạng thái bị hỏng trên toàn hệ thống. Dấu vết và bản ghi nhật ký với ID duy nhất có thể giải thích luồng / trạng thái ứng dụng.
gavenkoa

23

Trong câu hỏi gần đây hơn này, tôi đã đưa ra quan điểm rằng các ngoại lệ không nên chứa một thông điệp nào cả. Theo tôi, việc họ làm là một quan niệm sai lầm rất lớn. Điều tôi đang đề xuất là

"Thông điệp" của ngoại lệ là tên lớp (đủ điều kiện) của ngoại lệ.

Một ngoại lệ nên chứa trong các biến thành viên của chính nó càng nhiều chi tiết càng tốt về chính xác những gì đã xảy ra; ví dụ: IndexOutOfRangeExceptionnên chứa giá trị chỉ mục được coi là không hợp lệ, cũng như các giá trị trên và dưới có giá trị tại thời điểm ngoại lệ được ném. Bằng cách này, bằng cách sử dụng sự phản chiếu, bạn có thể có một thông báo được xây dựng tự động như thế này: IndexOutOfRangeException: index = -1; min=0; max=5và điều này, cùng với dấu vết ngăn xếp, phải là tất cả thông tin khách quan mà bạn cần để khắc phục sự cố. Định dạng nó thành một thông điệp đẹp như "chỉ số -1 không nằm trong khoảng từ 0 đến 5" không thêm bất kỳ giá trị nào.

Trong ví dụ cụ thể của bạn, NodePropertyNotFoundExceptionlớp sẽ chứa tên của thuộc tính không được tìm thấy và tham chiếu đến nút không chứa thuộc tính. Điều này rất quan trọng: nó không nên chứa tên của nút; nó nên chứa một tham chiếu đến nút thực tế. Trong trường hợp cụ thể của bạn, điều này có thể không cần thiết, nhưng đó là vấn đề nguyên tắc và cách suy nghĩ ưa thích: mối quan tâm hàng đầu khi xây dựng một ngoại lệ là nó phải có thể sử dụng được bằng mã có thể bắt được nó. Khả năng sử dụng của con người là một mối quan tâm quan trọng, nhưng chỉ là thứ yếu.

Điều này quan tâm đến tình huống rất bực bội mà bạn có thể đã chứng kiến ​​tại một thời điểm nào đó trong sự nghiệp của mình, nơi bạn có thể đã bắt gặp một ngoại lệ chứa thông tin quan trọng về những gì đã xảy ra trong văn bản tin nhắn, nhưng không phải trong các biến thành viên của nó, vì vậy bạn phải thực hiện phân tích chuỗi văn bản để tìm hiểu điều gì đã xảy ra, hy vọng rằng văn bản thông báo sẽ giữ nguyên trong các phiên bản tương lai của lớp bên dưới và cầu nguyện rằng văn bản thông báo sẽ không bằng tiếng nước ngoài khi chương trình của bạn chạy ở các nước khác

Tất nhiên, vì tên lớp của ngoại lệ là thông điệp của ngoại lệ, (và các biến thành viên của ngoại lệ là các chi tiết cụ thể,) điều này có nghĩa là bạn cần rất nhiều ngoại lệ khác nhau để truyền tải tất cả các thông điệp khác nhau và đó là tốt

Bây giờ, đôi khi, khi chúng ta viết mã, chúng ta gặp một tình huống sai lầm mà chúng ta chỉ muốn nhanh chóng viết mã một throwcâu lệnh và tiến hành viết mã thay vì phải làm gián đoạn những gì chúng ta đang làm để tạo ra một lớp ngoại lệ mới để chúng ta có thể ném nó ngay tại đó Đối với những trường hợp này, tôi có một GenericExceptionlớp trong thực tế chấp nhận một thông điệp chuỗi là tham số thời gian xây dựng, nhưng hàm tạo của lớp ngoại lệ này được trang trí bằng một FIXME XXX TODOnhận xét màu tím lớn rất lớn sáng rõ rằng mỗi lần khởi tạo của lớp này phải được thay thế bằng một khởi tạo của một số lớp ngoại lệ chuyên biệt hơn trước khi hệ thống phần mềm được phát hành, tốt nhất là trước khi mã được cam kết.


8
Nếu bạn đang ở một ngôn ngữ không có GC, như C ++, bạn nên rất cẩn thận về việc đưa các tham chiếu đến dữ liệu tùy ý vào các ngoại lệ mà bạn gửi lên ngăn xếp. Rất có thể, bất cứ điều gì bạn tham chiếu đã bị phá hủy vào thời điểm ngoại lệ bị bắt.
Sebastian Redl

4
@SebastianRedl Đúng. Và điều tương tự cũng có thể áp dụng cho C # và Java nếu nodeđối tượng được bảo vệ bởi mệnh đề sử dụng một lần (trong C #) hoặc try-with-resource (trong Java): đối tượng được lưu trữ với ngoại lệ sẽ bị loại bỏ / đóng, khiến nó trở thành bất hợp pháp để truy cập nó để lấy bất kỳ thông tin hữu ích nào ra khỏi đó tại nơi xử lý ngoại lệ. Tôi cho rằng trong những trường hợp này, một số loại tóm tắt của đối tượng nên được lưu trữ trong ngoại lệ, thay vì chính đối tượng đó. Tôi không thể nghĩ ra một cách chứng minh ngu ngốc để nói chung xử lý việc này cho tất cả các trường hợp.
Mike Nakis

13

Theo nguyên tắc chung, một ngoại lệ sẽ giúp các nhà phát triển xác định chính xác nguyên nhân bằng cách cung cấp thông tin hữu ích (giá trị dự kiến, giá trị thực tế, nguyên nhân / giải pháp có thể, v.v.).

Các loại ngoại lệ mới phải được tạo khi không có loại tích hợp nào có ý nghĩa . Một loại cụ thể cho phép các nhà phát triển khác nắm bắt một ngoại lệ cụ thể và xử lý nó. Nếu nhà phát triển sẽ biết cách xử lý ngoại lệ của bạn nhưng loại đó là Exception, anh ta sẽ không thể xử lý đúng.


+1 - giá trị mong đợi so với giá trị thực tế là vô cùng hữu ích. Trong ví dụ được đưa ra trong câu hỏi, bạn không nên nói đơn giản rằng một phương thức đã thất bại, nhưng tại sao nó lại thất bại (về cơ bản, lệnh chính xác đã thất bại và các trường hợp gây ra sự thất bại.)
Felix Dombek

4

Trong .NET không bao giờ throw new Exception("...")(như tác giả của câu hỏi đã hiển thị). Ngoại lệ là loại Ngoại lệ gốc và không được phép ném trực tiếp. Thay vào đó, ném một trong các loại ngoại lệ .NET dẫn xuất hoặc tạo ngoại lệ tùy chỉnh của riêng bạn xuất phát từ Ngoại lệ (hoặc loại ngoại lệ khác).

Tại sao không ném Ngoại lệ? Bởi vì ném Exception không có gì để mô tả ngoại lệ của bạn và buộc mã cuộc gọi của bạn viết mã như thế catch(Exception ex) { ... }thường không phải là một điều tốt! :-).


2

Những thứ bạn muốn tìm kiếm để "thêm" vào ngoại lệ là những yếu tố dữ liệu không có trong ngoại lệ hoặc theo dõi ngăn xếp. Cho dù đó là một phần của "tin nhắn" hay cần được đính kèm khi đăng nhập là một câu hỏi thú vị.

Như bạn đã lưu ý, ngoại lệ có thể cho bạn biết điều gì, stacktrace có thể cho bạn biết nhưng "tại sao" có thể liên quan nhiều hơn (nên là, người ta sẽ hy vọng) hơn là chỉ nhìn ngang qua một hoặc hai dòng và nói " doh! Tất nhiên rồi ". Điều này thậm chí còn đúng hơn khi ghi nhật ký lỗi trong mã sản xuất - Tôi thường xuyên bị cắn bởi dữ liệu xấu được tìm thấy trong một hệ thống trực tiếp không tồn tại trong các hệ thống thử nghiệm của chúng tôi. Đôi khi đơn giản như việc biết ID của bản ghi trong cơ sở dữ liệu gây ra lỗi (hoặc đóng góp) cho lỗi có thể tiết kiệm đáng kể thời gian.

Vì vậy, ... Được liệt kê hoặc, đối với .NET, được thêm vào bộ sưu tập dữ liệu ngoại lệ được ghi lại (cf @Plip!):

  • Các tham số (điều này có thể có một chút thú vị - bạn không thể thêm vào bộ sưu tập dữ liệu nếu nó không tuần tự hóa và đôi khi một tham số có thể phức tạp một cách đáng ngạc nhiên)
  • Dữ liệu bổ sung được trả về bởi ADO.NET hoặc Linq thành SQL hoặc tương tự (điều này cũng có thể có một chút thú vị!).
  • Bất cứ điều gì khác có thể không rõ ràng.

Tất nhiên, một số điều bạn sẽ không biết mình cần cho đến khi bạn không có chúng trong báo cáo / nhật ký lỗi ban đầu. Một số điều bạn không nhận ra mình có thể nhận được cho đến khi bạn thấy bạn cần chúng.


0

Ngoại lệ để làm gì?

(1) Nói với người dùng có gì đó không ổn?

Đây phải là giải pháp cuối cùng, bởi vì mã của bạn sẽ được can thiệp và hiển thị cho họ một cái gì đó "đẹp hơn" so với Ngoại lệ.

Thông báo "lỗi" cần nêu rõ ràng và ngắn gọn những gì đã sai và nếu có bất cứ điều gì, người dùng có thể làm gì để khôi phục từ tình trạng lỗi.

ví dụ: "Vui lòng không nhấn nút này lần nữa"

(2) Nói với một nhà phát triển khi gặp sự cố?

Đây là loại điều mà bạn đăng nhập vào một tệp để phân tích tiếp theo.
Các Stack Trace sẽ cho các nhà phát triển nơi các mã đã phá vỡ; tin nhắn nên, một lần nữa, chỉ ra những gì đã sai.

(3) Nói với một trình xử lý ngoại lệ (tức là mã) rằng có gì đó không ổn?

Các Loại của ngoại lệ sẽ quyết định xử lý ngoại lệ sẽ nhận được để nhìn vào nó và các thuộc tính xác định trên đối tượng ngoại lệ sẽ cho phép xử lý để đối phó với nó.

Thông điệp của Exception hoàn toàn không liên quan .


-3

Đừng tạo kiểu mới nếu bạn có thể giúp nó. Chúng có thể gây thêm nhầm lẫn, phức tạp và dẫn đến nhiều mã hơn để duy trì. Họ có thể làm cho mã của bạn phải mở rộng. Thiết kế một hệ thống phân cấp ngoại lệ đòi hỏi rất nhiều mặc dù và thử nghiệm. Đó không phải là một suy nghĩ sau. Tốt nhất là sử dụng hệ thống phân cấp ngoại lệ được tích hợp sẵn trong ngôn ngữ.

Nội dung của tin nhắn ngoại lệ phụ thuộc vào người nhận tin nhắn - vì vậy bạn phải đặt mình vào vị trí của người đó.

Một kỹ sư hỗ trợ sẽ cần có khả năng xác định nguồn lỗi nhanh nhất có thể. Bao gồm một chuỗi mô tả ngắn cộng với bất kỳ dữ liệu nào có thể giúp khắc phục sự cố. Luôn bao gồm theo dõi ngăn xếp - nếu bạn có thể - đây sẽ là nguồn thông tin thực sự duy nhất.

Trình bày lỗi cho người dùng chung trong hệ thống của bạn tùy thuộc vào loại lỗi: nếu người dùng có thể khắc phục sự cố, bằng cách cung cấp đầu vào khác nhau chẳng hạn, thì cần có thông báo mô tả ngắn gọn. Nếu người dùng không thể khắc phục sự cố, thì tốt nhất là nêu lỗi đã xảy ra và ghi / gửi lỗi để hỗ trợ (sử dụng các hướng dẫn ở trên).

Ngoài ra - đừng ném lên một "HALT ERROR!" biểu tượng. Đó là một lỗi - đó không phải là ngày tận thế.

Vì vậy, tóm lại: suy nghĩ về các tác nhân và trường hợp sử dụng cho hệ thống của bạn. Đặt mình vào đôi giày của người dùng. Hãy giúp đỡ Hãy tử tế Hãy suy nghĩ về điều này lên phía trước trong thiết kế hệ thống của bạn. Từ quan điểm của người dùng của bạn - những trường hợp ngoại lệ này và cách hệ thống xử lý chúng, cũng quan trọng như các trường hợp thông thường trong hệ thống của bạn.


10
Tôi không đồng ý. Có nhiều lý do chính đáng để thực hiện các ngoại lệ của riêng bạn khi API ngôn ngữ không đáp ứng nhu cầu chính xác của bạn. Một lý do là trong một phương pháp mà nhiều điều có thể thất bại, bạn có thể viết các mệnh đề bắt khác nhau cho các loại ngoại lệ khác nhau, nơi bạn có thể phản ứng với vấn đề chính xác. Một cách khác là bạn có thể tách nhiều lớp ngoại lệ đại diện cho các lớp trừu tượng khác nhau, trong đó lớp chính xác mà một ngoại lệ thuộc về có thể được mã hóa theo kiểu của nó. Chỉ cần sử dụng "Ngoại lệ" hoặc "IllegalStateException" và một chuỗi thông báo không giúp được gì nhiều ở đó.
Felix Dombek

1
Tôi cũng không đồng ý. Các loại ngoại lệ sẽ có ý nghĩa đối với mã gọi sẽ tiêu thụ nó. Ví dụ: nếu tôi đang gọi một khung công tác và điều này gây ra FileDoesNotExistException trong nội bộ thì điều này có thể không có ý nghĩa gì đối với tôi với tư cách là người gọi của khung công tác. Thay vào đó, có thể tốt hơn để tạo một ngoại lệ tùy chỉnh và chuyển vào ngoại lệ được ném thành ngoại lệ bên trong.
bytedev
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.