Các quy ước cho các trường hợp ngoại lệ hoặc mã lỗi


117

Hôm qua tôi đã có một cuộc tranh luận sôi nổi với đồng nghiệp về phương pháp báo cáo lỗi ưa thích. Chủ yếu chúng tôi đã thảo luận về việc sử dụng các ngoại lệ hoặc mã lỗi để báo cáo lỗi giữa các lớp hoặc mô-đun ứng dụng.

Bạn sử dụng quy tắc nào để quyết định xem bạn có ném ngoại lệ hoặc trả lại mã lỗi cho báo cáo lỗi không?

Câu trả lời:


81

Trong công cụ cấp cao, ngoại lệ; trong công cụ cấp thấp, mã lỗi.

Hành vi mặc định của một ngoại lệ là làm mất ngăn xếp và dừng chương trình, nếu tôi đang viết một kịch bản và tôi lấy một khóa không có trong từ điển thì đó có thể là một lỗi và tôi muốn chương trình tạm dừng và để tôi biết tất cả về điều đó

Tuy nhiên, nếu tôi đang viết một đoạn mã mà tôi phải biết hành vi của mọi tình huống có thể xảy ra, thì tôi muốn mã lỗi. Mặt khác, tôi phải biết mọi ngoại lệ có thể được ném bởi mọi dòng trong chức năng của mình để biết nó sẽ làm gì (Đọc Ngoại lệ căn cứ một hãng hàng không để có ý tưởng về việc này khó đến mức nào). Thật tẻ nhạt và khó để viết mã phản ứng phù hợp với mọi tình huống (kể cả những người không hài lòng), nhưng đó là vì viết mã không có lỗi là tẻ nhạt và khó, không phải vì bạn đang truyền mã lỗi.

Cả Raymond Chen Joel đã đưa ra một số lập luận hùng hồn chống lại việc sử dụng ngoại lệ cho mọi thứ.


5
+1 để chỉ ra rằng bối cảnh có liên quan đến chiến lược xử lý lỗi.
alx9r

2
@Tom, Điểm tốt, nhưng ngoại lệ được đảm bảo để được bắt. Làm thế nào để chúng tôi đảm bảo rằng mã lỗi được bắt và không bị bỏ qua âm thầm do lỗi?
Pacerier

13
Vì vậy, lý lẽ duy nhất của bạn chống lại các ngoại lệ là điều gì đó tồi tệ có thể xảy ra khi bạn quên bắt ngoại lệ nhưng bạn không xem xét tình huống có khả năng quên kiểm tra giá trị trả về cho lỗi không? Chưa kể bạn nhận được một dấu vết ngăn xếp khi bạn quên bắt ngoại lệ trong khi bạn không nhận được gì khi bạn quên kiểm tra mã lỗi.
Esailija

3
C ++ 17 giới thiệu nodiscardthuộc tính sẽ đưa ra cảnh báo trình biên dịch nếu giá trị trả về của hàm không được lưu trữ. Giúp một chút để bắt lỗi kiểm tra mã lỗi. Ví dụ: godbolt.org/g/6i6E0B
Zitrax

5
Nhận xét của @ Esailija chính xác ở điểm này. Đối số chống lại các trường hợp ngoại lệ ở đây có API dựa trên mã lỗi giả định trong đó tất cả các mã lỗi được ghi lại và một lập trình viên giả định đọc tài liệu, xác định chính xác tất cả các trường hợp lỗi có thể có trong ứng dụng của cô ấy và viết mã để xử lý từng lỗi đó , sau đó so sánh kịch bản đó với một API và lập trình viên dựa trên ngoại lệ giả định, vì một lý do nào đó, một trong những bước đó bị sai ... mặc dù nó dễ dàng như nhau ( dễ dàng hơn ) để có được tất cả các bước đó trong API dựa trên ngoại lệ.
Đánh dấu Amery

61

Tôi thường thích các trường hợp ngoại lệ, bởi vì chúng có nhiều thông tin theo ngữ cảnh hơn và có thể truyền tải (khi được sử dụng đúng cách) lỗi cho lập trình viên một cách rõ ràng hơn.

Mặt khác, mã lỗi nhẹ hơn ngoại lệ nhưng khó bảo trì hơn. Kiểm tra lỗi vô tình có thể được bỏ qua. Mã lỗi khó bảo trì hơn vì bạn phải giữ một danh mục với tất cả các mã lỗi và sau đó bật kết quả để xem lỗi nào được đưa ra. Phạm vi lỗi có thể giúp ích ở đây, bởi vì nếu điều duy nhất chúng ta quan tâm là liệu chúng ta có lỗi hay không, thì việc kiểm tra đơn giản hơn (ví dụ: mã lỗi HRESULT lớn hơn hoặc bằng 0 là thành công và ít hơn 0 là thất bại). Chúng có thể vô tình bị bỏ qua vì không có chương trình buộc nhà phát triển sẽ kiểm tra mã lỗi. Mặt khác, bạn không thể bỏ qua các ngoại lệ.

Để tóm tắt, tôi thích các ngoại lệ hơn mã lỗi trong hầu hết các tình huống.


4
"Mã lỗi nhẹ hơn ngoại lệ" phụ thuộc vào những gì bạn đo lường và cách bạn đo lường. Khá dễ dàng để đưa ra các thử nghiệm cho thấy các API dựa trên ngoại lệ có thể nhanh hơn nhiều.
Vịt Mooing

1
@smink, Điểm tốt, nhưng làm thế nào để chúng tôi giải quyết vấn đề ngoại lệ? Mã lỗi không chỉ nhẹ, về cơ bản là không trọng lượng ; các trường hợp ngoại lệ không chỉ có trọng lượng trung bình, chúng là các đối tượng nặng có chứa thông tin ngăn xếp và các thứ linh tinh mà chúng ta không sử dụng.
Pacerier

3
@Pacerier: Bạn chỉ đang xem xét các bài kiểm tra mà ngoại lệ được đưa ra. Bạn đúng 100% rằng việc ném ngoại lệ C ++ chậm hơn đáng kể so với trả lại mã lỗi. Chống tay, không tranh luận. Trường hợp chúng tôi làm khác nhau, là 99,999% mã khác. Với các ngoại lệ, chúng tôi không phải kiểm tra trả về mã lỗi giữa mỗi câu lệnh, làm chođó nhanh hơn 1-50% (hoặc không, tùy thuộc vào trình biên dịch của bạn). Điều đó có nghĩa là mã đầy đủ có thể nhanh hơn hoặc chậm hơn, tùy thuộc hoàn toàn vào cách viết mã và tần suất ném ngoại lệ.
Vịt Mooing

1
@Pacerier: Với bài kiểm tra nhân tạo này tôi vừa viết, mã dựa trên ngoại lệ nhanh như mã lỗi trong MSVC và Clang, mặc dù không phải là GCC: coliru.stacked-crooking.com/a/e81694e5c508945b (thời gian ở phía dưới). Dường như các cờ GCC mà tôi đã sử dụng đã tạo ra các ngoại lệ chậm bất thường so với các trình biên dịch khác. Tôi thiên vị, vì vậy hãy phê bình bài kiểm tra của tôi và thoải mái thử một biến thể khác.
Vịt Mooing

4
@Mooing Duck, Nếu bạn đã kiểm tra cả mã lỗi và ngoại lệ cùng một lúc, thì bạn phải bật ngoại lệ. Nếu đó là trường hợp, kết quả của bạn sẽ đề xuất rằng việc sử dụng mã lỗi với chi phí xử lý ngoại lệ không chậm hơn so với chỉ sử dụng ngoại lệ. Các thử nghiệm mã lỗi sẽ cần phải được thực hiện với các ngoại lệ bị vô hiệu hóa để có được kết quả có ý nghĩa.
Mika Haarahiltunen

24

Tôi thích ngoại lệ vì

  • họ làm gián đoạn dòng logic
  • họ được hưởng lợi từ hệ thống phân cấp lớp mang lại nhiều tính năng / chức năng hơn
  • khi được sử dụng đúng cách có thể biểu thị một loạt lỗi (ví dụ: UnlimitedMethodCallException cũng là LogicException, vì cả hai đều xảy ra khi có lỗi trong mã của bạn có thể phát hiện được trước khi chạy) và
  • chúng có thể được sử dụng để cải thiện lỗi (tức là định nghĩa lớp FileReadException có thể chứa mã để kiểm tra xem tệp có tồn tại hay đã bị khóa, v.v.)

2
Điểm thứ tư của bạn không phải là một điểm công bằng: Trạng thái lỗi khi được chuyển đổi thành đối tượng, cũng có thể chứa mã để kiểm tra xem tệp có tồn tại hay bị khóa hay không, v.v ... Nó chỉ đơn giản là một biến thể của stackoverflow.com/a/3157182/632951
Pacerier

1
"Họ làm gián đoạn dòng logic". Các ngoại lệ có ít nhiều ảnh hưởng củagoto
peterchaula

22

Mã lỗi có thể bị bỏ qua (và thường là!) Bởi những người gọi các chức năng của bạn. Các ngoại lệ ít nhất buộc họ phải xử lý lỗi theo một cách nào đó. Ngay cả khi phiên bản của họ đối phó với nó là để có một trình xử lý bắt trống (thở dài).


17

Ngoại lệ về mã lỗi, không có nghi ngờ gì về nó. Bạn nhận được nhiều lợi ích tương tự từ các ngoại lệ như bạn làm với mã lỗi, nhưng cũng nhiều hơn nữa, không có thiếu sót của mã lỗi. Điểm trừ duy nhất về ngoại lệ là nó hơi quá đầu; nhưng trong thời đại ngày nay, chi phí đó nên được coi là không đáng kể đối với gần như tất cả các ứng dụng.

Dưới đây là một số bài viết thảo luận, so sánh và đối chiếu hai kỹ thuật:

Có một số liên kết tốt trong những liên kết có thể cung cấp cho bạn đọc thêm.


16

Tôi sẽ không bao giờ trộn lẫn hai mô hình ... thật khó để chuyển đổi từ mô hình này sang mô hình khác khi bạn di chuyển từ một phần của ngăn xếp đang sử dụng mã lỗi, sang một phần cao hơn đang sử dụng ngoại lệ.

Các ngoại lệ dành cho "mọi thứ ngăn chặn hoặc ngăn cản phương thức hoặc chương trình con thực hiện những gì bạn yêu cầu" ... KHÔNG chuyển thông điệp trở lại về sự bất thường hoặc trường hợp bất thường hoặc trạng thái của hệ thống, v.v. Sử dụng giá trị trả về hoặc ref (hoặc ra) tham số cho điều đó.

Các ngoại lệ cho phép các phương thức được viết (và sử dụng) với ngữ nghĩa phụ thuộc vào chức năng của phương thức, tức là một phương thức trả về một đối tượng Nhân viên hoặc Danh sách nhân viên có thể được nhập để làm điều đó và bạn có thể sử dụng nó bằng cách gọi.

Employee EmpOfMonth = GetEmployeeOfTheMonth();

Với mã lỗi, tất cả các phương thức đều trả về mã lỗi, vì vậy, đối với những phương thức cần trả về một thứ khác được sử dụng bởi mã gọi, bạn phải truyền một biến tham chiếu để được nhập với dữ liệu đó và kiểm tra giá trị trả về cho mã lỗi và xử lý nó, trên mọi hàm gọi hoặc phương thức.

Employee EmpOfMonth; 
if (getEmployeeOfTheMonth(ref EmpOfMonth) == ERROR)
    // code to Handle the error here

Nếu bạn viết mã sao cho mỗi phương thức thực hiện một và chỉ một điều đơn giản, thì bạn nên đưa ra một ngoại lệ bất cứ khi nào phương thức không thể hoàn thành mục tiêu mong muốn của phương thức. Các ngoại lệ phong phú hơn và dễ sử dụng hơn theo cách này so với mã lỗi. Mã của bạn sạch hơn nhiều - Luồng tiêu chuẩn của đường dẫn mã "bình thường" có thể được dành riêng cho trường hợp phương thức IS có thể thực hiện những gì bạn muốn nó làm ... Và sau đó mã để dọn dẹp hoặc xử lý Các trường hợp "đặc biệt" khi có điều gì đó xấu xảy ra khiến phương thức hoàn thành thành công có thể bị loại khỏi mã thông thường. Ngoài ra, nếu bạn không thể xử lý ngoại lệ xảy ra và phải chuyển nó lên ngăn xếp cho UI, (hoặc tệ hơn, qua dây từ thành phần trung cấp đến UI), thì với mô hình ngoại lệ,


Câu trả lời chính xác! Giải pháp ra khỏi hộp chỉ trong thời gian!
Cristian E.

11

Trước đây tôi đã tham gia trại mã lỗi (đã lập trình C quá nhiều). Nhưng bây giờ tôi đã thấy ánh sáng.

Có ngoại lệ là một chút gánh nặng cho hệ thống. Nhưng họ đơn giản hóa mã, giảm số lỗi (và WTF).

Vì vậy, sử dụng ngoại lệ nhưng sử dụng chúng khôn ngoan. Và họ sẽ là bạn của bạn.

Như một lưu ý phụ. Tôi đã học cách ghi lại ngoại lệ nào có thể được ném bằng phương pháp nào. Thật không may, điều này không được yêu cầu bởi hầu hết các ngôn ngữ. Nhưng nó làm tăng cơ hội xử lý các ngoại lệ đúng ở cấp độ phù hợp.


1
yap C không để lại một vài thói quen trong tất cả chúng ta;)
Jorge Ferreira

11

Có thể có một vài tình huống sử dụng các ngoại lệ một cách rõ ràng, chính xác, rườm rà, nhưng phần lớn các ngoại lệ thời gian là sự lựa chọn rõ ràng. Việc xử lý ngoại lệ lợi ích lớn nhất có mã lỗi là nó thay đổi luồng thực thi, điều này rất quan trọng vì hai lý do.

Khi xảy ra ngoại lệ, ứng dụng không còn theo đường dẫn thực thi 'bình thường' nữa. Lý do đầu tiên tại sao điều này rất quan trọng là trừ khi tác giả của mã hoạt động tốt và thực sự tránh khỏi tình trạng xấu, chương trình sẽ tạm dừng và không tiếp tục làm những điều không thể đoán trước. Nếu mã lỗi không được kiểm tra và các hành động thích hợp không được thực hiện để phản hồi mã lỗi xấu, chương trình sẽ tiếp tục thực hiện những gì nó đang làm và ai biết kết quả của hành động đó sẽ là gì. Có rất nhiều tình huống mà chương trình làm 'bất cứ điều gì' có thể sẽ rất tốn kém. Hãy xem xét một chương trình lấy thông tin hiệu suất cho các công cụ tài chính khác nhau mà một công ty bán và cung cấp thông tin đó cho các nhà môi giới / nhà bán buôn. Nếu có sự cố và chương trình tiếp tục, nó có thể gửi dữ liệu hiệu suất sai cho các nhà môi giới và bán buôn. Tôi không biết về bất kỳ ai khác, nhưng tôi không muốn là người ngồi trong văn phòng VP giải thích lý do tại sao mã của tôi khiến công ty nhận được khoản tiền phạt quy định 7 con số. Việc gửi một thông báo lỗi cho khách hàng thường tốt hơn là cung cấp dữ liệu sai có vẻ như là 'thực' và tình huống sau sẽ dễ dàng hơn nhiều với cách tiếp cận ít tích cực hơn như mã lỗi.

Lý do thứ hai tại sao tôi thích các trường hợp ngoại lệ và việc họ phá vỡ việc thực thi thông thường là vì nó giúp cho việc 'logic những điều bình thường đang xảy ra' trở nên tách biệt với 'điều gì đó không đúng logic'. Đối với tôi, điều này:

try {
    // Normal things are happening logic
catch (// A problem) {
    // Something went wrong logic
}

... thích hợp hơn cho việc này:

// Some normal stuff logic
if (errorCode means error) {
    // Some stuff went wrong logic
}
// Some normal stuff logic
if (errorCode means error) {
    // Some stuff went wrong logic
}
// Some normal stuff logic
if (errorCode means error) {
    // Some stuff went wrong logic
}

Có những điều nhỏ khác về ngoại lệ là tốt, là tốt. Có một loạt logic có điều kiện để theo dõi xem liệu bất kỳ phương thức nào được gọi trong hàm có mã lỗi được trả về hay không, và trả lại mã lỗi cao hơn là rất nhiều tấm nồi hơi. Trong thực tế, đó là rất nhiều tấm nồi hơi có thể đi sai. Tôi có niềm tin nhiều hơn vào hệ thống ngoại lệ của hầu hết các ngôn ngữ so với việc tôi làm một tổ chuột của những câu nói if-if-if-if khác mà 'Fresh-out-of-University' Fred viết, và tôi có nhiều việc phải làm tốt hơn với thời gian của tôi hơn là xem xét mã cho biết tổ của chuột.


8

Bạn nên sử dụng cả hai. Vấn đề là quyết định khi nào nên sử dụng mỗi người .

Có một vài tình huống trong đó các trường hợp ngoại lệ là sự lựa chọn rõ ràng :

  1. Trong một số trường hợp, bạn không thể làm gì với mã lỗi và bạn chỉ cần xử lý nó ở mức cao hơn trong ngăn xếp cuộc gọi , thường chỉ cần ghi nhật ký lỗi, hiển thị nội dung nào đó cho người dùng hoặc đóng chương trình. Trong những trường hợp này, mã lỗi sẽ yêu cầu bạn tạo ra các mã lỗi theo cấp độ theo cách thủ công, điều này rõ ràng dễ thực hiện hơn với các ngoại lệ. Vấn đề là điều này dành cho những tình huống bất ngờ và không thể giải quyết được .

  2. Tuy nhiên, về tình huống 1 (khi có điều gì đó bất ngờ và không thể xử lý được xảy ra, bạn sẽ không đăng nhập nó), ngoại lệ có thể hữu ích vì bạn có thể thêm thông tin theo ngữ cảnh . Ví dụ: nếu tôi nhận được SqlException trong trình trợ giúp dữ liệu cấp thấp hơn, tôi sẽ muốn bắt lỗi đó ở cấp độ thấp (nơi tôi biết lệnh SQL gây ra lỗi) để tôi có thể nắm bắt thông tin đó và suy nghĩ lại với thông tin bổ sung . Xin lưu ý từ ma thuật ở đây: nghĩ lại , và không nuốt . Nguyên tắc đầu tiên của xử lý ngoại lệ: không nuốt ngoại lệ . Ngoài ra, lưu ý rằng sản phẩm khai thác bên trong của tôi không cần phải ghi nhật ký bất cứ thứ gì vì sản phẩm khai thác bên ngoài sẽ có toàn bộ dấu vết ngăn xếp và có thể ghi nhật ký.

  3. Trong một số trường hợp, bạn có một chuỗi lệnh và nếu bất kỳ lệnh nào thất bại, bạn nên dọn dẹp / xử lý tài nguyên (*), cho dù đây có phải là tình huống không thể phục hồi (nên được ném) hoặc tình huống có thể phục hồi (trong trường hợp đó bạn có thể xử lý cục bộ hoặc trong mã người gọi nhưng bạn không cần ngoại lệ). Rõ ràng việc đặt tất cả các lệnh đó trong một lần thử dễ dàng hơn nhiều, thay vì kiểm tra mã lỗi sau mỗi phương thức và dọn dẹp / xử lý trong khối cuối cùng. Xin lưu ý rằng nếu bạn muốn lỗi nổi lên (có thể là điều bạn muốn), bạn thậm chí không cần phải bắt lỗi - bạn chỉ cần sử dụng cuối cùng để dọn dẹp / xử lý - bạn chỉ nên sử dụng bắt / rút tiền nếu muốn để thêm thông tin theo ngữ cảnh (xem đạn 2).

    Một ví dụ sẽ là một chuỗi các câu lệnh SQL bên trong một khối giao dịch. Một lần nữa, đây cũng là một tình huống "không thể giải quyết", ngay cả khi bạn quyết định bắt sớm (xử lý cục bộ thay vì sủi bọt lên trên đỉnh), đó vẫn là một tình huống nghiêm trọng từ đó kết quả tốt nhất là hủy bỏ mọi thứ hoặc ít nhất là hủy bỏ một lượng lớn một phần của quá trình.
    (*) Điều này giống như cái on error gotomà chúng ta đã sử dụng trong Visual Basic cũ

  4. Trong constructor bạn chỉ có thể ném ngoại lệ.

Phải nói rằng, trong tất cả các tình huống khác khi bạn trả lại một số thông tin mà người gọi CÓ THỂ / NÊN thực hiện một số hành động , sử dụng mã trả lại có lẽ là một cách thay thế tốt hơn. Điều này bao gồm tất cả các "lỗi" dự kiến , bởi vì có lẽ chúng nên được xử lý bởi người gọi ngay lập tức và sẽ khó có thể được đưa lên quá nhiều cấp độ trong ngăn xếp.

Tất nhiên, luôn có thể coi các lỗi dự kiến ​​là ngoại lệ và bắt ngay lập tức một cấp ở trên và cũng có thể bao gồm mọi dòng mã trong một lần thử và thực hiện các hành động cho từng lỗi có thể. IMO, đây là thiết kế tồi, không chỉ vì nó dài dòng hơn, mà đặc biệt bởi vì các ngoại lệ có thể bị ném không rõ ràng nếu không đọc mã nguồn - và ngoại lệ có thể được ném từ bất kỳ phương thức sâu nào, tạo ra các hình ảnh vô hình . Chúng phá vỡ cấu trúc mã bằng cách tạo nhiều điểm thoát vô hình khiến mã khó đọc và kiểm tra. Nói cách khác, bạn không bao giờ nên sử dụng ngoại lệ làm kiểm soát luồng , bởi vì đó sẽ là khó để người khác hiểu và duy trì. Thậm chí có thể khó hiểu tất cả các dòng mã có thể để thử nghiệm.
Một lần nữa: để dọn dẹp / vứt bỏ chính xác, bạn có thể sử dụng thử-cuối cùng mà không bắt được gì .

Những lời chỉ trích phổ biến nhất về mã trả về là "ai đó có thể bỏ qua mã lỗi, nhưng theo nghĩa tương tự, ai đó cũng có thể nuốt ngoại lệ. Xử lý ngoại lệ xấu dễ dàng trong cả hai phương pháp. Nhưng viết chương trình dựa trên mã lỗi vẫn dễ dàng hơn nhiều hơn là viết một chương trình dựa trên ngoại lệ . Và nếu vì một lý do nào đó quyết định bỏ qua tất cả các lỗi (cũ on error resume next), bạn có thể dễ dàng làm điều đó với mã trả về và bạn không thể làm điều đó mà không cần nhiều bản tóm tắt thử.

Lời chỉ trích phổ biến thứ hai về mã trả lại là "rất khó để nổi bong bóng" - nhưng đó là vì mọi người không hiểu rằng các ngoại lệ dành cho các tình huống không thể phục hồi, trong khi mã lỗi thì không.

Quyết định giữa các ngoại lệ và mã lỗi là một khu vực màu xám. Thậm chí có khả năng bạn cần lấy mã lỗi từ một số phương thức kinh doanh có thể sử dụng lại, và sau đó bạn quyết định bọc nó thành một ngoại lệ (có thể thêm thông tin) và để nó nổi lên. Nhưng đó là một lỗi thiết kế khi cho rằng TẤT CẢ các lỗi nên được đưa ra làm ngoại lệ.

Tóm lại:

  • Tôi thích sử dụng các ngoại lệ khi tôi gặp tình huống không mong muốn, trong đó không có nhiều việc phải làm và thông thường chúng tôi muốn hủy bỏ một khối mã lớn hoặc thậm chí toàn bộ hoạt động hoặc chương trình. Điều này giống như "lỗi goto" cũ.

  • Tôi thích sử dụng mã trả về khi tôi gặp các tình huống dự kiến ​​trong đó mã người gọi có thể / nên thực hiện một số hành động. Điều này bao gồm hầu hết các phương thức kinh doanh, API, xác nhận, v.v.

Sự khác biệt giữa ngoại lệ và mã lỗi này là một trong những nguyên tắc thiết kế của ngôn ngữ GO, sử dụng "hoảng loạn" cho các tình huống bất ngờ gây tử vong, trong khi các tình huống dự kiến ​​thông thường được trả về là lỗi.

Tuy nhiên, về GO, nó cũng cho phép nhiều giá trị trả về , đây là điều giúp ích rất nhiều cho việc sử dụng mã trả về, vì bạn có thể đồng thời trả lại lỗi và một thứ khác. Trên C # / Java, chúng ta có thể đạt được điều đó với các tham số, Tuples hoặc Generics (yêu thích của tôi), kết hợp với enums có thể cung cấp mã lỗi rõ ràng cho người gọi:

public MethodResult<CreateOrderResultCodeEnum, Order> CreateOrder(CreateOrderOptions options)
{
    ....
    return MethodResult<CreateOrderResultCodeEnum>.CreateError(CreateOrderResultCodeEnum.NO_DELIVERY_AVAILABLE, "There is no delivery service in your area");

    ...
    return MethodResult<CreateOrderResultCodeEnum>.CreateSuccess(CreateOrderResultCodeEnum.SUCCESS, order);
}

var result = CreateOrder(options);
if (result.ResultCode == CreateOrderResultCodeEnum.OUT_OF_STOCK)
    // do something
else if (result.ResultCode == CreateOrderResultCodeEnum.SUCCESS)
    order = result.Entity; // etc...

Nếu tôi thêm một lợi nhuận mới có thể có trong phương thức của mình, tôi thậm chí có thể kiểm tra tất cả người gọi nếu họ đang bao gồm giá trị mới đó trong câu lệnh chuyển đổi chẳng hạn. Bạn thực sự không thể làm điều đó với ngoại lệ. Khi bạn sử dụng mã trả về, bạn thường sẽ biết trước tất cả các lỗi có thể xảy ra và kiểm tra chúng. Với ngoại lệ, bạn thường không biết điều gì có thể xảy ra. Gói enum bên trong các ngoại lệ (thay vì Generics) là một cách thay thế (miễn là nó rõ ràng loại ngoại lệ mà mỗi phương thức sẽ đưa ra), nhưng IMO vẫn là thiết kế tồi.


4

Lý do của tôi sẽ là nếu bạn đang viết một trình điều khiển cấp thấp thực sự cần hiệu năng, sau đó sử dụng mã lỗi. Nhưng nếu bạn đang sử dụng mã đó trong một ứng dụng cấp cao hơn và nó có thể xử lý một chút chi phí, thì hãy bọc mã đó bằng một giao diện kiểm tra các mã lỗi đó và đưa ra các ngoại lệ.

Trong tất cả các trường hợp khác, ngoại lệ có lẽ là con đường để đi.


4

Tôi có thể đang ngồi trên hàng rào ở đây, nhưng ...

  1. Nó phụ thuộc vào ngôn ngữ.
  2. Cho dù bạn chọn mô hình nào, hãy nhất quán về cách bạn sử dụng nó.

Trong Python, sử dụng các ngoại lệ là thông lệ tiêu chuẩn và tôi khá vui khi xác định các ngoại lệ của riêng mình. Trong C bạn không có ngoại lệ nào cả.

Trong C ++ (ít nhất là trong STL), các trường hợp ngoại lệ thường chỉ được đưa ra cho các lỗi thực sự đặc biệt (tôi hầu như không bao giờ nhìn thấy chúng). Tôi thấy không có lý do để làm bất cứ điều gì khác nhau trong mã của riêng tôi. Có, thật dễ dàng để bỏ qua các giá trị trả về, nhưng C ++ cũng không buộc bạn phải bắt ngoại lệ. Tôi nghĩ bạn chỉ cần có thói quen làm việc đó.

Cơ sở mã tôi làm việc chủ yếu là C ++ và chúng tôi sử dụng mã lỗi ở hầu hết mọi nơi, nhưng có một mô-đun làm tăng ngoại lệ cho bất kỳ lỗi nào, bao gồm cả những trường hợp không có ngoại lệ và tất cả mã sử dụng mô-đun đó là khá kinh khủng. Nhưng đó có thể là do chúng tôi đã trộn lẫn các ngoại lệ và mã lỗi. Mã liên tục sử dụng mã lỗi dễ làm việc hơn nhiều. Nếu mã của chúng tôi liên tục sử dụng ngoại lệ, có thể nó sẽ không tệ như vậy. Trộn cả hai dường như không hoạt động tốt.


4

Vì tôi làm việc với C ++ và có RAII để sử dụng chúng an toàn, tôi sử dụng các ngoại lệ gần như độc quyền. Nó kéo xử lý lỗi ra khỏi luồng chương trình bình thường và làm cho ý định rõ ràng hơn.

Tôi để lại ngoại lệ cho các trường hợp đặc biệt mặc dù. Nếu tôi hy vọng rằng một lỗi nào đó sẽ xảy ra rất nhiều, tôi sẽ kiểm tra xem thao tác đó có thành công hay không trước khi thực hiện hoặc gọi một phiên bản của hàm sử dụng mã lỗi thay thế (Thích TryParse())


3

Chữ ký phương thức sẽ truyền đạt cho bạn những gì phương thức làm. Một cái gì đó như errorCode dài = getErrorCode (); có thể ổn, nhưng longCode = fetchRecord (); thật khó hiểu.


3

Cách tiếp cận của tôi là chúng ta có thể sử dụng cả hai, tức là mã ngoại lệ và mã lỗi cùng một lúc.

Tôi được sử dụng để xác định một số loại Ngoại lệ (ví dụ: DataValidationException hoặc ProcessInterruptEx805ion) và bên trong mỗi ngoại lệ xác định mô tả chi tiết hơn về từng vấn đề.

Một ví dụ đơn giản trong Java:

public class DataValidationException extends Exception {


    private DataValidation error;

    /**
     * 
     */
    DataValidationException(DataValidation dataValidation) {
        super();
        this.error = dataValidation;
    }


}

enum DataValidation{

    TOO_SMALL(1,"The input is too small"),

    TOO_LARGE(2,"The input is too large");


    private DataValidation(int code, String input) {
        this.input = input;
        this.code = code;
    }

    private String input;

    private int code;

}

Theo cách này, tôi sử dụng Ngoại lệ để xác định lỗi danh mục và mã lỗi để xác định thông tin chi tiết hơn về sự cố.


2
erm ... throw new DataValidationException("The input is too small")? Một trong những lợi thế của ngoại lệ là cho phép thông tin chi tiết.
Eva

2

Các ngoại lệ dành cho các trường hợp đặc biệt - tức là khi chúng không phải là một phần của dòng chảy thông thường của mã.

Việc trộn lẫn các ngoại lệ và mã lỗi là khá hợp pháp, trong đó mã lỗi biểu thị trạng thái của một thứ gì đó, thay vì lỗi trong quá trình chạy mã mỗi lần (ví dụ: kiểm tra mã trả về từ quy trình con).

Nhưng khi một tình huống đặc biệt xảy ra, tôi tin rằng Ngoại lệ là mô hình biểu cảm nhất.

Có những trường hợp bạn có thể thích hoặc sử dụng mã lỗi thay cho Ngoại lệ và những trường hợp này đã được bảo vệ đầy đủ (trừ các ràng buộc rõ ràng khác như hỗ trợ trình biên dịch).

Nhưng đi theo một hướng khác, sử dụng Ngoại lệ cho phép bạn xây dựng các bản tóm tắt cấp độ cao hơn để xử lý lỗi, điều đó có thể làm cho mã của bạn trở nên rõ ràng và tự nhiên hơn. Tôi đặc biệt khuyên bạn nên đọc bài viết xuất sắc nhưng chưa được đánh giá cao này của chuyên gia C ++ Andrei Alexandrescu về chủ đề của cái mà anh ta gọi là "Enforcements": http://www.ddj.com/cpp/184403864 . Mặc dù đó là một bài viết về C ++, các nguyên tắc thường được áp dụng và tôi đã dịch khái niệm thực thi sang C # khá thành công.


2

Đầu tiên, tôi đồng ý với câu trả lời của Tom rằng đối với các công cụ cấp cao sử dụng ngoại lệ và đối với công cụ cấp thấp sử dụng mã lỗi, miễn là nó không phải là Kiến trúc hướng dịch vụ (SOA).

Trong SOA, nơi các phương thức có thể được gọi trên các máy khác nhau, ngoại lệ có thể không được truyền qua dây, thay vào đó, chúng tôi sử dụng các phản hồi thành công / thất bại với cấu trúc như dưới đây (C #):

public class ServiceResponse
{
    public bool IsSuccess => string.IsNullOrEmpty(this.ErrorMessage);

    public string ErrorMessage { get; set; }
}

public class ServiceResponse<TResult> : ServiceResponse
{
    public TResult Result { get; set; }
}

Và sử dụng như thế này:

public async Task<ServiceResponse<string>> GetUserName(Guid userId)
{
    var response = await this.GetUser(userId);
    if (!response.IsSuccess) return new ServiceResponse<string>
    {
        ErrorMessage = $"Failed to get user."
    };
    return new ServiceResponse<string>
    {
        Result = user.Name
    };
}

Khi chúng được sử dụng nhất quán trong các phản hồi dịch vụ của bạn, nó tạo ra một mô hình xử lý thành công / thất bại rất tốt trong ứng dụng. Điều này cho phép xử lý lỗi dễ dàng hơn trong các cuộc gọi không đồng bộ trong các dịch vụ cũng như trên các dịch vụ.


1

Tôi thích Ngoại lệ cho tất cả các trường hợp lỗi, ngoại trừ khi lỗi là kết quả không có lỗi có thể mong đợi của hàm trả về kiểu dữ liệu nguyên thủy. Ví dụ: tìm chỉ mục của một chuỗi con trong một chuỗi lớn hơn thường sẽ trả về -1 nếu không tìm thấy, thay vì tăng NotFoundException.

Trả về các con trỏ không hợp lệ có thể bị hủy đăng ký (ví dụ: gây ra NullPulumException trong Java) không được chấp nhận.

Sử dụng nhiều mã lỗi số khác nhau (-1, -2) làm giá trị trả về cho cùng một hàm thường là kiểu xấu, vì khách hàng có thể thực hiện kiểm tra "== -1" thay vì "<0".

Một điều cần lưu ý ở đây là sự phát triển của API theo thời gian. Một API tốt cho phép thay đổi và mở rộng hành vi thất bại theo nhiều cách mà không phá vỡ máy khách. Ví dụ: nếu xử lý lỗi máy khách kiểm tra 4 trường hợp lỗi và bạn thêm giá trị lỗi thứ năm vào chức năng của mình, trình xử lý máy khách có thể không kiểm tra điều này và ngắt. Nếu bạn nâng cao Ngoại lệ, điều này thường sẽ giúp khách hàng dễ dàng di chuyển sang phiên bản mới hơn của thư viện.

Một điều khác cần xem xét là khi làm việc trong một nhóm, nơi để vạch ra một ranh giới rõ ràng cho tất cả các nhà phát triển để đưa ra quyết định như vậy. Ví dụ: "Ngoại lệ cho nội dung cấp cao, mã lỗi cho nội dung cấp thấp" rất chủ quan.

Trong mọi trường hợp, nếu có nhiều hơn một loại lỗi nhỏ, mã nguồn không bao giờ nên sử dụng chữ số để trả về mã lỗi hoặc để xử lý nó (trả về -7, nếu x == -7 ...), nhưng luôn là hằng số được đặt tên (trả về NO_SUCH_FOO, nếu x == NO_SUCH_FOO).


1

Nếu bạn làm việc trong dự án lớn, bạn không thể chỉ sử dụng ngoại lệ hoặc chỉ mã lỗi. Trong các trường hợp khác nhau, bạn nên sử dụng các phương pháp khác nhau.

Ví dụ: bạn quyết định chỉ sử dụng ngoại lệ. Nhưng một khi bạn quyết định sử dụng xử lý sự kiện async. Đó là ý tưởng tồi để sử dụng các ngoại lệ để xử lý lỗi trong tình huống này. Nhưng sử dụng mã lỗi ở mọi nơi trong ứng dụng là tẻ nhạt.

Vì vậy, ý kiến ​​của tôi rằng việc sử dụng đồng thời cả ngoại lệ và mã lỗi là điều bình thường.


0

Đối với hầu hết các ứng dụng, ngoại lệ là tốt hơn. Ngoại lệ là khi phần mềm phải giao tiếp với các thiết bị khác. Tên miền tôi làm việc là kiểm soát công nghiệp. Ở đây mã lỗi được ưa thích và dự kiến. Vì vậy, câu trả lời của tôi là nó phụ thuộc vào tình hình.


0

Tôi nghĩ nó cũng phụ thuộc vào việc bạn có thực sự cần thông tin như dấu vết ngăn xếp từ kết quả hay không. Nếu có, bạn chắc chắn đi Exception cung cấp cho đối tượng đầy đủ thông tin về vấn đề. Tuy nhiên, nếu bạn chỉ quan tâm đến kết quả và không quan tâm tại sao kết quả đó thì hãy tìm mã lỗi.

ví dụ: Khi bạn đang xử lý tệp và đối mặt với IOException, khách hàng có thể quan tâm đến việc biết nơi này được kích hoạt, trong việc mở tệp hoặc phân tích tệp, v.v. Tuy nhiên, kịch bản như bạn có phương thức đăng nhập và bạn muốn biết nó có thành công hay không, có hoặc bạn chỉ trả về boolean hoặc để hiển thị thông báo chính xác, trả về mã lỗi. Ở đây Khách hàng không quan tâm đến việc biết phần nào của logic gây ra mã lỗi đó. Anh ta chỉ biết Thông tin xác thực không hợp lệ hoặc khóa tài khoản, v.v.

Một usecase khác tôi có thể nghĩ đến là khi dữ liệu di chuyển trên mạng. Phương pháp từ xa của bạn có thể trả về mã lỗi thay vì Ngoại lệ để giảm thiểu truyền dữ liệu.


0

Quy tắc chung của tôi là:

  • Chỉ một lỗi có thể xuất hiện trong một hàm: sử dụng mã lỗi (làm tham số của hàm)
  • Nhiều lỗi cụ thể có thể xuất hiện: ném ngoại lệ

-1

Mã lỗi cũng không hoạt động khi phương thức của bạn trả về bất cứ thứ gì ngoài giá trị số ...


3
À, không. Xem mô hình win32 GetLastError (). Tôi không bảo vệ nó, chỉ viết rằng bạn không chính xác.
Tim

4
Trong thực tế có nhiều cách khác để làm điều đó. Một cách khác là trả về một đối tượng có chứa mã lỗi và giá trị trả về thực. Tuy nhiên, một cách khác là làm thông qua tham khảo.
Pacerier
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.