Ngoại lệ so với kết quả trống được đặt khi đầu vào hợp lệ về mặt kỹ thuật, nhưng không thỏa mãn


108

Tôi đang phát triển một thư viện dành cho phát hành công khai. Nó chứa các phương thức khác nhau để vận hành trên các bộ đối tượng - tạo, kiểm tra, phân vùng và chiếu các bộ thành các biểu mẫu mới. Trong trường hợp có liên quan, đó là thư viện lớp C # có chứa các tiện ích mở rộng kiểu LINQ IEnumerable, sẽ được phát hành dưới dạng gói NuGet.

Một số phương thức trong thư viện này có thể được cung cấp các tham số đầu vào không thỏa mãn. Ví dụ, trong các phương thức tổ hợp, có một phương thức để tạo ra tất cả các bộ n mục có thể được xây dựng từ một tập hợp nguồn của các mục m . Ví dụ: đưa ra tập hợp:

1, 2, 3, 4, 5

và yêu cầu kết hợp 2 sẽ tạo ra:

1, 2
1, 3
1, 4
v.v ...
5, 3
5, 4

Bây giờ, rõ ràng có thể yêu cầu những thứ không thể thực hiện được, như đưa cho nó một bộ 3 vật phẩm và sau đó yêu cầu kết hợp 4 vật phẩm trong khi đặt tùy chọn cho biết nó chỉ có thể sử dụng mỗi vật phẩm một lần.

Trong trường hợp này, mỗi tham số là hợp lệ riêng :

  • Bộ sưu tập nguồn không phải là null và không chứa các mục
  • Kích thước được yêu cầu của các kết hợp là một số nguyên khác không dương
  • Chế độ được yêu cầu (chỉ sử dụng mỗi mục một lần) là một lựa chọn hợp lệ

Tuy nhiên, trạng thái của các tham số khi được thực hiện cùng nhau gây ra vấn đề.

Trong kịch bản này, bạn có muốn phương thức đưa ra một ngoại lệ (ví dụ InvalidOperationException:) hoặc trả về một bộ sưu tập trống không? Hoặc có vẻ hợp lệ với tôi:

  • Do đó, bạn không thể tạo kết hợp n mục từ một nhóm m trong đó n> m nếu bạn chỉ được phép sử dụng mỗi mục một lần, do đó thao tác này có thể được coi là không thể, do đó InvalidOperationException.
  • Tập hợp các kết hợp có kích thước n có thể được tạo từ các mục m khi n> m là một tập hợp trống; không có kết hợp có thể được sản xuất.

Đối số cho một tập hợp trống

Mối quan tâm đầu tiên của tôi là một ngoại lệ ngăn chặn các chuỗi phương thức theo kiểu LINQ khi bạn xử lý các bộ dữ liệu có thể có kích thước không xác định. Nói cách khác, bạn có thể muốn làm một cái gì đó như thế này:

var result = someInputSet
    .CombinationsOf(4, CombinationsGenerationMode.Distinct)
    .Select(combo => /* do some operation to a combination */)
    .ToList();

Nếu bộ đầu vào của bạn có kích thước thay đổi, hành vi của mã này là không thể đoán trước. Nếu .CombinationsOf()ném một ngoại lệ khi someInputSetcó ít hơn 4 phần tử, thì mã này đôi khi sẽ thất bại khi chạy mà không có một số kiểm tra trước. Trong ví dụ trên, việc kiểm tra này là không quan trọng, nhưng nếu bạn gọi nó là một nửa chuỗi LINQ dài hơn thì điều này có thể trở nên tẻ nhạt. Nếu nó trả về một tập hợp trống, thì nó resultsẽ trống, mà bạn có thể hoàn toàn hài lòng.

Đối số cho một ngoại lệ

Mối quan tâm thứ hai của tôi là việc trả lại một tập hợp trống có thể ẩn các vấn đề - nếu bạn đang gọi phương thức này ở giữa một chuỗi LINQ và nó lặng lẽ trả về một tập hợp trống, sau đó bạn có thể gặp phải các vấn đề sau đó hoặc thấy mình bị trống tập kết quả, và có thể không rõ ràng điều đó đã xảy ra như thế nào khi bạn chắc chắn có thứ gì đó trong bộ đầu vào.

Bạn mong đợi điều gì, và lý lẽ của bạn cho nó là gì?


66
Vì tập hợp trống là chính xác về mặt toán học, có thể là khi bạn nhận được nó, nó thực sự là những gì bạn muốn. Các định nghĩa và quy ước toán học thường được chọn vì tính nhất quán và thuận tiện để mọi thứ chỉ phù hợp với chúng.
asmeker

5
@asmeker Họ được chọn sao cho các định lý phù hợp và thuận tiện. Họ không được chọn để làm cho việc lập trình dễ dàng hơn. (Điều đó đôi khi cũng là một lợi ích phụ, nhưng đôi khi chúng cũng khiến việc lập trình trở nên khó khăn hơn.)
jpmc26

7
@ jpmc26 "Chúng được chọn sao cho các định lý phù hợp và thuận tiện" - đảm bảo rằng chương trình của bạn luôn hoạt động như mong đợi về cơ bản tương đương với việc chứng minh một định lý.
artem

2
@ jpmc26 Tôi không hiểu tại sao bạn đề cập đến lập trình chức năng. Chứng minh tính đúng đắn cho các chương trình bắt buộc là hoàn toàn có thể, luôn luôn thuận lợi và cũng có thể được thực hiện một cách không chính thức - chỉ cần suy nghĩ một chút và sử dụng ý nghĩa toán học thông thường khi bạn viết chương trình của mình và bạn sẽ mất ít thời gian kiểm tra hơn. Được thống kê trên mẫu của một ;-)
artem

5
@DmitryGrigoryev 1/0 dưới dạng không xác định thì đúng hơn về mặt toán học so với 1/0 là vô cùng.
asmeker

Câu trả lời:


145

Trả lại một tập hợp trống

Tôi sẽ mong đợi một bộ trống vì:

Có 0 kết hợp 4 số từ bộ 3 khi tôi chỉ có thể sử dụng mỗi số một lần


5
Về mặt toán học, vâng, nhưng nó cũng rất có thể là một nguồn gây ra lỗi. Nếu loại đầu vào này được mong đợi, có thể tốt hơn để yêu cầu người dùng bắt ngoại lệ trong thư viện mục đích chung.
Casey Kuball

56
Tôi không đồng ý rằng đó là nguyên nhân "rất có thể" gây ra lỗi. Giả sử, ví dụ, bạn đang thực hiện thuật toán "matchmaknig" ngây thơ từ một bộ đầu vào lớn. Bạn có thể yêu cầu tất cả các kết hợp của hai mục, tìm một kết hợp "tốt nhất" trong số chúng, sau đó loại bỏ cả hai yếu tố và bắt đầu lại với bộ mới, nhỏ hơn. Cuối cùng, tập hợp của bạn sẽ trống và sẽ không còn cặp nào để xem xét: một ngoại lệ tại thời điểm này là tắc nghẽn. Tôi nghĩ rằng có rất nhiều cách để kết thúc trong một tình huống tương tự.
amalloy

11
Thực tế bạn không biết đây có phải là lỗi trong mắt người dùng hay không. Bộ trống là thứ mà người dùng nên kiểm tra, nếu cần. if (result.Any ()) DoS Something (result.First ()); other DoS SomethingElse (); tốt hơn nhiều so với thử {result.first (). dos somebody ();} bắt {DoS SomethingElse ();}
Guran

4
@TripeHound Cuối cùng, đó là điều đã đưa ra quyết định: yêu cầu nhà phát triển sử dụng phương pháp này để kiểm tra, sau đó ném, có tác động nhỏ hơn nhiều so với ném một ngoại lệ mà họ không muốn, về nỗ lực phát triển, hiệu suất chương trình và sự đơn giản của dòng chương trình.
anaximander

6
@Darthfett Hãy thử so sánh điều này với một phương thức mở rộng LINQ hiện có: Where (). Nếu tôi có một mệnh đề: Trong đó (x => 1 == 2) Tôi không có ngoại lệ, tôi nhận được một tập hợp trống.
Necoras

79

Khi nghi ngờ, hãy hỏi người khác.

Hàm ví dụ của bạn có một hàm rất giống trong Python : itertools.combinations. Hãy xem cách nó hoạt động:

>>> import itertools
>>> input = [1, 2, 3, 4, 5]
>>> list(itertools.combinations(input, 2))
[(1, 2), (1, 3), (1, 4), (1, 5), (2, 3), (2, 4), (2, 5), (3, 4), (3, 5), (4, 5)]
>>> list(itertools.combinations(input, 5))
[(1, 2, 3, 4, 5)]
>>> list(itertools.combinations(input, 6))
[]

Và nó cảm thấy hoàn toàn tốt với tôi. Tôi đã mong đợi một kết quả mà tôi có thể lặp đi lặp lại và tôi đã có một kết quả.

Nhưng, rõ ràng, nếu bạn định hỏi điều gì đó ngu ngốc:

>>> list(itertools.combinations(input, -1))
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
ValueError: r must be non-negative

Vì vậy, tôi muốn nói, nếu tất cả các tham số của bạn xác thực nhưng kết quả là một tập hợp trống trả về một tập hợp trống, bạn không phải là người duy nhất làm điều đó.


Như @Bakuriu đã nói trong các bình luận, điều này cũng tương tự đối với một SQLtruy vấn như thế nào SELECT <columns> FROM <table> WHERE <conditions>. Chừng nào <columns>, <table>, <conditions>cũng được lập được hình thành và tham khảo tên hiện tại, bạn có thể xây dựng một tập hợp các điều kiện loại trừ lẫn nhau. Truy vấn kết quả sẽ không mang lại hàng nào thay vì ném một InvalidConditionsError.


4
Bị bỏ qua vì nó không phải là thành ngữ trong không gian ngôn ngữ C # / Linq (xem câu trả lời của người đánh dấu để biết cách xử lý các vấn đề giới hạn tương tự trong ngôn ngữ). Nếu đây là một câu hỏi Python thì đó sẽ là +1 từ tôi.
James Snell

32
@JamesSnell Tôi hầu như không thấy nó liên quan đến vấn đề ngoài luồng như thế nào. Chúng tôi không chọn các yếu tố theo chỉ mục để sắp xếp lại chúng, chúng tôi sẽ chọn ncác yếu tố trong bộ sưu tập để đếm số cách chúng tôi có thể chọn chúng. Nếu tại một thời điểm nào đó không còn phần tử nào trong khi chọn chúng, thì không có cách nào (0) chọn nphần tử từ bộ sưu tập đã nói.
Mathias Ettinger

1
Tuy nhiên, Python không phải là một lời tiên tri tốt cho các câu hỏi C #: ví dụ C # sẽ cho phép 1.0 / 0, Python sẽ không.
Dmitry Grigoryev

2
@MathiasEttinger Các hướng dẫn của Python về cách thức và thời điểm sử dụng các ngoại lệ rất khác với C #, vì vậy sử dụng hành vi từ các mô-đun Python làm hướng dẫn không phải là một số liệu tốt cho C #.
Ant P

2
Thay vì chọn Python, chúng ta chỉ có thể chọn SQL. Có một truy vấn được tạo tốt SELECT trên một bảng tạo ra một ngoại lệ khi tập kết quả trống không? (spoiler: không!). "Sự hình thành tốt" của một truy vấn chỉ phụ thuộc vào chính truy vấn đó và một số siêu dữ liệu (ví dụ: bảng có tồn tại và có trường này (với loại này) không?) Một khi truy vấn được định dạng tốt và bạn thực hiện nó (có thể trống) kết quả hợp lệ hoặc một ngoại lệ vì "máy chủ" có vấn đề (ví dụ: máy chủ db bị hỏng hoặc một cái gì đó).
Bakuriu

72

Trong điều khoản của Giáo dân:

  • Nếu có lỗi, bạn nên đưa ra một ngoại lệ. Điều đó có thể liên quan đến việc thực hiện mọi thứ theo các bước thay vì trong một cuộc gọi bị xiềng xích để biết chính xác lỗi xảy ra ở đâu.
  • Nếu không có lỗi nhưng tập kết quả trống, đừng đưa ra ngoại lệ, trả lại tập trống. Một bộ trống là một bộ hợp lệ.

23
+1, 0 là một con số hoàn toàn hợp lệ và thực sự là một con số cực kỳ quan trọng. Có rất nhiều trường hợp một truy vấn có thể trả về số lần truy cập một cách hợp pháp mà việc đưa ra một ngoại lệ sẽ rất khó chịu.
Kilian Foth

19
Lời khuyên tốt, nhưng câu trả lời của bạn có rõ ràng không?
Ewan

54
Tôi vẫn không khôn ngoan hơn nếu câu trả lời này gợi ý OP nên throwhoặc trả lại một bộ trống ...
James Snell

7
Câu trả lời của bạn nói rằng tôi có thể làm được, tùy thuộc vào việc có lỗi hay không, nhưng bạn không đề cập đến việc bạn có coi kịch bản ví dụ là lỗi hay không. Trong phần bình luận ở trên, bạn nói rằng tôi nên trả về một tập hợp trống "nếu không có vấn đề gì", nhưng một lần nữa, các điều kiện không thỏa mãn có thể được coi là một vấn đề, và bạn tiếp tục nói rằng tôi không nêu ra ngoại lệ khi tôi nên. Vậy tôi nên làm gì?
anaximander

3
Ah, tôi hiểu rồi - bạn có thể đã hiểu lầm; Tôi không xâu chuỗi bất cứ điều gì, tôi đang viết một phương pháp mà tôi dự kiến ​​sẽ được sử dụng trong một chuỗi. Tôi đang tự hỏi liệu nhà phát triển tương lai giả định sử dụng phương pháp này có muốn nó ném vào kịch bản trên không.
anaximander

53

Tôi đồng ý với câu trả lời của Ewan nhưng muốn thêm một lý do cụ thể.

Bạn đang xử lý các hoạt động toán học, vì vậy nó có thể là một lời khuyên tốt để gắn bó với các định nghĩa toán học tương tự. Từ quan điểm toán học, số lượng r -sets của n -set (tức là nCr ) được xác định rõ cho tất cả r> n> = 0. Nó bằng không. Do đó, trả về một tập hợp trống sẽ là trường hợp dự kiến ​​từ quan điểm toán học.


9
đây là một quan điểm tốt. Nếu thư viện ở cấp độ cao hơn, như chọn kết hợp các màu để tạo bảng màu - thì việc ném lỗi sẽ có ý nghĩa. Bởi vì bạn biết một bảng màu không có màu không phải là bảng màu. Nhưng một tập hợp không có mục nào vẫn là một tập hợp và toán học định nghĩa nó là một tập hợp trống.
Thuyền trưởng Man

1
Tốt hơn nhiều so với câu trả lời của Ewans vì nó trích dẫn thực hành toán học. +1
dùng949300

24

Tôi tìm thấy một cách tốt để xác định có nên sử dụng ngoại lệ hay không, là tưởng tượng mọi người tham gia vào giao dịch.

Lấy nội dung của tệp làm ví dụ:

  1. Vui lòng tải cho tôi nội dung của tệp, "không tồn tại"

    a. "Đây là nội dung: một bộ sưu tập các ký tự trống"

    b. "Erm, có một vấn đề, tập tin đó không tồn tại. Tôi không biết phải làm gì!"

  2. Vui lòng tìm nạp cho tôi nội dung của tệp, "tồn tại nhưng là void.txt"

    a. "Đây là nội dung: một bộ sưu tập các ký tự trống"

    b. "Erm, có một vấn đề, không có gì trong tập tin này. Tôi không biết phải làm gì!"

Chắc chắn một số người sẽ không đồng ý, nhưng với hầu hết dân gian, "Erm, có một vấn đề" có ý nghĩa khi tệp không tồn tại và trả về "một bộ sưu tập ký tự trống" khi tệp trống.

Vì vậy, áp dụng cách tiếp cận tương tự với ví dụ của bạn:

  1. Xin vui lòng cho tôi tất cả sự kết hợp của 4 mục cho {1, 2, 3}

    a. Không có, đây là một bộ trống.

    b. Có một vấn đề, tôi không biết phải làm gì.

Một lần nữa, "Có một vấn đề" sẽ có ý nghĩa nếu ví dụ nullđược cung cấp dưới dạng tập hợp các mục, nhưng "đây là một tập hợp trống" có vẻ như là một phản ứng hợp lý cho yêu cầu trên.

Nếu trả về giá trị trống sẽ che giấu một vấn đề (ví dụ: tệp bị thiếu, a null), thì thường nên sử dụng ngoại lệ (trừ khi ngôn ngữ bạn chọn hỗ trợ option/maybecác loại, đôi khi chúng có ý nghĩa hơn). Mặt khác, trả về một giá trị trống sẽ có khả năng đơn giản hóa chi phí và tuân thủ tốt hơn với nguyên tắc ít gây ngạc nhiên nhất.


4
Lời khuyên này là tốt, nhưng không áp dụng cho trường hợp này. Một ví dụ tốt hơn: Có bao nhiêu ngày sau ngày 30 tháng 1 ?: 1 Bao nhiêu ngày sau ngày 30 tháng hai ?: 0 Bao nhiêu ngày sau ngày 30: ngày tận thế?: Ngoại lệ Có bao nhiêu giờ làm việc trong ngày 30 : th của tháng hai: Ngoại lệ
Guran

9

Vì nó là một thư viện cho mục đích chung, bản năng của tôi sẽ là cho phép người dùng cuối chọn.

Giống như chúng ta có Parse()TryParse()có sẵn cho chúng ta, chúng ta có thể có tùy chọn mà chúng ta sử dụng tùy thuộc vào đầu ra mà chúng ta cần từ hàm. Bạn sẽ dành ít thời gian hơn để viết và duy trì trình bao bọc hàm để ném ngoại lệ hơn là tranh cãi về việc chọn một phiên bản duy nhất của hàm.


3
+1 vì tôi luôn thích ý tưởng lựa chọn và vì mẫu này có xu hướng khuyến khích các lập trình viên xác nhận đầu vào của chính họ. Sự tồn tại của chức năng TryFoo cho thấy rõ ràng rằng các kết hợp đầu vào nhất định có thể gây ra sự cố và nếu tài liệu giải thích những vấn đề tiềm ẩn đó là gì, lập trình viên có thể kiểm tra chúng và xử lý các đầu vào không hợp lệ theo cách sạch hơn bằng cách chỉ cần bắt một ngoại lệ hoặc trả lời một bộ trống. Hoặc họ có thể lười biếng. Dù bằng cách nào, quyết định là của họ.
aleppke

19
Không, một tập hợp trống là câu trả lời đúng về mặt toán học. Làm một cái gì đó khác là xác định lại toán học theo thỏa thuận chắc chắn, điều không nên làm một cách bất chợt. Trong khi chúng ta đang ở đó, chúng ta sẽ xác định lại hàm lũy thừa để đưa ra lỗi nếu số mũ bằng 0, bởi vì chúng ta quyết định chúng ta không thích quy ước rằng bất kỳ số nào được tăng lên với công suất "zeroth" đều bằng 1?
tự đại diện

1
Tôi đã định trả lời một cái gì đó tương tự. Các phương pháp mở rộng Linq Single()SingleOrDefault()ngay lập tức nảy ra trong tâm trí. Single ném một ngoại lệ nếu không có hoặc 1 kết quả, trong khi SingleOrDefault sẽ không ném, và thay vào đó trả về default(T). Có lẽ OP có thể sử dụng CombinationsOf()CombinationsOfOrThrow().
RubberDuck

1
@Wildcard - bạn đang nói về hai kết quả khác nhau cho cùng một đầu vào không giống nhau. OP chỉ quan tâm đến việc chọn a) kết quả hoặc b) đưa ra một ngoại lệ không phải là kết quả.
James Snell

4
SingleSingleOrDefault(hoặc FirstFirstOrDefault) là một câu chuyện hoàn toàn khác, @RubberDuck. Lấy ví dụ của tôi từ nhận xét trước đây của tôi. Nó hoàn toàn tốt đối với người trả lời không có câu hỏi nào "Số nguyên tố nào lớn hơn hai tồn tại?" , vì đây là một câu trả lời hợp lý và hợp lý. Dù sao, nếu bạn đang hỏi "số nguyên tố đầu tiên thậm chí lớn hơn hai là gì?" không có câu trả lời tự nhiên Bạn chỉ không thể trả lời câu hỏi, bởi vì bạn đang yêu cầu một số duy nhất. Nó không phải là không (không phải là một số duy nhất, mà là một tập hợp), không phải 0. Do đó, chúng tôi đưa ra một ngoại lệ.
Paul Kertscher

4

Bạn cần xác thực các đối số được cung cấp khi hàm của bạn được gọi. Và như một vấn đề thực tế, bạn muốn biết cách xử lý các đối số không hợp lệ. Thực tế là nhiều đối số phụ thuộc lẫn nhau, không bù đắp cho thực tế là bạn xác thực các đối số.

Do đó, tôi sẽ bỏ phiếu cho ArgumentException cung cấp thông tin cần thiết cho người dùng để hiểu những gì đã sai.

Ví dụ, kiểm tra public static TSource ElementAt<TSource>(this IEnumerable<TSource>, Int32)chức năng trong Linq. Việc ném một ArgumentOutOfRangeException nếu chỉ số nhỏ hơn 0 hoặc lớn hơn hoặc bằng số lượng phần tử trong nguồn. Do đó, chỉ số được xác nhận liên quan đến số lượng được cung cấp bởi người gọi.


3
+1 để trích dẫn một ví dụ về tình huống tương tự trong ngôn ngữ.
James Snell

13
Điều kỳ lạ là, ví dụ của bạn khiến tôi suy nghĩ, và dẫn tôi đến một điều mà tôi nghĩ là một ví dụ phản biện mạnh mẽ. Như tôi đã lưu ý, phương pháp này được thiết kế giống như LINQ, để người dùng có thể xâu chuỗi nó cùng với các phương thức LINQ khác. Nếu bạn new[] { 1, 2, 3 }.Skip(4).ToList();nhận được một bộ trống, điều này khiến tôi nghĩ rằng trả lại một bộ trống có lẽ là lựa chọn tốt hơn ở đây.
anaximander

14
Đây không phải là một vấn đề ngữ nghĩa ngôn ngữ, ngữ nghĩa của miền vấn đề đã cung cấp câu trả lời chính xác, đó là trả về tập hợp trống. Làm bất cứ điều gì khác vi phạm nguyên tắc ít gây ngạc nhiên nhất cho một người làm việc trong lĩnh vực vấn đề tổ hợp.
Ukko

1
Tôi đang bắt đầu hiểu các đối số để trả về một tập hợp trống. Tại sao nó sẽ có ý nghĩa. Ít nhất là cho ví dụ cụ thể này.
bắt đầu

2
@sbecker, để mang điểm về nhà: Nếu câu hỏi là "Có bao nhiêu kết hợp ..." thì "không" sẽ là một câu trả lời hoàn toàn hợp lệ. Tương tự như vậy, "Những gì sự kết hợp như vậy ..." có tập rỗng như một câu trả lời hoàn toàn hợp lệ. Nếu câu hỏi là " Sự kết hợp đầu tiên sao cho ...", thì và chỉ sau đó, một ngoại lệ sẽ phù hợp, vì câu hỏi không thể trả lời được. Xem thêm bình luận Paul K .
tự đại diện

3

Bạn nên thực hiện một trong những điều sau đây (mặc dù tiếp tục liên tục đưa ra các vấn đề cơ bản như số lượng kết hợp âm):

  1. Cung cấp hai triển khai, một triển khai trả về một tập hợp trống khi các đầu vào cùng nhau là vô nghĩa và một thực hiện ném. Hãy thử gọi cho họ CombinationsOfCombinationsOfWithInputCheck. Hoặc bất cứ điều gì bạn thích. Bạn có thể đảo ngược điều này để cái kiểm tra đầu vào là tên ngắn hơn và danh sách là CombinationsOfAllowInconsistentParameters.

  2. Đối với các phương thức Linq, trả lại khoảng trống IEnumerabletrên tiền đề chính xác mà bạn đã vạch ra. Sau đó, thêm các phương thức Linq này vào thư viện của bạn:

    public static class EnumerableExtensions {
       public static IEnumerable<T> ThrowIfEmpty<T>(this IEnumerable<T> input) {
          return input.IfEmpty<T>(() => {
             throw new InvalidOperationException("An enumerable was unexpectedly empty");
          });
       }
    
       public static IEnumerable<T> IfEmpty<T>(
          this IEnumerable<T> input,
          Action callbackIfEmpty
       ) {
          var enumerator = input.GetEnumerator();
          if (!enumerator.MoveNext()) {
             // Safe because if this throws, we'll never run the return statement below
             callbackIfEmpty();
          }
          return EnumeratePrimedEnumerator(enumerator);
       }
    
       private static IEnumerable<T> EnumeratePrimedEnumerator<T>(
          IEnumerator<T> primedEnumerator
       ) {
          yield return primedEnumerator.Current;
          while (primedEnumerator.MoveNext()) {
             yield return primedEnumerator.Current;
          }
       }
    }

    Cuối cùng, sử dụng như vậy:

    var result = someInputSet
       .CombinationsOf(4, CombinationsGenerationMode.Distinct)
       .ThrowIfEmpty()
       .Select(combo => /* do some operation to a combination */)
       .ToList();

    hoặc như thế này:

    var result = someInputSet
       .CombinationsOf(4, CombinationsGenerationMode.Distinct)
       .IfEmpty(() => _log.Warning(
          $@"Unexpectedly received no results when creating combinations for {
             nameof(someInputSet)}"
       ))
       .Select(combo => /* do some operation to a combination */)
       .ToList();

    Xin lưu ý rằng phương thức riêng khác với phương thức chung là bắt buộc đối với hành vi ném hoặc hành động xảy ra khi chuỗi linq được tạo thay vì một thời gian sau khi được liệt kê. Bạn muốn nó ném ngay lập tức.

    Tuy nhiên, lưu ý rằng tất nhiên nó phải liệt kê ít nhất là mục đầu tiên để xác định xem có mục nào không. Đây là một nhược điểm tiềm năng mà tôi nghĩ là chủ yếu được giảm nhẹ bởi thực tế là bất kỳ người xem tương lai có thể khá dễ dàng suy luận rằng một ThrowIfEmptyphương pháp liệt kê ít nhất một mục, vì vậy nên không ngạc nhiên bởi nó làm như vậy. Nhưng bạn không bao giờ biết. Bạn có thể làm điều này rõ ràng hơn ThrowIfEmptyByEnumeratingAndReEmittingFirstItem. Nhưng điều đó có vẻ như quá mức cần thiết.

Tôi nghĩ # 2 là khá, tốt, tuyệt vời! Bây giờ sức mạnh nằm trong mã gọi và người đọc mã tiếp theo sẽ hiểu chính xác những gì nó đang làm và sẽ không phải đối phó với các ngoại lệ không mong muốn.


Hãy giải thích những gì bạn có ý nghĩa. Làm thế nào là một cái gì đó trong bài viết của tôi "không hành xử như một bộ" và tại sao đó là một điều xấu?
ErikE

Về cơ bản, bạn hoàn toàn làm theo câu trả lời của tôi, thay vì gói truy vấn mà bạn muốn truy vấn Nếu có điều kiện, kết quả không thay đổi uu
GameDeveloper

Tôi không thể thấy câu trả lời của tôi là "về cơ bản giống như" câu trả lời của bạn. Họ dường như hoàn toàn khác nhau, với tôi.
ErikE

Về cơ bản, bạn chỉ đang thực thi cùng một điều kiện của "lớp xấu của tôi". Nhưng bạn không nói điều đó ^^. Bạn có đồng ý không, bạn đang thực thi sự tồn tại của 1 yếu tố trong kết quả truy vấn?
Nhà phát triển GameD

Bạn sẽ phải nói rõ hơn cho những lập trình viên ngu ngốc rõ ràng vẫn không hiểu những gì bạn đang nói. Lớp nào xấu? Tại sao nó xấu? Tôi không thi hành bất cứ điều gì. Tôi đang cho phép NGƯỜI DÙNG của lớp quyết định hành vi cuối cùng thay vì TẠO RA của lớp. Điều này cho phép một người sử dụng mô hình mã hóa nhất quán trong đó các phương thức Linq không ném ngẫu nhiên vì khía cạnh tính toán không thực sự vi phạm các quy tắc thông thường như nếu bạn yêu cầu -1 mục mỗi lần.
ErikE

2

Tôi có thể thấy các đối số cho cả hai trường hợp sử dụng - một ngoại lệ là tuyệt vời nếu mã xuôi dòng mong đợi các tập hợp chứa dữ liệu. Mặt khác, chỉ cần một bộ trống là tuyệt vời nếu điều này được mong đợi.

Tôi nghĩ rằng nó phụ thuộc vào sự mong đợi của người gọi nếu đây là lỗi hoặc kết quả chấp nhận được - vì vậy tôi sẽ chuyển sự lựa chọn cho người gọi. Có thể giới thiệu một lựa chọn?

.CombinationsOf(4, CombinationsGenerationMode.Distinct, Options.AllowEmptySets)


10
Trong khi tôi đến nơi bạn đang đi với điều này, tôi cố gắng tránh các phương thức có nhiều tùy chọn được thông qua để sửa đổi hành vi của họ. Tôi vẫn chưa hài lòng 100% với chế độ enum đã có; Tôi thực sự không thích ý tưởng về một tham số tùy chọn thay đổi hành vi ngoại lệ của phương thức. Tôi cảm thấy nếu một phương thức cần ném một ngoại lệ, thì nó cần phải ném; nếu bạn muốn ẩn ngoại lệ đó là quyết định của bạn dưới dạng mã gọi và không phải là điều bạn có thể làm cho mã của tôi thực hiện với tùy chọn nhập đúng.
anaximander

Nếu người gọi mong đợi một bộ không trống, thì người gọi sẽ xử lý một bộ trống hoặc ném ngoại lệ. Theo như callee có liên quan, một bộ trống là một câu trả lời hoàn toàn tốt. Và bạn có thể có nhiều hơn một người gọi, với các ý kiến ​​khác nhau về việc các bộ trống có tốt hay không.
gnasher729

2

Có hai cách tiếp cận để quyết định nếu không có câu trả lời rõ ràng:

  • Viết mã giả sử một tùy chọn đầu tiên, sau đó khác. Xem xét cái nào sẽ làm việc tốt nhất trong thực tế.

  • Thêm một tham số boolean "nghiêm ngặt" để cho biết bạn có muốn các tham số được xác minh nghiêm ngặt hay không. Ví dụ: Java SimpleDateFormatcó một setLenientphương pháp để phân tích cú pháp các đầu vào không khớp hoàn toàn với định dạng. Tất nhiên, bạn phải quyết định mặc định là gì.


2

Dựa trên phân tích của riêng bạn, việc trả lại tập hợp trống dường như rõ ràng đúng - bạn thậm chí đã xác định đó là điều mà một số người dùng thực sự muốn và không rơi vào cái bẫy cấm sử dụng vì bạn không thể tưởng tượng người dùng muốn sử dụng nó theo cách đó

Nếu bạn thực sự cảm thấy rằng một số người dùng có thể muốn buộc trả lại tiền không phải trả tiền, thì hãy cho họ một cách để yêu cầu hành vi đó thay vì ép buộc mọi người. Ví dụ: bạn có thể:

  • Làm cho nó trở thành một tùy chọn cấu hình trên bất kỳ đối tượng nào đang thực hiện hành động cho người dùng.
  • Làm cho nó trở thành một cờ người dùng có thể tùy ý chuyển vào chức năng.
  • Cung cấp một AssertNonemptykiểm tra họ có thể đưa vào chuỗi của họ.
  • Thực hiện hai chức năng, một chức năng khẳng định không trống và một chức năng không.

điều này dường như không cung cấp bất cứ điều gì đáng kể qua các điểm được thực hiện và giải thích trong 12 câu trả lời trước
gnat

1

Nó thực sự phụ thuộc vào những gì người dùng của bạn mong đợi nhận được. Đối với ví dụ (hơi không liên quan) nếu mã của bạn thực hiện phép chia, bạn có thể ném ngoại lệ hoặc trả về Infhoặc NaNkhi bạn chia cho số không. Không có đúng hay sai, tuy nhiên:

  • nếu bạn quay lại Infthư viện Python, mọi người sẽ tấn công bạn để che giấu lỗi
  • nếu bạn gây ra lỗi trong thư viện Matlab, mọi người sẽ tấn công bạn vì không xử lý dữ liệu bị thiếu

Trong trường hợp của bạn, tôi sẽ chọn giải pháp ít gây ngạc nhiên nhất cho người dùng cuối. Vì bạn đang phát triển thư viện xử lý các bộ, một bộ trống có vẻ như là thứ mà người dùng của bạn sẽ xử lý, do đó, việc trả lại nó nghe có vẻ là một việc hợp lý. Nhưng tôi có thể nhầm: bạn hiểu rõ hơn về bối cảnh hơn bất kỳ ai khác ở đây, vì vậy nếu bạn mong muốn người dùng của mình dựa vào bộ luôn không trống, bạn nên ném ngoại lệ ngay lập tức.

Các giải pháp cho phép người dùng chọn (như thêm tham số "nghiêm ngặt") không dứt khoát, vì chúng thay thế câu hỏi ban đầu bằng một câu hỏi tương đương mới: "Giá trị nào strictnên được mặc định?"


2
Tôi đã nghĩ về việc sử dụng đối số NaN trong câu trả lời của mình và tôi chia sẻ ý kiến ​​của bạn. Chúng tôi không thể nói nhiều hơn mà không biết tên miền của OP
GameDeveloper

0

Một quan niệm phổ biến (trong toán học) là khi bạn chọn các phần tử trên một tập hợp, bạn không thể tìm thấy phần tử nào và do đó bạn có được một tập hợp trống . Tất nhiên bạn phải phù hợp với toán học nếu bạn đi theo cách này:

Quy tắc thiết lập chung:

  • Set.Foreach (vị ngữ); // luôn trả về true cho các tập hợp trống
  • Set.Exists (vị ngữ); // luôn trả về false cho các tập hợp trống

Câu hỏi của bạn rất tinh tế:

  • Có thể là đầu vào của chức năng của bạn phải tôn trọng hợp đồng : Trong trường hợp đó, bất kỳ đầu vào không hợp lệ nào cũng sẽ đưa ra một ngoại lệ, đó là chức năng không hoạt động theo các tham số thông thường.

  • Có thể là đầu vào của hàm của bạn phải hoạt động chính xác như một tập hợp , và do đó sẽ có thể trả về một tập hợp trống.

Bây giờ nếu tôi ở trong bạn, tôi sẽ đi theo con đường "Đặt", nhưng với một "NHƯNG" lớn.

Giả sử bạn có một bộ sưu tập "do thôi miên" chỉ nên có sinh viên nữ:

class FemaleClass{

    FemaleStudent GetAnyFemale(){
        var femaleSet= mySet.Select( x=> x.IsFemale());
        if(femaleSet.IsEmpty())
            throw new Exception("No female students");
        else
            return femaleSet.Any();
    }
}

Bây giờ bộ sưu tập của bạn không còn là "bộ thuần túy", bởi vì bạn có hợp đồng trên đó, và do đó bạn nên thực thi hợp đồng của mình với một ngoại lệ.

Khi bạn sử dụng các chức năng "tập hợp" của mình một cách thuần túy, bạn không nên đưa ra các ngoại lệ trong trường hợp tập hợp trống, nhưng nếu bạn có các bộ sưu tập không còn là "tập hợp thuần túy" thì bạn nên đưa ra các ngoại lệ phù hợp .

Bạn nên luôn luôn làm những gì cảm thấy tự nhiên và nhất quán hơn: với tôi một bộ nên tuân thủ các quy tắc, trong khi những thứ không phải là bộ nên có quy tắc suy nghĩ đúng đắn của chúng.

Trong trường hợp của bạn, có vẻ là một ý tưởng tốt để làm:

List SomeMethod( Set someInputSet){
    var result = someInputSet
        .CombinationsOf(4, CombinationsGenerationMode.Distinct)
        .Select(combo => /* do some operation to a combination */)
        .ToList();

    // the only information here is that set is empty => there are no combinations

    // BEWARE! if 0 here it may be invalid input, but also a empty set
    if(result.Count == 0)  //Add: "&&someInputSet.NotEmpty()"

    // we go a step further, our API require combinations, so
    // this method cannot satisfy the API request, then we throw.
         throw new Exception("you requsted impossible combinations");

    return result;
}

Nhưng nó không thực sự là một ý tưởng hay, hiện tại chúng ta có trạng thái không hợp lệ có thể xảy ra trong thời gian chạy ngẫu nhiên, tuy nhiên đó là vấn đề tiềm ẩn nên chúng ta không thể loại bỏ nó, chắc chắn chúng ta có thể di chuyển ngoại lệ bên trong một số phương thức tiện ích (chính xác là cùng một mã, được di chuyển ở những nơi khác nhau), nhưng điều đó sai và về cơ bản, điều tốt nhất bạn có thể làm là tuân theo các quy tắc được đặt thông thường .

Hoàn toàn thêm độ phức tạp mới chỉ để cho thấy bạn có thể viết các phương thức truy vấn linq dường như không có giá trị cho vấn đề của bạn , tôi khá chắc chắn rằng nếu OP có thể cho chúng tôi biết thêm về miền đó, có lẽ chúng ta có thể tìm thấy vị trí ngoại lệ thực sự cần thiết (nếu tất cả, có thể vấn đề không yêu cầu bất kỳ ngoại lệ nào cả).


Vấn đề là bạn đang sử dụng các đối tượng được xác định theo toán học để giải quyết vấn đề, nhưng bản thân vấn đề có thể không yêu cầu một đối tượng được xác định theo toán học là câu trả lời (ở đây trả về danh sách). Tất cả phụ thuộc vào mức độ cao hơn của vấn đề, bất kể bạn giải quyết nó như thế nào, đó gọi là ẩn / đóng gói thông tin.
Nhà phát triển GameD

Lý do khiến "someMethod" của bạn không còn hoạt động như một tập hợp là vì bạn đang yêu cầu một phần thông tin "ngoài bộ", đặc biệt xem phần nhận xét "&& someInputset.NotEmpty ()"
GameDeveloper

1
KHÔNG! Bạn không nên ném một ngoại lệ trong lớp nữ của bạn. Bạn nên sử dụng mẫu Builder để thực thi rằng bạn thậm chí không thể NHẬN một thể hiện của FemaleClasstrừ khi nó chỉ có nữ (nó không thể tạo công khai, chỉ có lớp Builder mới có thể đưa ra một thể hiện) và có thể có ít nhất một nữ trong đó . Sau đó, mã của bạn ở khắp nơi lấy FemaleClasslàm đối số không phải xử lý ngoại lệ vì đối tượng ở trạng thái sai.
ErikE

Bạn đang giả định rằng lớp này đơn giản đến mức bạn có thể thực thi các điều kiện tiên quyết của nó một cách đơn giản bởi nhà xây dựng, trong thực tế. Bạn tranh luận là thiếu sót. Nếu bạn cấm không còn nữ, thì hoặc bạn không thể có một phương pháp để loại bỏ các học sinh ra khỏi lớp hoặc phương pháp của bạn phải đưa ra một ngoại lệ khi có 1 học sinh rời đi. Bạn vừa chuyển vấn đề của tôi đi nơi khác. Vấn đề là sẽ luôn có một số trạng thái chức năng / điều kiện tiên quyết không thể duy trì "theo thiết kế" và người dùng sẽ phá vỡ: đó là lý do tại sao Ngoại lệ tồn tại! Đây là công cụ lý thuyết khá hay, tôi hy vọng downvote không phải của bạn.
Nhà phát triển GameD

Downvote không phải của tôi, nhưng nếu có, tôi tự hào về nó. Tôi downvote cẩn thận nhưng với niềm tin. Nếu quy tắc của lớp của bạn là nó PHẢI có một mục trong đó và tất cả các mục phải đáp ứng một điều kiện cụ thể, thì việc cho phép lớp ở trạng thái không nhất quán là một ý tưởng tồi. Di chuyển vấn đề đi nơi khác chính xác là ý định của tôi! Tôi muốn vấn đề xảy ra vào thời gian xây dựng, không phải lúc sử dụng. Điểm của việc sử dụng lớp Builder thay vì ném constructor là bạn có thể xây dựng một trạng thái rất phức tạp thành nhiều phần và nhiều phần thông qua sự không nhất quán.
ErikE
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.