it-swarm-vi.tech

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

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, PasswordNotCorrectException, v.v.

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ệ?

402
Kwan Cheng

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 được tìm thấy 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 không 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 hàm trả về hoặc thật hoặc là sai, sau đó nó đang phá vỡ các quy tắc riêng của nó. Hàm không thể quay lại 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 "câu hỏi được tải" ngụy biện logic. 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ó sẽ 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.

577
The Digital Gabeg

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ệ. Các trường hợp ngoại lệ phải là một điều thực sự hiếm, các tình huống loại UserHasDiedAtKeyboard.

279
blowdart

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.
  • Các ngoại lệ nên được dành riêng cho những gì thực sự đặc biệt.
61
Commander Keen

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").

34
EricSchaefer

Một nguyên tắc nhỏ là sử dụng các ngoại lệ trong trường hợp điều gì đó 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 sai mật khẩu của họ.

25
japollock

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, webrequest, 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 mật khẩu chính xác và tập tin 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ề 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.

23
Bjorn Reppen

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.

16
Mitchel Sellers

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 (cho C++, có sẵn trong bài viết của Tiến sĩ Dobbs Khi nào và Cách sử dụng Ngoại lệ , và cả trong cuốn sách của anh ấy 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 số 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 định throw 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ĩ theo các điều kiện tiên quyết, hậu điều kiện và bất biến, đó là các giả định mà người gọi phương thức của chúng ta nên thực hiện 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 của chúng tôi.
  • 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 được kiểm tra đơn vị, bởi vì các điều kiện hậu là các vị từ dự đoán mã kiểm thử đơ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 mệnh đề catch.
14
Jon

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 chính đáng để 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 một đố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 có thể đôi khi nhanh hơn 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 là ngoại lệ an toàn (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à bao bọc cá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.

10
Robert

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
Shachar

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.

5
Charles Graham

Tôi đồng ý với japollock trên đó - đưa ra 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 của bạn.

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
dclaysmith

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.

3
goran

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ả.

Điều tôi thường thích là tạo 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 các 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ư sernameNotEx hiện, PasswordMismatch vv Sau đó, bạn sẽ không kết thúc 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
Petr Macek

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
Joe Skora

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" nhiều 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ệ.

2
Gord

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
Jason Etheridge

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 "nơi khác" có thể là dự đoán của bất cứ ai.

2
Dan

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. Ứng dụng 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
Michael

đối với tôi Ngoại lệ nên được ném khi một quy tắc kỹ thuật hoặc kinh doanh 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 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 được coi là không có giá trị ngay từ đầu.

1
Genjuro

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 OR 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ì đây là lúc 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
core

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 tham số để chỉ ra 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 không (người gọi có thể mong đợi rằng vài lần đầu tiên " ping "cố gắng sẽ thất bại, nhưng cuối cùng một người sẽ làm việc). Mặt khác, nếu ai đó có một chuỗi các lệnh như:

[.__.] exchange_command ("open tempfile"); [.__. .] exchange_command ("ghi dữ liệu tempfile {bất cứ điều gì}"); [.___ ("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 trường hợp 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 mọi trường hợp lệnh không trả về "thành công "Chỉ định.

1
supercat

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
Sam

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
anon

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
Arno

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 thích 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 nhập 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.

0
Mike Kantor

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 xử lý lỗi người dùng, tôi muốn xử lý khác (nghĩa 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
Crusty

"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ị thông báo lỗi Nice, vì vậy đâ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 đ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 khi tìm ra what đã xảy ra và where.

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
DanMan

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
Serhat Ozgel

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
eli