Tại sao chúng ta không ném những ngoại lệ này?


111

Tôi đã xem trang MSDN này nói rằng:

Không cố tình ném Exception , SystemException , NullReferenceException hoặc IndexOutOfRangeException khỏi mã nguồn của riêng bạn.

Thật không may, nó không bận tâm để giải thích tại sao. Tôi có thể đoán lý do nhưng tôi hy vọng rằng ai đó có thẩm quyền hơn về chủ đề này có thể cung cấp cái nhìn sâu sắc của họ.

Hai cái đầu tiên có ý nghĩa rõ ràng, nhưng hai cái sau có vẻ giống như những cái bạn muốn tuyển dụng (và trên thực tế, tôi có).

Hơn nữa, đây có phải là những ngoại lệ duy nhất mà người ta nên tránh không? Nếu có những người khác, họ là gì và tại sao họ cũng nên tránh?


22
Từ msdn.microsoft.com/en-us/library/ms182338.aspx : Nếu bạn ném một loại ngoại lệ chung, chẳng hạn như Exception hoặc SystemException trong thư viện hoặc khuôn khổ, nó buộc người tiêu dùng phải bắt tất cả các ngoại lệ, bao gồm cả những ngoại lệ không xác định mà họ thực hiện không biết cách xử lý.
Henrik

3
Tại sao bạn lại ném NullReferenceException?
Rik

5
@Rik: tương tự như NullArgumentExceptionmột số người có thể nhầm lẫn cả hai.
thờ Hồi giáo Ben Dhaou

2
Phương pháp mở rộng @Rik, theo câu trả lời của tôi
Marc Gravell

3
Một số khác bạn không nên ném làApplicationException
Matthew Watson

Câu trả lời:


98

Exceptionlà kiểu cơ sở cho tất cả các trường hợp ngoại lệ và như vậy là không cụ thể. Bạn không bao giờ nên ném ngoại lệ này vì nó chỉ đơn giản là không chứa bất kỳ thông tin hữu ích nào. Việc gọi mã bắt mã cho các ngoại lệ không thể phân biệt ngoại lệ được cố ý ném (theo logic của bạn) với các ngoại lệ hệ thống khác hoàn toàn không mong muốn và chỉ ra các lỗi thực sự.

Lý do tương tự cũng áp dụng cho SystemException. Nếu bạn nhìn vào danh sách các kiểu dẫn xuất, bạn có thể thấy một số lượng lớn các ngoại lệ khác với ngữ nghĩa rất khác nhau.

NullReferenceExceptionIndexOutOfRangeExceptionthuộc một loại khác. Bây giờ đây là những ngoại lệ rất cụ thể, vì vậy bạn có thể ném chúng đi . Tuy nhiên, bạn vẫn sẽ không muốn ném chúng đi, vì chúng thường có nghĩa là có một số sai lầm thực tế trong logic của bạn. Ví dụ, ngoại lệ tham chiếu null có nghĩa là bạn đang cố gắng truy cập vào một thành viên của một đối tượng null. Nếu đó là một khả năng xảy ra trong mã của bạn, thì bạn nên luôn kiểm tra rõ ràng nullvà thay vào đó đưa ra một ngoại lệ hữu ích hơn (ví dụ ArgumentNullException). Tương tự, IndexOutOfRangeExceptions xảy ra khi bạn truy cập một chỉ mục không hợp lệ (trên mảng — không phải danh sách). Bạn phải luôn đảm bảo rằng bạn không làm điều đó ngay từ đầu và kiểm tra ranh giới của ví dụ một mảng trước.

Ví dụ , có một vài trường hợp ngoại lệ khác như hai trường hợp đó InvalidCastExceptionhoặc DivideByZeroExceptionđược đưa ra cho các lỗi cụ thể trong mã của bạn và thường có nghĩa là bạn đang làm sai hoặc bạn không kiểm tra một số giá trị không hợp lệ trước. Bằng cách cố ý ném chúng khỏi mã của bạn, bạn chỉ làm cho mã gọi khó xác định xem chúng bị ném do lỗi nào đó trong mã hay chỉ vì bạn quyết định sử dụng lại chúng cho một cái gì đó trong quá trình triển khai của mình.

Tất nhiên, có một số ngoại lệ (hah) đối với các quy tắc này. Nếu bạn đang xây dựng một thứ gì đó có thể gây ra một ngoại lệ khớp chính xác với một cái hiện có, thì hãy thoải mái sử dụng cái đó, đặc biệt nếu bạn đang cố gắng khớp một số hành vi được tạo sẵn. Chỉ cần đảm bảo rằng bạn chọn một loại ngoại lệ rất cụ thể sau đó.

Nói chung, mặc dù vậy, trừ khi bạn tìm thấy một ngoại lệ (cụ thể) đáp ứng nhu cầu của mình, bạn nên luôn cân nhắc tạo các loại ngoại lệ của riêng mình cho các ngoại lệ dự kiến ​​cụ thể. Đặc biệt khi bạn đang viết mã thư viện, điều này có thể rất hữu ích để tách các nguồn ngoại lệ.


3
Phần thứ ba có ý nghĩa nhỏ đối với tôi. Chắc chắn, bạn nên tránh gây ra những lỗi này bắt đầu, nhưng khi bạn viết một IListtriển khai, ví dụ: bạn không thể ảnh hưởng đến các chỉ số được yêu cầu, đó là lỗi logic của người gọi khi một chỉ mục không hợp lệ và bạn chỉ có thể thông báo cho họ của lỗi logic này bằng cách ném một ngoại lệ thích hợp. Tại sao IndexOutOfRangeExceptionkhông thích hợp?

7
@delnan Nếu bạn đang triển khai IList, thì bạn sẽ ném ArgumentOutOfRangeExceptionnhư tài liệu giao diện gợi ý. IndexOutOfRangeExceptionlà dành cho mảng và theo như tôi biết, bạn không thể triển khai lại mảng.
poke

2
Điều gì cũng có thể có phần nào liên quan, NullReferenceExceptionthường được đưa ra bên trong như một trường hợp đặc biệt của một AccessViolationException(IIRC mà bài kiểm tra là một cái gì đó giống như cmp [addr], addr, tức là nó cố gắng bỏ tham chiếu đến con trỏ và nếu nó không thành công với vi phạm quyền truy cập, nó sẽ xử lý sự khác biệt giữa NRE và AVE trong trình xử lý ngắt kết quả). Vì vậy, ngoài lý do ngữ nghĩa, cũng có một số gian lận liên quan. Nó cũng có thể giúp không khuyến khích bạn kiểm tra nulltheo cách thủ công khi nó không hữu ích - nếu bạn vẫn định ném NRE, tại sao không để .NET làm điều đó?
Luaan

Đối với tuyên bố cuối cùng của bạn, về các ngoại lệ tùy chỉnh: Tôi chưa bao giờ cảm thấy cần phải làm điều này. Có lẽ tôi đang bỏ lỡ điều gì đó. Trong những điều kiện nào người ta sẽ cần tạo một kiểu ngoại lệ tùy chỉnh thay cho một thứ gì đó từ khung?
DonBoitnott

1
Chà, đối với các ứng dụng nhỏ hơn có thể không cần. Nhưng ngay sau khi bạn tạo ra một thứ gì đó phức tạp hơn trong đó các bộ phận riêng lẻ hoạt động như những “thành phần” độc lập, bạn thường nên đưa ra các ngoại lệ tùy chỉnh cho các tình huống lỗi tùy chỉnh. Ví dụ: nếu bạn có một số lớp kiểm soát truy cập và bạn cố gắng thực thi một số dịch vụ mặc dù bạn không được phép làm như vậy, bạn có thể ném một "ngoại lệ bị từ chối truy cập" tùy chỉnh hoặc một cái gì đó. Hoặc nếu bạn có trình phân tích cú pháp phân tích một số tệp, bạn có thể có lỗi trình phân tích cú pháp của riêng mình để báo cáo lại cho người dùng.
poke

36

Tôi nghi ngờ mục đích với 2 cuối cùng là để tránh nhầm lẫn với các ngoại lệ có sẵn có ý nghĩa mong đợi. Tuy nhiên, tôi cho rằng nếu bạn đang duy trì ý định chính xác của ngoại lệ : thì đó là ý định chính xác throw. Ví dụ: nếu bạn đang viết một bộ sưu tập tùy chỉnh, nó có vẻ hoàn toàn hợp lý khi sử dụng IndexOutOfRangeException- rõ ràng và cụ thể hơn, IMO, hơn ArgumentOutOfRangeException. Và trong khi List<T>có thể chọn cái sau, có ít nhất 41 địa điểm (lịch sự của phản xạ) trong BCL (không bao gồm các mảng) ném theo yêu cầu IndexOutOfRangeException- không có địa điểm nào đủ "cấp thấp" để xứng đáng được miễn trừ đặc biệt. Vì vậy, tôi nghĩ bạn có thể lập luận rằng hướng dẫn đó là ngớ ngẩn. Tương tự,NullReferenceException khá hữu ích trong các phương thức mở rộng - nếu bạn muốn duy trì ngữ nghĩa rằng:

obj.SomeMethod(); // this is actually an extension method

ném một NullReferenceExceptionkhi objnull.


2
"nếu bạn đang duy trì ý định chính xác của ngoại lệ" - chắc chắn nếu trường hợp đó xảy ra, ngoại lệ sẽ được ném ra mà bạn không cần phải kiểm tra nó ngay từ đầu? Và nếu bạn đã thử nghiệm nó, thì nó không thực sự là một ngoại lệ?
PugFugly

5
@PugFugly hãy dành 2 giây để xem ví dụ về phương thức mở rộng: không, điều đó sẽ không được ném ra mà bạn không cần phải kiểm tra nó. Nếu SomeMethod()không cần thực hiện quyền truy cập thành viên, thì việc ép buộc nó là không chính xác. Tương tự: lấy điểm đó lên với 41 địa điểm trong BCL tạo ra tùy chỉnh IndexOutOfRangeExceptionvà 16 địa điểm tạo ra tùy chỉnhNullReferenceException
Marc Gravell

16
Tôi sẽ tranh luận rằng một phương thức mở rộng vẫn nên ném một ArgumentNullExceptionthay vì a NullReferenceException. Ngay cả khi cú pháp từ các phương thức mở rộng cho phép cú pháp giống như truy cập thành viên bình thường, nó vẫn hoạt động rất khác. Và việc nhận NRE từ MyStaticHelpers.SomeMethod(obj)sẽ chỉ là sai lầm.
poke

1
@PugFugly BCL là “thư viện lớp cơ sở”, về cơ bản là nội dung cốt lõi trong .NET.
poke

1
@PugFugly: Có nhiều trường hợp trong đó việc không phát hiện trước một điều kiện sẽ dẫn đến một ngoại lệ được đưa ra vào thời điểm "bất tiện". Nếu một hoạt động không thành công, việc ném một ngoại lệ sớm sẽ tốt hơn là bắt đầu hoạt động, thực hiện giữa chừng và sau đó phải dọn dẹp mớ hỗn độn đã được xử lý một phần.
supercat

5

Như bạn đã chỉ ra, trong bài viết Tạo và ném ngoại lệ (Hướng dẫn lập trình C #) với chủ đề Những điều cần tránh khi ném ngoại lệ , Microsoft thực sự liệt kê System.IndexOutOfRangeExceptionlà một loại ngoại lệ không nên cố ý ném khỏi mã nguồn của chính bạn.

Tuy nhiên, ngược lại, trong bài ném (C # Reference) , Microsoft dường như đã vi phạm các nguyên tắc của chính mình. Đây là một phương pháp mà Microsoft đã đưa vào ví dụ của nó:

static int GetNumber(int index)
{
    int[] nums = { 300, 600, 900 };
    if (index > nums.Length)
    {
        throw new IndexOutOfRangeException();
    }
    return nums[index];
}

Vì vậy, bản thân Microsoft không nhất quán vì nó thể hiện việc ném IndexOutOfRangeExceptionvào tài liệu của mình throw!

Điều này khiến tôi tin rằng ít nhất là đối với trường hợp của IndexOutOfRangeException, có thể có những trường hợp mà kiểu ngoại lệ đó có thể được lập trình viên ném ra và được coi là một phương pháp chấp nhận được.


1

Khi tôi đọc câu hỏi của bạn, tôi đã tự hỏi mình trong những điều kiện nào mà người ta muốn ném các loại ngoại lệ NullReferenceException, InvalidCastExceptionhoặc ArgumentOutOfRangeException.

Theo ý kiến ​​của tôi, khi gặp một trong những loại ngoại lệ đó, tôi (nhà phát triển) cảm thấy lo lắng bởi cảnh báo theo nghĩa rằng trình biên dịch đang nói chuyện với tôi. Vì vậy, việc cho phép bạn (nhà phát triển) ném các loại ngoại lệ như vậy tương đương với việc (trình biên dịch) bán trách nhiệm. Ví dụ, điều này cho thấy trình biên dịch bây giờ nên cho phép nhà phát triển quyết định xem có phải là một đối tượng hay không null. Nhưng việc xác định như vậy thực sự nên là công việc của trình biên dịch.

Tái bút: Kể từ năm 2003, tôi đã phát triển các ngoại lệ của riêng mình để tôi có thể ném chúng theo ý muốn. Tôi nghĩ rằng nó được coi là một thực tiễn tốt nhất để làm như vậy.


Điểm tốt. Tuy nhiên, tôi nghĩ sẽ chính xác hơn nếu nói rằng lập trình viên nên để thời gian chạy .NET Framework ném các loại ngoại lệ này (và rằng lập trình viên nên xử lý chúng theo cách thích hợp).
DavidRR

0

Đặt cuộc thảo luận về NullReferenceExceptionIndexOutOfBoundsExceptionsang một bên:

Còn về việc bắt và ném System.Exception. Tôi đã ném loại ngoại lệ này vào mã của mình rất nhiều và tôi chưa bao giờ bị nó làm cho mê mẩn. Tương tự, tôi rất thường xuyên bắt gặp Exceptionloại không cụ thể và nó cũng hoạt động khá tốt đối với tôi. Vậy, tại sao lại như vậy?

Thông thường người dùng tranh luận rằng họ có thể phân biệt nguyên nhân lỗi. Theo kinh nghiệm của tôi, chỉ có một số tình huống mà bạn muốn xử lý các loại ngoại lệ khác nhau theo cách khác nhau. Đối với những trường hợp đó, khi bạn mong đợi người dùng xử lý lỗi theo chương trình, bạn nên đưa ra một loại ngoại lệ cụ thể hơn. Đối với các trường hợp khác, tôi không bị thuyết phục bởi hướng dẫn thực hành tốt nhất chung.

Vì vậy, Exceptiontôi không thấy lý do gì để cấm điều này trong mọi trường hợp.

CHỈNH SỬA: cũng từ trang MSDN:

Không nên sử dụng ngoại lệ để thay đổi luồng chương trình như một phần của quá trình thực thi thông thường. Các ngoại lệ chỉ nên được sử dụng để báo cáo và xử lý các điều kiện lỗi.

Lạm dụng các mệnh đề bắt với logic riêng lẻ cho các loại ngoại lệ khác nhau cũng không phải là phương pháp hay nhất.


Thông báo ngoại lệ vẫn được đưa ra để cho biết điều gì đã xảy ra. Tôi không thể hình dung rằng bạn đi và tạo một loại ngoại lệ mới cho từng lỗi khác nhau, đề phòng trường hợp người tiêu dùng có thể muốn phân biệt những lỗi đó, theo chương trình.
uebe

Điều gì đã xảy ra với nhận xét trước đây của tôi?
DonBoitnott
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.