Khi nào nên ném một ngoại lệ?


435

Tôi có các ngoại lệ được tạo cho mọi điều kiện mà ứng dụng của tôi không mong đợi. UserNameNotValidException, PasswordNotCorrectExceptionVv

Tuy nhiên tôi đã nói rằng tôi không nên tạo ra ngoại lệ cho những điều kiện đó. Trong UML của tôi, những ngoại lệ đó là luồng chính, vậy tại sao nó không phải là ngoại lệ?

Bất kỳ hướng dẫn hoặc thực hành tốt nhất để tạo ngoại lệ?


58
Vui lòng mở lại, đây là một câu hỏi rất hợp lý và hợp lệ. Bất kỳ câu hỏi nào cũng liên quan đến một số ý kiến ​​nhất định, nhưng trong trường hợp này tôi nghi ngờ đó là vấn đề 'thực hành tốt nhất'.
Tim Long

19
+1 để mở lại. Như nhiều chủ đề thú vị khác, 'nó phụ thuộc' và rất hữu ích để phân tích sự đánh đổi khi đưa ra quyết định. Việc mọi người nhầm lẫn ý kiến ​​với sự thật trong các câu trả lời không phủ nhận điều này. Lướt qua bùn là một bài tập nên để lại cho người đọc.
aron

12
Tôi cũng đồng ý câu hỏi này nên được mở lại vì nó liên quan đến thực tiễn tốt nhất. Bằng cách này, thực tiễn tốt nhất luôn là ý kiến ​​có thể giúp đỡ người khác.
Ajay Sharma

6
Microsoft nói: "Không trả lại mã lỗi. Ngoại lệ là phương tiện chính để báo cáo lỗi trong các khung." và "... Nếu một thành viên không thể thực hiện thành công những gì nó được thiết kế để làm, thì đó nên được coi là một lỗi thực thi và một ngoại lệ nên được ném ra.". msdn.microsoft.com/l
Library / ms229030% 28v = vs.100% 29.aspx

4
Đây có thể là ngoại lệ hoàn toàn hợp lý, nó chỉ phụ thuộc vào phương pháp nào ném chúng. Một phương thức được gọi IsCredentialsValid(username,password)không nên ném ngoại lệ nếu tên người dùng hoặc mật khẩu không hợp lệ, nhưng trả về sai. Nhưng nói rằng một phương pháp đọc dữ liệu từ cơ sở dữ liệu có thể đưa ra một ngoại lệ như vậy, nếu xác thực thất bại. Tóm lại: Bạn nên ném một ngoại lệ nếu một phương thức không thể thực hiện nhiệm vụ mà nó được cho là phải làm.
JacquesB

Câu trả lời:


629

Hướng dẫn cá nhân của tôi là: một ngoại lệ được đưa ra khi một giả định cơ bản của khối mã hiện tại là sai.

Ví dụ 1: giả sử tôi có một hàm được cho là kiểm tra một lớp tùy ý và trả về true nếu lớp đó kế thừa từ Danh sách <>. Hàm này đặt câu hỏi: "Đối tượng này có phải là hậu duệ của Danh sách không?" Hàm này không bao giờ được đưa ra một ngoại lệ, vì không có vùng màu xám nào trong hoạt động của nó - mỗi lớp duy nhất có hoặc không kế thừa từ Danh sách <>, vì vậy câu trả lời luôn là "có" hoặc "không".

Ví dụ 2: giả sử tôi có một hàm khác kiểm tra Danh sách <> và trả về true nếu độ dài của nó lớn hơn 50 và sai nếu độ dài nhỏ hơn. Hàm này đặt câu hỏi "Danh sách này có hơn 50 mục không?" Nhưng câu hỏi này đưa ra một giả định - nó giả định rằng đối tượng mà nó được đưa ra là một danh sách. Nếu tôi đưa cho nó một NULL, thì giả định đó là sai. Trong trường hợp đó, nếu trở về chức năng hoặc là đúng hoặc sai sự thật, sau đó nó là phá vỡ quy tắc riêng của mình. Hàm không thể trả về bất cứ điều gì và tuyên bố rằng nó đã trả lời chính xác câu hỏi. Vì vậy, nó không trở lại - nó ném một ngoại lệ.

Điều này có thể so sánh với sai lầm logic "câu hỏi được tải" . Mỗi chức năng hỏi một câu hỏi. Nếu đầu vào được đưa ra làm cho câu hỏi đó trở thành ngụy biện, thì hãy ném một ngoại lệ. Dòng này khó vẽ hơn với các hàm trả về khoảng trống, nhưng dòng dưới cùng là: nếu các giả định của hàm về các đầu vào của nó bị vi phạm, thì nó nên ném một ngoại lệ thay vì trả về bình thường.

Mặt khác của phương trình này là: nếu bạn thấy các hàm của mình đưa ra các ngoại lệ thường xuyên, thì có lẽ bạn cần phải tinh chỉnh các giả định của chúng.


15
Chính xác! Một ngoại lệ được đưa ra khi và chỉ khi các điều kiện tiên quyết của hàm (giả định về đối số) bị phá vỡ !
Lightman

11
Trong ngôn ngữ học, điều này đôi khi được gọi là thất bại giả định . Ví dụ kinh điển là do Bertrand Russell: "Vua của Pháp hói" không thể được trả lời có hay không, (tôn trọng. "Vua Pháp bị hói" không đúng cũng không sai), bởi vì nó chứa một giả định sai , cụ thể là có một vị vua của Pháp. Thất bại giả định thường được nhìn thấy với các mô tả xác định, và đó là phổ biến khi lập trình. Ví dụ: "Người đứng đầu danh sách" có lỗi giả định khi danh sách trống, và sau đó, việc đưa ra một ngoại lệ là phù hợp.
Mohan

Đây có thể là lời giải thích tốt nhất!
gaurav

Cảm ơn bạn. Vì thế. Nhiều lắm. "Là vua hói của Pháp". Tôi đã nghe điều này trước đây khi nghiên cứu về rừng rậm của meinong .... :) Cảm ơn bạn. @Mohan
ErlichBachman

285

Bởi vì chúng là những thứ sẽ xảy ra bình thường. Ngoại lệ không kiểm soát cơ chế dòng chảy. Người dùng thường nhận được mật khẩu sai, đây không phải là trường hợp ngoại lệ. Trường hợp ngoại lệ nên là một điều thực sự hiếm, UserHasDiedAtKeyboardloại tình huống.


3
Hừm, không. Các ngoại lệ có thể được sử dụng làm cơ chế luồng điều khiển nếu không yêu cầu hiệu suất tối đa, điều này đúng với hầu hết các ứng dụng web. Python sử dụng ngoại lệ 'StopIteration' để chấm dứt các trình vòng lặp và nó hoạt động rất tốt. Chi phí không là gì so với IO, v.v.
Seun Osewa

9
+1 câu trả lời xuất sắc. Tôi rất thất vọng bởi các nhà phát triển làm việc trên API mà tôi phải tiêu thụ và ném Ngoại lệ cho mọi thứ nhỏ nhặt. RẤT vài trường hợp thực sự yêu cầu ngoại lệ. Nếu bạn có 25 loại ngoại lệ khác nhau được xác định, hãy xem lại thiết kế của bạn, bạn có thể đã làm sai.
7wp

1
nên có một ngoại lệ khi người dùng thử một hành động bất hợp pháp mà anh ta không được phép bằng cách thao túng mã trang web, ví dụ như xóa các bài đăng của người khác ở đây tại StackOverflow?
Rajat Gupta

30
Ngoại lệ cơ chế kiểm soát dòng chảy. Bạn có thể ném chúng. Bạn có thể bắt chúng. Bạn đã chuyển điều khiển sang một đoạn mã khác. Đó là kiểm soát dòng chảy. Lý do duy nhất khiến ngôn ngữ có ngoại lệ là vì vậy bạn có thể viết mã đơn giản mà không cần hỏi "điều đó có thất bại không?" sau tất cả mọi thứ bạn làm Ví dụ, Haskell không có ngoại lệ vì các đơn nguyên và ký hiệu có thể tự động hóa việc kiểm tra lỗi cho bạn.
Jesse

2
Ngoại lệ là nhiều hơn các cơ chế dòng kiểm soát. Họ cung cấp cho khách hàng (phương pháp) thông tin hữu ích về các kết quả đặc biệt mà họ phải biết và xử lý. Đó là, được sử dụng đúng cách, các ngoại lệ làm cho API mạnh mẽ hơn
idelvall

67

Hướng dẫn nhỏ của tôi bị ảnh hưởng nặng nề bởi cuốn sách tuyệt vời "Hoàn thành mã":

  • Sử dụng các ngoại lệ để thông báo về những điều không nên bỏ qua.
  • Không sử dụng ngoại lệ nếu lỗi có thể được xử lý cục bộ
  • Hãy chắc chắn rằng các ngoại lệ ở cùng mức độ trừu tượng như phần còn lại của thói quen của bạn.
  • Trường hợp ngoại lệ nên được dành riêng cho những gì thực sự đặc biệt .

35

Nó KHÔNG phải là ngoại lệ nếu tên người dùng không hợp lệ hoặc mật khẩu không đúng. Đó là những điều bạn nên mong đợi trong dòng hoạt động bình thường. Ngoại lệ là những thứ không phải là một phần của hoạt động chương trình bình thường và khá hiếm.

EDIT: Tôi không thích sử dụng các ngoại lệ vì bạn không thể biết liệu một phương thức có ném ngoại lệ hay không chỉ bằng cách nhìn vào cuộc gọi. Đó là lý do tại sao các trường hợp ngoại lệ chỉ nên được sử dụng nếu bạn không thể xử lý tình huống một cách tử tế (nghĩ rằng "hết bộ nhớ" hoặc "máy tính bị cháy").


"Tôi không thích sử dụng các ngoại lệ vì bạn không thể biết liệu một phương thức có ném ngoại lệ hay không chỉ bằng cách xem cuộc gọi." Đây là lý do tại sao có các ngoại lệ được kiểm tra cho các ngôn ngữ hỗ trợ chúng.
Newtopian

1
Các trường hợp ngoại lệ được kiểm tra có bộ vấn đề riêng của họ. Tôi vẫn muốn sử dụng các ngoại lệ của "hoàn cảnh đặc biệt", không phải cho những thứ là một phần của quy trình làm việc bình thường.
EricSchaefer

2
Đáp lại chỉnh sửa của bạn. Tôi luôn đặt các tài liệu xml của mình vào cuối phần tóm tắt các trường hợp ngoại lệ mà hàm ném ra để tôi có thể thấy thông tin đó trong intellisense.
Matthew Vines

1
nên có một ngoại lệ khi người dùng thử một hành động bất hợp pháp mà anh ta không được phép bằng cách thao túng mã trang web, ví dụ như xóa các bài đăng của người khác ở đây tại StackOverflow?
Rajat Gupta

1
Có vẻ như bạn đang nói về việc xử lý lỗi (bộ nhớ của chúng tôi, máy tính bị cháy) so với xử lý ngoại lệ mã (thiếu bản ghi, loại đầu vào không hợp lệ, v.v.). Tôi nghĩ rằng có một sự khác biệt rõ ràng giữa hai.
Chuck Burgess

28

Một nguyên tắc nhỏ là sử dụng các ngoại lệ trong trường hợp điều mà bạn thường không thể dự đoán. Ví dụ là kết nối cơ sở dữ liệu, thiếu tệp trên đĩa, v.v. Bạn không muốn đột ngột kết thúc việc thực thi bằng cách ném một ngoại lệ chỉ vì ai đó nhập nhầm mật khẩu của họ.


6
bạn không cần dừng thực thi chương trình trên một ngoại lệ ... ném ngoại lệ, người gọi sau đó bắt được ngoại lệ và nên xử lý nó, nếu có thể, đăng nhập và báo lỗi và tiếp tục. Đó là 'hình thức xấu' khi cứ tiếp tục ném ngoại lệ lên ngăn xếp cuộc gọi - bắt nó ở nơi nó xảy ra và xử lý nó ở đó
Krakkos

2
nhưng tại sao thậm chí ném chúng nếu bạn có thể xử lý nó trực tiếp. nếu mật khẩu sai hoặc bất cứ điều gì sai, tôi chỉ để lại nếu trả về sai và đưa ra lỗi
My1

" Thiếu tệp trên đĩa " Hầu hết các khung ngôn ngữ, ví dụ .NET framework, cũng cung cấp API để kiểm tra sự tồn tại của tệp. Tại sao không sử dụng chúng trước khi truy cập các tập tin trực tiếp!
user1451111

23

Những người khác đề xuất rằng các trường hợp ngoại lệ không nên được sử dụng vì đăng nhập xấu sẽ được dự kiến ​​trong một luồng bình thường nếu người dùng nhầm lẫn. Tôi không đồng ý và tôi không hiểu lý do. So sánh nó với việc mở một tệp .. nếu tệp không tồn tại hoặc không có sẵn vì một số lý do thì một ngoại lệ sẽ được ném bởi khung. Sử dụng logic trên đây là một sai lầm của Microsoft. Họ nên đã trả lại một mã lỗi. Tương tự cho phân tích cú pháp, webrequests, vv, vv ..

Tôi không xem xét một phần đăng nhập xấu của một luồng bình thường, nó là ngoại lệ. Thông thường người dùng nhập đúng mật khẩu và tệp tồn tại. Các trường hợp đặc biệt là đặc biệt và sử dụng ngoại lệ cho những trường hợp đó là hoàn toàn tốt. Làm phức tạp mã của bạn bằng cách truyền các giá trị trả về thông qua n cấp lên ngăn xếp là một sự lãng phí năng lượng và sẽ dẫn đến mã lộn xộn. Làm điều đơn giản nhất có thể có thể làm việc. Đừng tối ưu hóa sớm bằng cách sử dụng mã lỗi, nội dung đặc biệt theo định nghĩa hiếm khi xảy ra và ngoại lệ không mất bất cứ chi phí nào trừ khi bạn ném chúng.


Ngoại trừ bạn có thể kiểm tra xem tệp có tồn tại trước khi gọi mở không (tùy thuộc vào khung khóa học của bạn). Vì vậy, cơ sở đó tồn tại và do đó nếu tập tin biến mất giữa kiểm tra và bạn cố mở nó thì đó là ngoại lệ.
blowdart

7
Ví dụ, tệp hiện tại không có nghĩa là người dùng được phép ghi vào tệp. Kiểm tra cho mọi vấn đề có thể thực sự tẻ nhạt và dễ bị lỗi. + bạn đang sao chép mã (DRY).
Bjorn Reppen

Một điểm với mật khẩu không hợp lệ Ngoại lệ là mọi sự chậm chạp so với giải pháp mã trả lại sẽ không được chấp nhận đối với người dùng nhập mật khẩu.
paperhorse

7
"Làm phức tạp mã của bạn bằng cách truyền các giá trị trả về qua n cấp lên ngăn xếp là một sự lãng phí năng lượng và sẽ dẫn đến mã lộn xộn". Điều đó, đối với tôi, là một lý do rất mạnh mẽ để sử dụng các ngoại lệ. Mã tốt thường bao gồm các chức năng nhỏ. Bạn không muốn truyền đi mã lỗi đó lặp đi lặp lại từ chức năng nhỏ này sang chức năng nhỏ khác.
beluchin

Tôi nghĩ rằng sự nhầm lẫn xuất phát từ một giả định có lẽ là kết quả có thể dự đoán được của loginphương pháp -type sẽ là mật khẩu có thể không chính xác, trên thực tế nó có thể được sử dụng để xác định điều này và không muốn có ngoại lệ trong trường hợp này; trong khi đó trong một file openkịch bản loại, có kết quả cụ thể mong muốn - nếu hệ thống không thể cung cấp kết quả do tham số đầu vào không chính xác hoặc một số yếu tố bên ngoài, đó là cách sử dụng ngoại lệ hoàn toàn hợp lý.
Người chơi

17

Các ngoại lệ là một hiệu ứng hơi tốn kém, ví dụ, nếu bạn có một người dùng cung cấp mật khẩu không hợp lệ, thì thông thường tốt hơn là gửi lại một cờ thất bại hoặc một số chỉ báo khác rằng nó không hợp lệ.

Điều này là do cách xử lý các ngoại lệ, đầu vào xấu thực sự và các mục dừng quan trọng duy nhất phải là ngoại lệ, nhưng không phải là thông tin đăng nhập thất bại.


14

Tôi nghĩ bạn chỉ nên ném một ngoại lệ khi không có gì bạn có thể làm để thoát khỏi tình trạng hiện tại của bạn. Ví dụ: nếu bạn đang phân bổ bộ nhớ và không có bất kỳ phân bổ nào. Trong các trường hợp bạn đề cập, bạn có thể khôi phục rõ ràng từ các trạng thái đó và có thể trả lại mã lỗi cho người gọi của bạn.


Bạn sẽ thấy rất nhiều lời khuyên, bao gồm cả trong câu trả lời cho câu hỏi này, rằng bạn chỉ nên ném ngoại lệ trong những trường hợp "đặc biệt". Điều đó có vẻ hợp lý bề ngoài, nhưng là lời khuyên thiếu sót, bởi vì nó thay thế một câu hỏi ("khi nào tôi nên ném một ngoại lệ") bằng một câu hỏi chủ quan khác ("điều gì là đặc biệt"). Thay vào đó, hãy làm theo lời khuyên của Herb Sutter (đối với C ++, có sẵn trong bài viết của Dr Dobbs Khi nào và Cách sử dụng Ngoại lệ , và trong cuốn sách của ông với Andrei Alexandrescu, Tiêu chuẩn mã hóa C ++ ): ném ngoại lệ nếu và chỉ khi

  • một điều kiện tiên quyết không được đáp ứng (điều này thường làm cho một trong những điều sau là không thể) hoặc
  • sự thay thế sẽ không đáp ứng một điều kiện hậu hoặc
  • sự thay thế sẽ không duy trì một bất biến.

Tại sao điều này tốt hơn? Nó không thay thế câu hỏi bằng một vài câu hỏi về điều kiện tiên quyết, hậu điều kiện và bất biến? Điều này là tốt hơn cho một số lý do kết nối.

  • Điều kiện tiên quyết, hậu điều kiện và bất biến là các đặc điểm thiết kế của chương trình của chúng tôi (API nội bộ của nó), trong khi quyết địnhthrow là một chi tiết triển khai. Nó buộc chúng ta phải nhớ rằng chúng ta phải xem xét thiết kế và triển khai nó một cách riêng biệt, và công việc của chúng ta trong khi thực hiện một phương pháp là tạo ra một cái gì đó thỏa mãn các ràng buộc thiết kế.
  • Nó buộc chúng ta phải suy nghĩ về các điều kiện tiên quyết, hậu điều kiện và bất biến, đó là duy nhất giả định mà người gọi phương thức của chúng ta nên đưa ra, và được thể hiện chính xác, cho phép ghép lỏng giữa các thành phần của chương trình.
  • Khớp nối lỏng lẻo đó sau đó cho phép chúng tôi tái cấu trúc việc thực hiện, nếu cần thiết.
  • Các điều kiện hậu và bất biến là có thể kiểm tra được; nó dẫn đến mã có thể dễ dàng kiểm tra đơn vị, bởi vì các điều kiện hậu là các vị từ kiểm tra mã đơn vị của chúng tôi có thể kiểm tra (khẳng định).
  • Suy nghĩ về các điều kiện hậu tự nhiên tạo ra một thiết kế có thành công như một điều kiện hậu , đó là phong cách tự nhiên để sử dụng các ngoại lệ. Đường dẫn thực thi ("hạnh phúc") bình thường của chương trình của bạn được trình bày tuyến tính, với tất cả các mã xử lý lỗi được chuyển sang các catchmệnh đề.

10

Tôi muốn nói rằng không có quy tắc cứng và nhanh về thời điểm sử dụng ngoại lệ. Tuy nhiên, có những lý do tốt để sử dụng hoặc không sử dụng chúng:

Lý do nên sử dụng ngoại lệ:

  • Luồng mã cho trường hợp phổ biến rõ ràng hơn
  • Có thể trả về thông tin lỗi phức tạp dưới dạng đối tượng (mặc dù điều này cũng có thể đạt được bằng cách sử dụng tham số "out" lỗi được truyền bằng tham chiếu)
  • Các ngôn ngữ thường cung cấp một số phương tiện để quản lý dọn dẹp gọn gàng trong trường hợp ngoại lệ (thử / cuối cùng bằng Java, sử dụng trong C #, RAII trong C ++)
  • Trong trường hợp không có ngoại lệ nào được đưa ra, việc thực thi đôi khi có thể nhanh hơn việc kiểm tra mã trả về
  • Trong Java, các ngoại lệ được kiểm tra phải được khai báo hoặc bắt (mặc dù đây có thể là một lý do chống lại)

Lý do không sử dụng ngoại lệ:

  • Đôi khi nó quá mức nếu xử lý lỗi đơn giản
  • Nếu các trường hợp ngoại lệ không được ghi lại hoặc khai báo, chúng có thể bị hủy bằng cách gọi mã, điều này có thể tồi tệ hơn nếu mã cuộc gọi chỉ bỏ qua mã trả về (thoát ứng dụng so với lỗi im lặng - điều tồi tệ hơn có thể phụ thuộc vào kịch bản)
  • Trong C ++, mã sử dụng ngoại lệ phải an toàn ngoại lệ (ngay cả khi bạn không ném hoặc bắt chúng, nhưng gọi gián tiếp chức năng ném)
  • Trong C ++, thật khó để biết khi nào một chức năng có thể ném, do đó bạn phải hoang tưởng về an toàn ngoại lệ nếu bạn sử dụng chúng
  • Ném và bắt ngoại lệ thường đắt hơn đáng kể so với kiểm tra cờ trả về

Nói chung, tôi sẽ có xu hướng sử dụng các ngoại lệ trong Java hơn là trong C ++ hoặc C #, vì tôi cho rằng một ngoại lệ, được khai báo hay không, về cơ bản là một phần của giao diện chính thức của một hàm, vì việc thay đổi bảo đảm ngoại lệ của bạn có thể phá mã gọi. Ưu điểm lớn nhất của việc sử dụng chúng trong Java IMO là bạn biết rằng người gọi của bạn PHẢI xử lý ngoại lệ và điều này giúp cải thiện cơ hội hành vi đúng.

Bởi vì điều này, trong bất kỳ ngôn ngữ nào, tôi sẽ luôn rút ra tất cả các ngoại lệ trong một lớp mã hoặc API từ một lớp chung, để mã gọi luôn có thể đảm bảo bắt được tất cả các ngoại lệ. Ngoài ra, tôi sẽ coi việc ném các lớp ngoại lệ là đặc thù triển khai, khi viết API hoặc thư viện (nghĩa là bọc ngoại lệ từ các lớp thấp hơn để ngoại lệ mà người gọi của bạn nhận được có thể hiểu được trong ngữ cảnh giao diện của bạn).

Lưu ý rằng Java tạo ra sự khác biệt giữa các ngoại lệ chung và ngoại lệ trong đó không cần phải khai báo cái sau. Tôi sẽ chỉ sử dụng các lớp ngoại lệ Runtime khi bạn biết rằng lỗi là kết quả của một lỗi trong chương trình.


5

Các lớp ngoại lệ giống như các lớp "bình thường". Bạn tạo một lớp mới khi nó "là" một loại đối tượng khác nhau, với các trường khác nhau và các hoạt động khác nhau.

Theo nguyên tắc thông thường, bạn nên thử cân bằng giữa số lượng ngoại lệ và mức độ chi tiết của các ngoại lệ. Nếu phương thức của bạn đưa ra hơn 4-5 trường hợp ngoại lệ khác nhau, có lẽ bạn có thể hợp nhất một số trong số chúng thành các ngoại lệ "chung" hơn, (ví dụ trong trường hợp của bạn là "Xác thực ngoại lệ") và sử dụng thông báo ngoại lệ để chi tiết những gì sai. Trừ khi mã của bạn xử lý từng loại khác nhau, bạn không cần tạo nhiều lớp ngoại lệ. Và nếu có, bạn có thể trả lại một enum với lỗi xảy ra. Cách này sạch sẽ hơn một chút.


5

Nếu mã của nó chạy bên trong một vòng lặp có thể gây ra ngoại lệ lặp đi lặp lại, thì việc ném ngoại lệ không phải là một điều tốt, bởi vì chúng khá chậm đối với N. lớn, nhưng không có gì sai khi ném ngoại lệ tùy chỉnh nếu hiệu suất không phải là hiệu suất một vấn đề. Chỉ cần đảm bảo rằng bạn có một ngoại lệ cơ bản mà tất cả chúng đều được kế thừa, được gọi là BaseException hoặc một cái gì đó tương tự. BaseException kế thừa System.Exception, nhưng tất cả các ngoại lệ của bạn đều thừa hưởng BaseException. Bạn thậm chí có thể có một cây các loại Ngoại lệ để nhóm các loại tương tự, nhưng điều này có thể hoặc không thể quá mức cần thiết.

Vì vậy, câu trả lời ngắn gọn là nếu nó không gây ra một hình phạt hiệu suất đáng kể (điều này không nên trừ khi bạn đưa ra nhiều ngoại lệ), thì hãy tiếp tục.


Tôi thực sự thích bình luận của bạn về các ngoại lệ bên trong một vòng lặp và nghĩ rằng để cho nó một thử bản thân mình. Tôi đã viết một chương trình mẫu chạy một int.MaxValuethời gian vòng lặp và tạo ra ngoại lệ 'chia cho 0' trong đó. Phiên bản IF / ELSE, trong đó tôi đã kiểm tra xem cổ tức có bằng không trước khi hoạt động phân chia hay không , đã hoàn thành trong 6082 ms và 15407722 tick trong khi phiên bản TRY / CATCH, trong đó tôi đang tạo ngoại lệ và bắt ngoại lệ , hoàn thành vào 28174385 ms và 71371326155 tick: một con số khổng lồ gấp 4632 lần so với phiên bản if / other.
user1451111

3

Tôi đồng ý với japollock trên đó - ném một nhận thức khi bạn không chắc chắn về kết quả của một hoạt động. Gọi API, truy cập hệ thống tệp, gọi cơ sở dữ liệu, v.v ... Bất cứ khi nào bạn di chuyển qua "ranh giới" của ngôn ngữ lập trình.

Tôi muốn thêm, hãy thoải mái ném một ngoại lệ tiêu chuẩn. Trừ khi bạn sẽ làm một cái gì đó "khác biệt" (bỏ qua, email, đăng nhập, cho thấy rằng hình ảnh cá voi twitter, v.v.), sau đó không bận tâm với các ngoại lệ tùy chỉnh.


3

quy tắc của việc ném ngoại lệ là khá đơn giản. bạn làm như vậy khi mã của bạn đã được nhập vào trạng thái KHÔNG GIỚI HẠN KHÔNG GIỚI HẠN. nếu dữ liệu bị xâm phạm hoặc bạn không thể quay lại quá trình xử lý xảy ra cho đến thời điểm đó thì bạn phải chấm dứt nó. thực sự bạn có thể làm gì khác? logic xử lý của bạn cuối cùng sẽ thất bại ở nơi khác. nếu bạn có thể phục hồi bằng cách nào đó thì hãy làm điều đó và đừng ném ngoại lệ.

trong trường hợp cụ thể của bạn nếu bạn bị buộc phải làm điều gì đó ngớ ngẩn như chấp nhận rút tiền và chỉ sau đó kiểm tra người dùng / mật khẩu, bạn nên chấm dứt quá trình bằng cách ném một ngoại lệ để thông báo rằng điều gì đó xấu đã xảy ra và ngăn ngừa thiệt hại thêm.


2

Nói chung, bạn muốn đưa ra một ngoại lệ cho bất kỳ điều gì có thể xảy ra trong ứng dụng của bạn là "Đặc biệt"

Trong ví dụ của bạn, cả hai trường hợp ngoại lệ đó trông giống như bạn đang gọi chúng thông qua xác thực mật khẩu / tên người dùng. Trong trường hợp đó, có thể lập luận rằng không thực sự đặc biệt rằng ai đó sẽ gõ nhầm tên người dùng / mật khẩu.

Chúng là "ngoại lệ" cho luồng chính của UML của bạn nhưng là "nhánh" hơn trong quá trình xử lý.

Nếu bạn đã cố truy cập vào tệp hoặc cơ sở dữ liệu mật khẩu của mình và không thể, đó sẽ là một trường hợp đặc biệt và sẽ đảm bảo ném một ngoại lệ.


" Nếu bạn đã cố truy cập tệp passwd hoặc cơ sở dữ liệu của mình và không thể, đó sẽ là một trường hợp ngoại lệ và sẽ đảm bảo ném một ngoại lệ. " Hầu hết các khung ngôn ngữ, ví dụ .NET framework, cũng cung cấp API để kiểm tra sự tồn tại của tệp. Tại sao không sử dụng chúng trước khi truy cập các tập tin trực tiếp!
dùng1451111

2

Thứ nhất, nếu người dùng API của bạn không quan tâm đến những thất bại cụ thể, chi tiết, thì việc có những ngoại lệ cụ thể đối với họ không có giá trị gì.

Vì thường không thể biết những gì có thể hữu ích cho người dùng của bạn, nên cách tiếp cận tốt hơn là có các ngoại lệ cụ thể, nhưng đảm bảo họ thừa hưởng từ một lớp chung (ví dụ: std :: ngoại lệ hoặc các dẫn xuất của nó trong C ++). Điều đó cho phép khách hàng của bạn nắm bắt các ngoại lệ cụ thể nếu họ chọn hoặc ngoại lệ chung hơn nếu họ không quan tâm.


2

Các ngoại lệ được dành cho các sự kiện là hành vi bất thường, lỗi, thất bại, và như vậy. Thay vào đó, hành vi chức năng, lỗi người dùng, v.v., nên được xử lý bằng logic chương trình. Vì tài khoản hoặc mật khẩu xấu là một phần được mong đợi của luồng logic trong thói quen đăng nhập, nên nó có thể xử lý các tình huống đó mà không có ngoại lệ.


2

Tôi có vấn đề triết học với việc sử dụng các ngoại lệ. Về cơ bản, bạn đang mong đợi một kịch bản cụ thể xảy ra, nhưng thay vì xử lý nó một cách rõ ràng, bạn đang đẩy vấn đề ra để được xử lý "ở nơi khác". Và nơi mà "nơi khác" có thể là dự đoán của bất cứ ai.


2

Tôi muốn nói rằng nói chung mọi chủ nghĩa cơ bản đều dẫn đến địa ngục.

Bạn chắc chắn sẽ không muốn kết thúc với dòng chảy ngoại lệ, nhưng tránh hoàn toàn ngoại lệ cũng là một ý tưởng tồi. Bạn phải tìm một sự cân bằng giữa cả hai phương pháp. Những gì tôi sẽ không làm là tạo ra một loại ngoại lệ cho mọi tình huống đặc biệt. Đó là không hiệu quả.

Những gì tôi thường thích là tạo ra hai loại ngoại lệ cơ bản được sử dụng trên toàn hệ thống: LogicalExceptionTechnicalException . Chúng có thể được phân biệt bằng các kiểu con nếu cần, nhưng nói chung là không cần thiết.

Ngoại lệ kỹ thuật biểu thị ngoại lệ thực sự bất ngờ như máy chủ cơ sở dữ liệu bị ngừng hoạt động, kết nối với dịch vụ web đã ném IOException, v.v.

Mặt khác, các ngoại lệ logic được sử dụng để truyền bá tình huống sai lầm ít nghiêm trọng hơn đến các lớp trên (nói chung là một số kết quả xác nhận).

Xin lưu ý rằng ngay cả ngoại lệ logic không được sử dụng thường xuyên để kiểm soát luồng chương trình, mà là để làm nổi bật tình huống khi luồng thực sự kết thúc. Khi được sử dụng trong Java, cả hai loại ngoại lệ là RuntimeException lớp con và xử lý lỗi được định hướng theo khía cạnh cao.

Vì vậy, trong ví dụ đăng nhập, có thể là khôn ngoan khi tạo một cái gì đó như xác thựcException và phân biệt các tình huống cụ thể bằng các giá trị enum như UsernameNotEx hiện tại , PasswordMismatch , v.v. Sau đó, bạn sẽ không có một hệ thống phân cấp ngoại lệ lớn và có thể giữ các khối bắt ở mức có thể duy trì . Bạn cũng có thể dễ dàng sử dụng một số cơ chế xử lý ngoại lệ chung vì bạn có các ngoại lệ được phân loại và biết khá rõ những gì cần truyền bá cho người dùng và làm thế nào.

Cách sử dụng thông thường của chúng tôi là ném LogicalException trong cuộc gọi Dịch vụ web khi đầu vào của người dùng không hợp lệ. Ngoại lệ được sắp xếp theo chi tiết SOAPFault và sau đó lại không được xử lý ngoại lệ trên máy khách, điều này dẫn đến việc hiển thị lỗi xác thực trên một trường nhập trang web nhất định do ngoại lệ có ánh xạ phù hợp với trường đó.

Đây chắc chắn không phải là tình huống duy nhất: bạn không cần phải đánh dịch vụ web để đưa ra ngoại lệ. Bạn có thể tự do làm điều đó trong bất kỳ tình huống đặc biệt nào (như trong trường hợp bạn cần phải thất bại nhanh) - tất cả là theo ý của bạn.


2

đối với tôi Ngoại lệ nên được ném khi quy tắc kinh doanh hoặc kỹ thuật bắt buộc không thành công. chẳng hạn, nếu một thực thể ô tô được liên kết với mảng 4 lốp xe ... nếu một lốp xe trở lên là null ... thì ngoại lệ phải là "NotEnoughTiresException", vì nó có thể bị bắt ở cấp độ khác nhau của hệ thống và có một mức đáng kể nghĩa là thông qua đăng nhập. bên cạnh đó nếu chúng ta chỉ cố gắng điều khiển dòng chảy null và ngăn chặn sự xâm nhập của chiếc xe. chúng ta có thể không bao giờ tìm thấy nguồn gốc của vấn đề, vì lốp xe không phải là không có giá trị ngay từ đầu.


1

lý do chính để tránh ném ngoại lệ là có rất nhiều chi phí liên quan đến việc ném ngoại lệ.

Một điều mà bài viết dưới đây nêu rõ là một ngoại lệ dành cho một điều kiện và lỗi đặc biệt.

Tên người dùng sai không nhất thiết là lỗi chương trình mà là lỗi người dùng ...

Đây là một điểm khởi đầu tốt cho các trường hợp ngoại lệ trong .NET: http://msdn.microsoft.com/en-us/l Library / ms229030 (VS.80) .aspx


1

Ném ngoại lệ làm cho ngăn xếp thư giãn, có một số tác động hiệu suất (thừa nhận, môi trường được quản lý hiện đại đã cải thiện điều đó). Vẫn liên tục ném và bắt ngoại lệ trong một tình huống lồng nhau sẽ là một ý tưởng tồi.

Có lẽ quan trọng hơn thế, ngoại lệ có nghĩa là cho các điều kiện đặc biệt. Chúng không nên được sử dụng cho luồng điều khiển thông thường, vì điều này sẽ làm tổn thương khả năng đọc mã của bạn.


1

Tôi có ba loại điều kiện mà tôi bắt được.

  1. Đầu vào xấu hoặc thiếu không nên là một ngoại lệ. Sử dụng cả js phía máy khách và regex phía máy chủ để phát hiện, đặt thuộc tính và chuyển tiếp trở lại cùng một trang với các tin nhắn.

  2. Các ngoại lệ. Đây thường là một ngoại lệ mà bạn phát hiện và ném vào trong mã của mình. Nói cách khác, đây là những cái bạn mong đợi (tệp không tồn tại). Đăng nhập nó, đặt thông báo và chuyển trở lại trang lỗi chung. Trang này thường có một chút thông tin về những gì đã xảy ra.

  3. Ngoại lệ bất ngờ. Đây là những cái bạn không biết. Đăng nhập nó với các chi tiết và chuyển tiếp chúng đến một trang lỗi chung.

Hi vọng điêu nay co ich


1

Bảo mật được giới hạn với ví dụ của bạn: Bạn không nên nói với kẻ tấn công rằng tên người dùng tồn tại, nhưng mật khẩu bị sai. Đó là thông tin bổ sung mà bạn không cần chia sẻ. Chỉ cần nói "tên người dùng hoặc mật khẩu không chính xác."


1

Câu trả lời đơn giản là, bất cứ khi nào một hoạt động là không thể (vì một trong hai ứng dụng HOẶC vì nó sẽ vi phạm logic kinh doanh). Nếu một phương thức được gọi và không thể thực hiện những gì phương thức được viết để làm, hãy ném Ngoại lệ. Một ví dụ điển hình là các hàm tạo luôn ném ArgumentExceptions nếu một thể hiện có thể được tạo bằng các tham số được cung cấp. Một ví dụ khác là UnlimitedOperationException, được ném khi một hoạt động không thể được thực hiện do trạng thái của một thành viên khác hoặc các thành viên của lớp.

Trong trường hợp của bạn, nếu một phương thức như Đăng nhập (tên người dùng, mật khẩu) được gọi, nếu tên người dùng không hợp lệ, thì thực sự đúng khi ném UserNameNotValidException hoặc PasswordNotC CorrException nếu mật khẩu không chính xác. Người dùng không thể đăng nhập bằng (các) tham số được cung cấp (nghĩa là không thể vì nó sẽ vi phạm xác thực), vì vậy hãy ném Ngoại lệ. Mặc dù tôi có thể thừa hưởng hai Ngoại lệ của bạn từ ArgumentException.

Như đã nói, nếu bạn muốn KHÔNG ném Ngoại lệ vì lỗi đăng nhập có thể rất phổ biến, một chiến lược là thay vào đó tạo ra một phương thức trả về các loại đại diện cho các lỗi khác nhau. Đây là một ví dụ:

{ // class
    ...

    public LoginResult Login(string user, string password)
    {
        if (IsInvalidUser(user))
        {
            return new UserInvalidLoginResult(user);
        }
        else if (IsInvalidPassword(user, password))
        {
            return new PasswordInvalidLoginResult(user, password);
        }
        else
        {
            return new SuccessfulLoginResult();
        }
    }

    ...
}

public abstract class LoginResult
{
    public readonly string Message;

    protected LoginResult(string message)
    {
        this.Message = message;
    }
}

public class SuccessfulLoginResult : LoginResult
{
    public SucccessfulLogin(string user)
        : base(string.Format("Login for user '{0}' was successful.", user))
    { }
}

public class UserInvalidLoginResult : LoginResult
{
    public UserInvalidLoginResult(string user)
        : base(string.Format("The username '{0}' is invalid.", user))
    { }
}

public class PasswordInvalidLoginResult : LoginResult
{
    public PasswordInvalidLoginResult(string password, string user)
        : base(string.Format("The password '{0}' for username '{0}' is invalid.", password, user))
    { }
}

Hầu hết các nhà phát triển được dạy để tránh Ngoại lệ vì chi phí gây ra bằng cách ném chúng. Thật tuyệt khi có ý thức về tài nguyên, nhưng thường không phải trả giá cho thiết kế ứng dụng của bạn. Đó có lẽ là lý do bạn được khuyên không nên ném hai Ngoại lệ của mình. Có nên sử dụng Ngoại lệ hay không thường làm giảm mức độ thường xuyên của Ngoại lệ. Nếu đó là một kết quả khá phổ biến hoặc khá đáng mong đợi, thì đó là khi hầu hết các nhà phát triển sẽ tránh Ngoại lệ và thay vào đó tạo ra một phương pháp khác để biểu thị sự thất bại, vì tiêu thụ tài nguyên được cho là.

Đây là một ví dụ về việc tránh sử dụng Ngoại lệ trong một kịch bản như vừa mô tả, sử dụng mẫu Thử ():

public class ValidatedLogin
{
    public readonly string User;
    public readonly string Password;

    public ValidatedLogin(string user, string password)
    {
        if (IsInvalidUser(user))
        {
            throw new UserInvalidException(user);
        }
        else if (IsInvalidPassword(user, password))
        {
            throw new PasswordInvalidException(password);
        }

        this.User = user;
        this.Password = password;
    }

    public static bool TryCreate(string user, string password, out ValidatedLogin validatedLogin)
    {
        if (IsInvalidUser(user) || 
            IsInvalidPassword(user, password))
        {
            return false;
        }

        validatedLogin = new ValidatedLogin(user, password);

        return true;
    }
}

1

Theo tôi, câu hỏi cơ bản nên là liệu người ta có mong đợi rằng người gọi có muốn tiếp tục dòng chương trình bình thường hay không nếu có một điều kiện xảy ra. Nếu bạn không biết, có các phương thức doS Something và tryS Something riêng biệt, trong đó phương thức trước trả về lỗi và phương thức sau không, hoặc có một thói quen chấp nhận một tham số để cho biết liệu có nên ném ngoại lệ nếu thất bại hay không. Xem xét một lớp để gửi lệnh đến một hệ thống từ xa và báo cáo phản hồi. Một số lệnh nhất định (ví dụ khởi động lại) sẽ khiến hệ thống từ xa gửi phản hồi nhưng sau đó không phản hồi trong một khoảng thời gian nhất định. Do đó, rất hữu ích khi có thể gửi lệnh "ping" và tìm hiểu xem hệ thống từ xa có phản hồi trong một khoảng thời gian hợp lý mà không phải ném ngoại lệ nếu nó không ' t (người gọi có thể mong đợi rằng một vài lần thử "ping" đầu tiên sẽ thất bại, nhưng cuối cùng một lần sẽ hoạt động). Mặt khác, nếu ai đó có một chuỗi các lệnh như:

  exchange_command ("mở tempfile");
  exchange_command ("ghi dữ liệu tempfile {anything}");
  exchange_command ("ghi dữ liệu tempfile {anything}");
  exchange_command ("ghi dữ liệu tempfile {anything}");
  exchange_command ("ghi dữ liệu tempfile {anything}");
  exchange_command ("đóng tempfile");
  exchange_command ("sao chép tempfile sang realfile");

người ta muốn thất bại của bất kỳ hoạt động nào để hủy bỏ toàn bộ chuỗi. Mặc dù người ta có thể kiểm tra từng thao tác để đảm bảo nó thành công, nhưng sẽ hữu ích hơn khi thói quen exchange_command () ném ngoại lệ nếu một lệnh thất bại.

Trên thực tế, trong kịch bản trên, có thể hữu ích khi có một tham số để chọn một số chế độ xử lý lỗi: không bao giờ ném ngoại lệ, chỉ ném ngoại lệ cho lỗi giao tiếp hoặc ném ngoại lệ trong bất kỳ trường hợp nào lệnh không trả về "thành công "Chỉ định.


1

"PasswordNotC CorrException" không phải là một ví dụ hay cho việc sử dụng các ngoại lệ. Người dùng nhận được mật khẩu sai là điều được mong đợi, do đó, hầu như không phải là ngoại lệ IMHO. Bạn thậm chí có thể khôi phục từ nó, hiển thị một thông báo lỗi tốt, vì vậy đó chỉ là kiểm tra tính hợp lệ.

Các trường hợp ngoại lệ chưa được xử lý cuối cùng sẽ dừng việc thực thi - điều này là tốt. Nếu bạn trả lại mã sai, null hoặc mã lỗi, bạn sẽ phải tự mình xử lý trạng thái của chương trình. Nếu bạn quên kiểm tra các điều kiện ở đâu đó, chương trình của bạn có thể tiếp tục chạy với dữ liệu sai và bạn có thể gặp khó khăn trong việc tìm hiểu điều gì đã xảy ra và ở đâu .

Tất nhiên, bạn có thể gây ra vấn đề tương tự với các câu lệnh bắt trống, nhưng ít nhất việc phát hiện ra những câu đó dễ hơn và không yêu cầu bạn phải hiểu logic.

Vì vậy, như một quy tắc của ngón tay cái:

Sử dụng chúng bất cứ nơi nào bạn không muốn hoặc đơn giản là không thể phục hồi từ một lỗi.


0

Bạn có thể sử dụng một chút ngoại lệ chung cho các điều kiện đó. Ví dụ: ArgumentException có nghĩa là được sử dụng khi có bất kỳ sự cố nào xảy ra với các tham số cho một phương thức (ngoại trừ ArgumentNullException). Nói chung, bạn sẽ không cần các ngoại lệ như LessThanZeroException, NotPrimeNumberException, v.v. Hãy nghĩ về người dùng phương pháp của bạn. Số lượng các điều kiện mà cô ấy sẽ muốn xử lý cụ thể bằng với số loại ngoại lệ mà phương thức của bạn cần đưa ra. Bằng cách này, bạn có thể xác định cách bạn sẽ có ngoại lệ chi tiết.

Nhân tiện, luôn cố gắng cung cấp một số cách cho người dùng thư viện của bạn để tránh ngoại lệ. TryPude là một ví dụ hay, nó tồn tại để bạn không phải sử dụng int.Pude và bắt ngoại lệ. Trong trường hợp của bạn, bạn có thể muốn cung cấp một số phương pháp để kiểm tra xem tên người dùng có hợp lệ hay mật khẩu là chính xác để người dùng của bạn (hoặc bạn) sẽ không phải xử lý ngoại lệ. Điều này hy vọng sẽ dẫn đến mã dễ đọc hơn và hiệu suất tốt hơn.


0

Cuối cùng, quyết định đưa ra liệu có hữu ích hơn khi xử lý các lỗi ở cấp ứng dụng như thế này bằng cách xử lý ngoại lệ hoặc thông qua cơ chế cuộn tại nhà của bạn như trả lại mã trạng thái. Tôi không nghĩ rằng có một quy tắc khó và nhanh về cái nào tốt hơn, nhưng tôi sẽ xem xét:

  • Ai đang gọi mã của bạn? Đây có phải là API công khai của một số loại hoặc một thư viện nội bộ?
  • Ngôn ngữ của bạn đang sử dụng là gì? Ví dụ, nếu đó là Java, thì việc ném ngoại lệ (đã kiểm tra) sẽ tạo gánh nặng rõ ràng cho người gọi của bạn để xử lý tình trạng lỗi này theo một cách nào đó, trái ngược với trạng thái trả về có thể bị bỏ qua. Điều đó có thể là tốt hoặc xấu.
  • Các điều kiện lỗi khác trong cùng một ứng dụng được xử lý như thế nào? Người gọi sẽ không muốn xử lý một mô-đun xử lý lỗi theo cách bình dị không giống như bất kỳ điều gì khác trong hệ thống.
  • Có bao nhiêu điều có thể sai với thói quen trong câu hỏi, và chúng sẽ được xử lý khác nhau như thế nào? Xem xét sự khác biệt giữa một loạt các khối bắt xử lý các lỗi khác nhau và chuyển sang mã lỗi.
  • Bạn có cấu trúc thông tin về lỗi bạn cần trả lại không? Ném một ngoại lệ cung cấp cho bạn một nơi tốt hơn để đưa thông tin này thay vì chỉ trả lại trạng thái.

0

Có hai loại ngoại lệ chính:

1) Ngoại lệ hệ thống (ví dụ: Mất kết nối cơ sở dữ liệu) hoặc 2) Ngoại lệ người dùng. (ví dụ: Xác thực nhập của người dùng, 'mật khẩu không chính xác')

Tôi thấy hữu ích khi tạo Lớp ngoại lệ người dùng của riêng mình và khi tôi muốn đưa ra lỗi người dùng, tôi muốn xử lý khác nhau (tức là lỗi được cung cấp lại cho người dùng), sau đó tất cả những gì tôi cần làm trong trình xử lý lỗi chính của mình là kiểm tra loại đối tượng :

            If TypeName(ex) = "UserException" Then
               Display(ex.message)
            Else
               DisplayError("An unexpected error has occured, contact your help  desk")                   
               LogError(ex)
            End If

0

Một số điều hữu ích để suy nghĩ khi quyết định liệu một ngoại lệ có phù hợp hay không:

  1. mức độ mã bạn muốn chạy sau khi ứng viên ngoại lệ xảy ra - nghĩa là, có bao nhiêu lớp của ngăn xếp cuộc gọi sẽ thư giãn. Bạn thường muốn xử lý một ngoại lệ càng gần càng tốt với nơi nó xảy ra. Để xác thực tên người dùng / mật khẩu, thông thường bạn sẽ xử lý các lỗi trong cùng một khối mã, thay vì để một bong bóng ngoại lệ xuất hiện. Vì vậy, một ngoại lệ có lẽ là không phù hợp. (OTOH, sau ba lần đăng nhập thất bại, luồng kiểm soát có thể thay đổi ở nơi khác và một ngoại lệ có thể phù hợp ở đây.)

  2. Đây có phải là sự kiện mà bạn muốn thấy trong nhật ký lỗi không? Không phải mọi ngoại lệ được ghi vào nhật ký lỗi, nhưng thật hữu ích khi hỏi liệu mục này trong nhật ký lỗi có hữu ích hay không - tức là bạn sẽ cố gắng làm gì đó về nó, hoặc sẽ là rác bạn bỏ qua.

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.