Trả lại giá trị ma thuật, ném ngoại lệ hoặc trả lại sai khi thất bại?


83

Đôi khi tôi cuối cùng phải viết một phương thức hoặc thuộc tính cho một thư viện lớp mà không có câu trả lời thực sự nào, nhưng đó là một thất bại. Một cái gì đó không thể được xác định, không có sẵn, không tìm thấy, hiện không thể hoặc không có thêm dữ liệu.

Tôi nghĩ có ba giải pháp khả thi cho một tình huống tương đối không đặc biệt như vậy để chỉ ra sự thất bại trong C # 4:

  • trả về một giá trị ma thuật không có ý nghĩa khác (như null-1);
  • ném một ngoại lệ (ví dụ KeyNotFoundException);
  • trả về falsevà cung cấp giá trị trả về thực tế trong một outtham số, (chẳng hạn như Dictionary<,>.TryGetValue).

Vì vậy, các câu hỏi là: trong trường hợp không đặc biệt nào tôi nên ném một ngoại lệ? Và nếu tôi không nên ném: khi nào trả về một giá trị ma thuật được xác định ở trên thực hiện một Try*phương thức với một outtham số ? (Đối với tôi, outtham số có vẻ bẩn và sử dụng đúng cách sẽ tốn nhiều công sức hơn.)

Tôi đang tìm kiếm câu trả lời thực tế, chẳng hạn như câu trả lời liên quan đến hướng dẫn thiết kế (tôi không biết gì về Try*phương pháp), tính khả dụng (khi tôi hỏi điều này đối với thư viện lớp), tính nhất quán với BCL và khả năng đọc.


Trong Thư viện lớp cơ sở .NET Framework, cả ba phương thức đều được sử dụng:

Lưu ý rằng như Hashtableđược tạo ra vào thời điểm khi không có tướng trong C #, nó sử dụng objectvà do đó có thể trở lại nullnhư một giá trị ma thuật. Nhưng với thuốc generic, các trường hợp ngoại lệ được sử dụng Dictionary<,>và ban đầu nó không có TryGetValue. Rõ ràng cái nhìn sâu sắc thay đổi.

Rõ ràng, Item- TryGetValueParse- TryParselưỡng tính là có một lý do, vì vậy tôi cho rằng ném ngoại lệ cho những thất bại không phải là đặc biệt là trong C # 4 không được thực hiện . Tuy nhiên, các Try*phương thức không phải lúc nào cũng tồn tại, ngay cả khi Dictionary<,>.Itemtồn tại.


6
"Ngoại lệ có nghĩa là lỗi". yêu cầu một từ điển cho một mục không tồn tại là một lỗi; yêu cầu một luồng để đọc dữ liệu khi EOF xảy ra mỗi khi bạn sử dụng một luồng. (điều này tóm tắt dài một câu trả lời được định dạng đẹp mắt tôi đã không nhận được một cơ hội để nộp :))
KutuluMike

6
Không phải tôi nghĩ rằng câu hỏi của bạn quá rộng, đó không phải là một câu hỏi mang tính xây dựng. Không có câu trả lời chính tắc như các câu trả lời đã được hiển thị.
George Stocker

2
@GeorgeStocker Nếu câu trả lời thẳng thắn và rõ ràng thì tôi sẽ không đặt câu hỏi. Thực tế là mọi người có thể tranh luận lý do tại sao một lựa chọn nhất định thích hợp hơn từ bất kỳ quan điểm nào (chẳng hạn như hiệu suất, khả năng đọc, khả năng sử dụng, khả năng bảo trì, hướng dẫn thiết kế hoặc tính nhất quán) làm cho nó không phù hợp với sự hài lòng của tôi. Tất cả các câu hỏi có thể được trả lời hơi chủ quan. Rõ ràng bạn mong đợi một câu hỏi có hầu hết các câu trả lời tương tự cho nó là một câu hỏi hay.
Daniel AA Pelsmaeker

3
@Virtlink: George là một người điều hành được cộng đồng bầu chọn, người tình nguyện dành rất nhiều thời gian của mình để giúp duy trì Stack Overflow. Anh ta nói lý do tại sao anh ta đóng câu hỏi, và Câu hỏi thường gặp lại.
Eric J.

15
Câu hỏi thuộc về đây, không phải vì nó chủ quan, mà vì nó mang tính khái niệm. Nguyên tắc nhỏ: nếu bạn đang ngồi trước mã hóa IDE của mình, hãy hỏi nó trên Stack Overflow. Nếu bạn đang đứng trước một bảng trắng động não, hãy hỏi nó ở đây trên Lập trình viên.
Robert Harvey

Câu trả lời:


56

Tôi không nghĩ rằng các ví dụ của bạn thực sự tương đương. Có ba nhóm riêng biệt, mỗi nhóm có lý do riêng cho hành vi của nó.

  1. Giá trị ma thuật là một lựa chọn tốt khi có một điều kiện "cho đến khi" như StreamReader.Readhoặc khi có một giá trị sử dụng đơn giản sẽ không bao giờ là câu trả lời hợp lệ (-1 cho IndexOf).
  2. Ném ngoại lệ khi ngữ nghĩa của hàm là người gọi chắc chắn rằng nó sẽ hoạt động. Trong trường hợp này, khóa không tồn tại hoặc định dạng kép xấu thực sự đặc biệt.
  3. Sử dụng một tham số out và trả về một bool nếu ngữ nghĩa là để thăm dò nếu hoạt động có thể hay không.

Các ví dụ bạn cung cấp hoàn toàn rõ ràng cho trường hợp 2 và 3. Đối với các giá trị ma thuật, có thể tranh luận nếu đây là một quyết định thiết kế tốt hay không trong mọi trường hợp.

Trả NaNvề bởi Math.Sqrtmột trường hợp đặc biệt - nó tuân theo tiêu chuẩn dấu phẩy động.


10
Không đồng ý với số 1. Giá trị ma thuật không bao giờ là một lựa chọn tốt vì chúng yêu cầu người viết mã tiếp theo phải biết tầm quan trọng của giá trị ma thuật. Họ cũng làm tổn thương khả năng đọc. Tôi có thể nghĩ rằng không có trường hợp nào sử dụng giá trị ma thuật tốt hơn mẫu Thử.
0b101010

1
Nhưng còn Either<TLeft, TRight>đơn nguyên thì sao?
sara

2
@ 0b101010: chỉ cần dành chút thời gian để tìm cách streamreader.read có thể quay trở lại -1 một cách an toàn ...
jmoreno

33

Bạn đang cố gắng truyền đạt cho người dùng API những gì họ phải làm. Nếu bạn ném một ngoại lệ, sẽ không có gì buộc họ phải nắm bắt nó và chỉ đọc tài liệu sẽ cho họ biết tất cả các khả năng là gì. Cá nhân tôi thấy chậm và tẻ nhạt khi đào sâu vào tài liệu để tìm tất cả các ngoại lệ mà một phương pháp nhất định có thể đưa ra (ngay cả khi nó trong intellisense, tôi vẫn phải sao chép chúng ra bằng tay).

Một giá trị ma thuật vẫn yêu cầu bạn đọc tài liệu và có thể tham khảo một số constbảng để giải mã giá trị. Ít nhất là nó không có chi phí ngoại lệ cho những gì bạn gọi là sự cố không đặc biệt.

Đó là lý do tại sao mặc dù outcác tham số đôi khi được tán thành, tôi thích phương thức đó, với Try...cú pháp. Đó là cú pháp .NET và C # chuẩn. Bạn đang liên lạc với người dùng API rằng họ phải kiểm tra giá trị trả về trước khi sử dụng kết quả. Bạn cũng có thể bao gồm một outtham số thứ hai với thông báo lỗi hữu ích, một lần nữa giúp gỡ lỗi. Đó là lý do tại sao tôi bỏ phiếu cho giải pháp Try...với outtham số.

Một lựa chọn khác là trả về một đối tượng "kết quả" đặc biệt, mặc dù tôi thấy điều này tẻ nhạt hơn nhiều:

interface IMyResult
{
    bool Success { get; }
    // Only access this if Success is true
    MyOtherClass Result { get; }
    // Only access this if Success is false
    string ErrorMessage { get; }
}

Sau đó, chức năng của bạn có vẻ đúng bởi vì nó chỉ có các tham số đầu vào và nó chỉ trả về một điều. Chỉ là một thứ mà nó trả lại là một loại tuple.

Trong thực tế, nếu bạn thích kiểu đó, bạn có thể sử dụng các Tuple<>lớp mới được giới thiệu trong .NET 4. Cá nhân tôi không thích thực tế là ý nghĩa của từng trường ít rõ ràng hơn vì tôi không thể đưa ra Item1Item2tên hữu ích.


3
Cá nhân, tôi thường sử dụng một thùng chứa kết quả như IMyResultnguyên nhân của bạn , có thể truyền đạt một kết quả phức tạp hơn chỉ truehoặc falsehoặc giá trị kết quả. Try*()chỉ hữu ích cho những thứ đơn giản như chuyển đổi chuỗi sang int.
02

1
Bài đăng tốt. Đối với tôi, tôi thích thành ngữ cấu trúc kết quả mà bạn đã nêu ở trên thay vì phân tách giữa các giá trị "return" và "out". Giữ nó gọn gàng và ngăn nắp.
Ocean Airdrop

2
Vấn đề với tham số out thứ hai là khi bạn có 50 hàm sâu trong một chương trình phức tạp, làm thế nào để bạn truyền thông báo lỗi đó lại cho người dùng? Ném một ngoại lệ đơn giản hơn nhiều so với các lớp kiểm tra lỗi. Khi bạn nhận được một chỉ cần ném nó và nó không quan trọng bạn sâu bao nhiêu.
cuộn

@rolls - Khi chúng tôi sử dụng các đối tượng đầu ra, chúng tôi cho rằng người gọi ngay lập tức sẽ làm bất cứ điều gì anh ta muốn với đầu ra: xử lý cục bộ, bỏ qua nó hoặc bong bóng lên bằng cách ném gói đó vào một ngoại lệ. Đó là từ tốt nhất trong cả hai từ - người gọi có thể thấy rõ tất cả các kết quả đầu ra có thể (với enums, v.v.), có thể quyết định làm gì với lỗi và không cần phải thử bắt từng cuộc gọi. Nếu bạn chủ yếu mong muốn xử lý kết quả ngay lập tức hoặc bỏ qua nó, việc trả lại các đối tượng sẽ dễ dàng hơn. Nếu bạn muốn ném tất cả các lỗi lên các lớp trên, ngoại lệ sẽ dễ dàng hơn.
Drizin

2
Đây chỉ là phát minh lại các ngoại lệ được kiểm tra trong C # theo cách tẻ nhạt hơn nhiều so với các ngoại lệ được kiểm tra trong Java.
Mùa đông

17

Như các ví dụ của bạn đã cho thấy, mỗi trường hợp như vậy phải được đánh giá riêng biệt và có một dải màu xám đáng kể giữa "hoàn cảnh đặc biệt" và "kiểm soát dòng chảy", đặc biệt nếu phương pháp của bạn dự định có thể sử dụng lại và có thể được sử dụng theo các mẫu khá khác nhau hơn nó được thiết kế ban đầu cho. Đừng hy vọng tất cả chúng ta ở đây đồng ý về ý nghĩa "không đặc biệt", đặc biệt nếu bạn thảo luận ngay về khả năng sử dụng "ngoại lệ" để thực hiện điều đó.

Chúng tôi cũng có thể không đồng ý về thiết kế nào giúp mã dễ đọc và duy trì nhất, nhưng tôi sẽ cho rằng người thiết kế thư viện có tầm nhìn cá nhân rõ ràng về điều đó và chỉ cần cân bằng nó với các cân nhắc khác có liên quan.

Câu trả lời ngắn

Theo dõi cảm xúc của bạn ngoại trừ khi bạn đang thiết kế các phương pháp khá nhanh và mong đợi khả năng tái sử dụng không lường trước được.

Câu trả lời dài

Mỗi người gọi trong tương lai có thể tự do dịch giữa các mã lỗi và ngoại lệ theo ý muốn theo cả hai hướng; điều này làm cho hai cách tiếp cận thiết kế gần như tương đương ngoại trừ hiệu năng, thân thiện với trình gỡ lỗi và một số bối cảnh tương tác bị hạn chế. Điều này thường làm giảm hiệu suất, vì vậy hãy tập trung vào đó.

  • Theo nguyên tắc thông thường, mong đợi rằng việc ném một ngoại lệ chậm hơn 200 lần so với lợi nhuận thông thường (trong thực tế, có một sự khác biệt đáng kể trong đó).

  • Như một quy tắc khác, việc ném một ngoại lệ thường có thể cho phép mã sạch hơn nhiều so với giá trị ma thuật thô sơ nhất, bởi vì bạn không dựa vào lập trình viên dịch mã lỗi sang mã lỗi khác, vì nó đi qua nhiều lớp mã khách hàng một điểm có đủ bối cảnh để xử lý nó một cách nhất quán và đầy đủ. (Trường hợp đặc biệt: nullcó xu hướng giá tốt hơn ở đây so với các giá trị ma thuật khác vì xu hướng tự động dịch sang NullReferenceExceptiontrường hợp của một số, nhưng không phải tất cả các loại khiếm khuyết; thông thường, nhưng không phải lúc nào cũng khá gần với nguồn gốc của khuyết điểm. )

Vậy bài học là gì?

Đối với một chức năng được gọi chỉ một vài lần trong suốt vòng đời của một ứng dụng (như khởi tạo ứng dụng), hãy sử dụng bất cứ thứ gì mang lại cho bạn mã sạch hơn, dễ hiểu hơn. Hiệu suất không thể là một mối quan tâm.

Đối với chức năng vứt bỏ, hãy sử dụng bất cứ thứ gì cung cấp cho bạn mã sạch hơn. Sau đó thực hiện một số hồ sơ (nếu cần thiết) và thay đổi ngoại lệ thành trả lại mã nếu chúng nằm trong số các tắc nghẽn hàng đầu bị nghi ngờ dựa trên các phép đo hoặc cấu trúc chương trình tổng thể.

Đối với một chức năng tái sử dụng đắt tiền, hãy sử dụng bất cứ thứ gì cung cấp cho bạn mã sạch hơn. Nếu về cơ bản bạn luôn phải trải qua một vòng mạng hoặc phân tích tệp XML trên đĩa, thì chi phí ném ngoại lệ có thể không đáng kể. Điều quan trọng hơn là không mất chi tiết về bất kỳ thất bại nào, thậm chí không vô tình, hơn là trở về từ một "thất bại không đặc biệt" cực nhanh.

Một chức năng tái sử dụng nạc đòi hỏi nhiều suy nghĩ. Bằng cách sử dụng các ngoại lệ, bạn đang buộc một số thứ như làm chậm 100 lần đối với người gọi, những người sẽ thấy ngoại lệ trên một nửa số cuộc gọi của họ, nếu cơ thể của hàm thực thi rất nhanh. Các ngoại lệ vẫn là một tùy chọn thiết kế, nhưng bạn sẽ phải cung cấp một giải pháp thay thế chi phí thấp cho những người gọi không đủ khả năng này. Hãy xem một ví dụ.

Bạn liệt kê một ví dụ tuyệt vời Dictionary<,>.Item, trong đó, nói một cách lỏng lẻo, đã thay đổi từ trả về nullgiá trị sang ném KeyNotFoundExceptiongiữa .NET 1.1 và .NET 2.0 (chỉ khi bạn sẵn sàng coi Hashtable.Itemlà tiền thân không chung chung thực tế của nó). Lý do của sự "thay đổi" này không phải là không có hứng thú ở đây. Tối ưu hóa hiệu suất của các loại giá trị (không có thêm quyền anh) làm cho giá trị ma thuật ban đầu ( null) không còn tùy chọn; outcác tham số sẽ chỉ mang lại một phần nhỏ của chi phí hiệu suất trở lại. Việc xem xét hiệu suất sau này là hoàn toàn không đáng kể so với chi phí ném KeyNotFoundException, nhưng thiết kế ngoại lệ vẫn vượt trội ở đây. Tại sao?

  • tham số ref / out phát sinh chi phí của họ mọi lúc, không chỉ trong trường hợp "thất bại"
  • Bất cứ ai quan tâm đều có thể gọi Containstrước bất kỳ cuộc gọi nào đến người lập chỉ mục và mẫu này đọc hoàn toàn tự nhiên. Nếu một nhà phát triển muốn nhưng quên cuộc gọi Contains, không có vấn đề về hiệu suất nào có thể xảy ra; KeyNotFoundExceptionlà đủ lớn để được chú ý và cố định.

Tôi nghĩ 200x đang lạc quan về sự hoàn hảo của các ngoại lệ ... xem blog.msdn.com/b/cbrumme/archive/2003/10/01/51524.aspx phần "Hiệu suất và Xu hướng" ngay trước khi nhận xét.
gbjbaanb

@gbjbaanb - Vâng, có thể. Bài viết đó sử dụng tỷ lệ thất bại 1% để thảo luận về chủ đề không phải là một sân bóng hoàn toàn khác. Suy nghĩ của riêng tôi và các phép đo được nhớ một cách mơ hồ sẽ xuất phát từ bối cảnh của C ++ được thực hiện theo bảng (xem Phần 5.4.1.2 của báo cáo này , trong đó một vấn đề là ngoại lệ đầu tiên của loại này có thể bắt đầu bằng lỗi trang (quyết liệt và biến đổi nhưng được khấu hao một lần trên đầu). Nhưng tôi sẽ làm và đăng một thử nghiệm với .NET 4 và có thể điều chỉnh giá trị sân bóng này. Tôi đã nhấn mạnh phương sai.
Jirka Hanika

Là chi phí cho các tham số ref / out sau đó cao ? Làm sao vậy Và gọi Containstrước một cuộc gọi đến một người lập chỉ mục có thể gây ra một điều kiện cuộc đua mà không phải có mặt TryGetValue.
Daniel AA Pelsmaeker

@gbjbaanb - Đã thử nghiệm xong. Tôi đã lười biếng và sử dụng mono trên Linux. Các ngoại lệ đã cho tôi ~ 563000 cú ném trong 3 giây. Trả về cho tôi ~ 10900000 trả lại trong 3 giây. Đó là 1:20, thậm chí 1: 200. Tôi vẫn khuyên bạn nên suy nghĩ 1: 100+ cho bất kỳ mã thực tế hơn. (trong param biến thể, như tôi đã dự đoán, có chi phí không đáng kể - Tôi thực sự nghi ngờ rằng các jitter lẽ đã được tối ưu hóa các cuộc gọi đi hoàn toàn trong ví dụ Minimalistic của tôi nếu không có ngoại lệ ném, không phụ thuộc vào chữ ký.)
Jirka Hanika

@Virtlink - Nói chung, đồng ý về an toàn luồng, nhưng cho rằng bạn đã nói đến .NET 4 nói riêng, sử dụng ConcurrentDictionaryđể truy cập đa luồng và Dictionarytruy cập luồng đơn để có hiệu suất tối ưu. Đó là, không sử dụng `` Chứa` KHÔNG làm cho chuỗi mã an toàn với Dictionarylớp cụ thể này .
Jirka Hanika

10

Điều tốt nhất để làm trong một tình huống tương đối không đặc biệt như vậy để chỉ ra thất bại, và tại sao?

Bạn không nên cho phép thất bại.

Tôi biết, đó là sóng tay và lý tưởng, nhưng hãy nghe tôi nói. Khi thực hiện thiết kế, có một số trường hợp bạn có cơ hội ủng hộ một phiên bản không có chế độ thất bại. Thay vì thất bại 'Find ALL', LINQ sử dụng mệnh đề where chỉ đơn giản trả về một số không có giá trị. Thay vì có một đối tượng cần được khởi tạo trước khi sử dụng, hãy để hàm tạo khởi tạo đối tượng (hoặc khởi tạo khi không phát hiện được khởi tạo). Điều quan trọng là loại bỏ nhánh thất bại trong mã người tiêu dùng. Đây là vấn đề, vì vậy hãy tập trung vào nó.

Một chiến lược khác cho điều này là KeyNotFoundsscenario. Trong hầu hết mọi cơ sở mã mà tôi đã làm việc kể từ 3.0, một cái gì đó giống như phương thức mở rộng này tồn tại:

public static class DictionaryExtensions {
    public static V GetValue<K, V>(this IDictionary<K, V> arg, K key, Func<K,V> ifNotFound) {
        if (!arg.ContainsKey(key)) {
            return ifNotFound(key);
        }

        return arg[key];
    }
}

Không có chế độ thất bại thực sự cho việc này. ConcurrentDictionarycó một tương tự GetOrAddđược xây dựng trong.

Tất cả những gì đã nói, sẽ luôn có những lúc đơn giản là không thể tránh khỏi. Cả ba đều có vị trí của họ, nhưng tôi sẽ ủng hộ lựa chọn đầu tiên. Bất chấp tất cả những gì được tạo ra từ sự nguy hiểm của null, nó nổi tiếng và phù hợp với rất nhiều trường hợp 'không tìm thấy' hoặc 'kết quả không thể áp dụng' tạo nên tập hợp "không phải là thất bại đặc biệt". Đặc biệt là khi bạn đang tạo các loại giá trị vô giá trị, tầm quan trọng của 'điều này có thể thất bại' rất rõ ràng trong mã và khó có thể quên / làm hỏng.

Tùy chọn thứ hai là đủ tốt khi người dùng của bạn làm điều gì đó ngớ ngẩn. Cung cấp cho bạn một chuỗi có định dạng sai, cố gắng đặt ngày thành ngày 42 tháng 12 ... một cái gì đó mà bạn muốn mọi thứ sẽ nổ tung nhanh chóng và ngoạn mục trong quá trình thử nghiệm để mã xấu được xác định và sửa chữa.

Lựa chọn cuối cùng là một cái tôi ngày càng không thích. Các tham số out rất khó xử và có xu hướng vi phạm một số thực tiễn tốt nhất khi thực hiện các phương pháp, như tập trung vào một thứ và không có tác dụng phụ. Thêm vào đó, thông số out thường chỉ có ý nghĩa trong quá trình thành công. Điều đó nói rằng, chúng rất cần thiết cho một số hoạt động nhất định thường bị hạn chế bởi các mối quan tâm đồng thời hoặc cân nhắc về hiệu suất (ví dụ bạn không muốn thực hiện chuyến đi thứ hai đến DB).

Nếu giá trị trả về và tham số out là không tầm thường, thì đề xuất của Scott Whitlock về một đối tượng kết quả được ưu tiên (như Matchlớp của Regex ).


7
Pet peeve ở đây: outcác tham số hoàn toàn trực giao với vấn đề tác dụng phụ. Sửa đổi một reftham số là một hiệu ứng phụ và sửa đổi trạng thái của một đối tượng mà bạn truyền qua một tham số đầu vào là một hiệu ứng phụ, nhưng một outtham số chỉ là một cách khó xử khi làm cho hàm trả về nhiều hơn một giá trị. Không có tác dụng phụ, chỉ có nhiều giá trị trả về.
Scott Whitlock

Tôi nói có xu hướng , vì cách mọi người sử dụng chúng. Giống như bạn nói, chúng chỉ đơn giản là nhiều giá trị trả về.
Telastyn

Nhưng nếu bạn không thích các tham số và sử dụng các ngoại lệ để làm nổ tung mọi thứ một cách ngoạn mục khi định dạng sai ... thì làm thế nào để bạn xử lý trường hợp định dạng của bạn là đầu vào của người dùng? Sau đó, một người dùng có thể làm nổ tung mọi thứ, hoặc một người phải chịu hình phạt hiệu suất của việc ném và sau đó bắt một ngoại lệ. Đúng?
Daniel AA Pelsmaeker

@virtlink bằng cách có một phương thức xác nhận riêng biệt. Bạn cần nó dù sao để cung cấp thông điệp thích hợp cho UI trước khi họ gửi nó.
Telastyn

1
Có một mẫu hợp pháp cho các tham số out, và đó là một hàm có quá tải trả về các loại khác nhau. Độ phân giải quá tải sẽ không hoạt động đối với các loại trả về, nhưng nó sẽ cho các tham số.
Robert Harvey

2

Luôn luôn thích ném một ngoại lệ. Nó có một giao diện thống nhất trong số tất cả các chức năng có thể bị lỗi và nó cho thấy sự thất bại càng nhiều càng tốt - một đặc tính rất đáng mong đợi.

Lưu ý rằng ParseTryParsekhông thực sự giống nhau ngoài các chế độ thất bại. Thực tế TryParsecũng có thể trả về giá trị là hơi trực giao, thực sự. Xem xét tình huống, ví dụ, bạn đang xác nhận một số đầu vào. Bạn không thực sự quan tâm giá trị là gì, miễn là nó hợp lệ. Và không có gì sai khi cung cấp một loại IsValidFor(arguments)chức năng. Nhưng nó không bao giờ có thể là chính phương thức hoạt động.


4
Nếu bạn đang xử lý một số lượng lớn các cuộc gọi đến một phương thức, các ngoại lệ có thể có tác động tiêu cực rất lớn đến hiệu suất. Các ngoại lệ nên được dành riêng cho các điều kiện đặc biệt và sẽ được chấp nhận hoàn toàn để xác thực nhập mẫu, nhưng không phải để phân tích số từ các tệp lớn.
Robert Harvey

1
Đó là một nhu cầu chuyên môn hơn, không phải là trường hợp chung.
DeadMG

2
Vì vậy, bạn nói. Nhưng bạn đã sử dụng từ "luôn luôn." :)
Robert Harvey

@DeadMG, đã đồng ý với RobertHarvey mặc dù tôi nghĩ rằng câu trả lời đã bị bỏ phiếu quá mức, nếu nó được sửa đổi để phản ánh "hầu hết thời gian" và sau đó chỉ ra các trường hợp ngoại lệ (không có ý định chơi chữ) cho trường hợp chung được sử dụng thường xuyên. các cuộc gọi để xem xét các lựa chọn khác.
Gerald Davis

Ngoại lệ không đắt tiền. Bắt các ngoại lệ ném sâu có thể tốn kém vì hệ thống phải giải phóng ngăn xếp đến điểm quan trọng gần nhất. Nhưng cái "đắt" đó tương đối nhỏ và không nên sợ ngay cả trong những cái vòng được lồng chặt.
Matthew Whited

2

Như những người khác đã lưu ý, giá trị ma thuật (bao gồm giá trị trả về boolean) không phải là một giải pháp tuyệt vời, ngoại trừ như một điểm đánh dấu "cuối phạm vi". Lý do: Các ngữ nghĩa không rõ ràng, ngay cả khi bạn kiểm tra các phương thức của đối tượng. Bạn phải thực sự đọc tài liệu đầy đủ cho toàn bộ đối tượng để "oh yeah, nếu nó trả về -42 có nghĩa là bla bla bla".

Giải pháp này có thể được sử dụng vì lý do lịch sử hoặc vì lý do hiệu suất, nhưng nếu không thì nên tránh.

Điều này để lại hai trường hợp chung: Thăm dò hoặc ngoại lệ.

Ở đây nguyên tắc chung là chương trình không nên phản ứng với các ngoại lệ ngoại trừ để xử lý khi chương trình / vô ý / vi phạm một số điều kiện. Probing nên được sử dụng để đảm bảo rằng điều này không xảy ra. Do đó, một ngoại lệ có nghĩa là việc thăm dò có liên quan đã không được thực hiện trước hoặc điều gì đó hoàn toàn bất ngờ đã xảy ra.

Thí dụ:

Bạn muốn tạo một tập tin từ một đường dẫn nhất định.

Bạn nên sử dụng đối tượng Tệp để đánh giá trước xem đường dẫn này có hợp pháp để tạo hoặc ghi tệp hay không.

Nếu chương trình của bạn bằng cách nào đó vẫn kết thúc cố gắng viết vào một con đường bất hợp pháp hoặc nếu không không thể ghi được, bạn sẽ nhận được một đoạn trích. Điều này có thể xảy ra do một điều kiện cuộc đua (một số người dùng khác đã xóa thư mục hoặc làm cho nó chỉ đọc, sau khi bạn gặp phải)

Nhiệm vụ xử lý một lỗi không mong muốn (được báo hiệu bởi một ngoại lệ) và kiểm tra xem các điều kiện phù hợp với hoạt động trước (thăm dò) thường sẽ được cấu trúc khác nhau, và do đó nên sử dụng các cơ chế khác nhau.


0

Tôi nghĩ rằng Trymô hình là sự lựa chọn tốt nhất, khi mã chỉ cho biết những gì đã xảy ra. Tôi ghét param và thích đối tượng nullable. Tôi đã tạo lớp sau

public sealed class Bag<TValue>
{
    public Bag(TValue value, bool hasValue = true)
    {
        HasValue = hasValue;
        Value = value;
    }

    public static Bag<TValue> Empty
    {
        get { return new Bag<TValue>(default(TValue), false); }
    }

    public bool HasValue { get; private set; }
    public TValue Value { get; private set; }
}

để tôi có thể viết mã sau

    public static Bag<XElement> GetXElement(this XElement element, string elementName)
    {
        try
        {
            XElement result = element.Element(elementName);
            return result == null
                       ? Bag<XElement>.Empty
                       : new Bag<XElement>(result);
        }
        catch (Exception)
        {
            return Bag<XElement>.Empty;
        }
    }

Trông giống như nullable nhưng không chỉ cho loại giá trị

Một vi dụ khac

    public static Bag<string> TryParseString(this XElement element, string attributeName)
    {
        Bag<string> attributeResult = GetString(element, attributeName);
        if (attributeResult.HasValue)
        {
            return new Bag<string>(attributeResult.Value);
        }
        return Bag<string>.Empty;
    }

    private static Bag<string> GetString(XElement element, string attributeName)
    {
        try
        {
            string result = element.GetAttribute(attributeName).Value;
            return new Bag<string>(result);
        }
        catch (Exception)
        {
            return Bag<string>.Empty;
        }
    }

3
try catchsẽ tàn phá hiệu suất của bạn nếu bạn gọi GetXElement()và thất bại nhiều lần.
Robert Harvey

đôi khi nó không thành vấn đề. Hãy xem cuộc gọi Bag. Cảm ơn sự quan sát của bạn

Túi <T> clas của bạn gần giống với System.Nullable <T> aka "nullable object"
aeroson

vâng, gần như public struct Nullable<T> where T : structsự khác biệt chính trong một ràng buộc. btw phiên bản mới nhất là ở đây github.com/Nelibur/Nelibur/blob/master/Source/Nelibur.Sword/...
GSerjo

0

Nếu bạn quan tâm đến con đường "giá trị ma thuật", thì một cách khác để giải quyết điều này là làm quá tải mục đích của lớp Lazy. Mặc dù Lazy có ý định trì hoãn việc khởi tạo, nhưng không có gì thực sự ngăn cản bạn sử dụng như Có thể hoặc Tùy chọn. Ví dụ:

    public static Lazy<TValue> GetValue<TValue, TKey>(
        this IDictionary<TKey, TValue> dictionary,
        TKey key)
    {
        TValue retVal;
        if (dictionary.TryGetValue(key, out retVal))
        {
            var retValRef = retVal;
            var lazy = new Lazy<TValue>(() => retValRef);
            retVal = lazy.Value;
            return lazy;
        }

        return new Lazy<TValue>(() => default(TValue));
    }
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.