Виняток проти порожнього результату встановлюється, коли входи технічно достовірні, але незадовільні


108

Я розробляю бібліотеку, призначену для публічного випуску. Він містить різні методи роботи з наборами об'єктів - генерування, перевірка, розділення та проектування наборів у нові форми. У випадку, якщо це доречно, це бібліотека класів C #, що містить розширення у стилі LINQ IEnumerable, випускається як пакет NuGet.

Деяким із методів цієї бібліотеки можуть бути задані незадовільні вхідні параметри. Наприклад, у комбінаторних методах є метод для генерації всіх наборів з n елементів, які можна побудувати з вихідного набору m елементів. Наприклад, з урахуванням набору:

1, 2, 3, 4, 5

а запитання про комбінації 2 призведе до:

1, 2
1, 3
1, 4
тощо ...
5, 3
5, 4

Тепер, очевидно, можна попросити щось, чого неможливо зробити, як-от надати йому набір з 3-х предметів, а потім попросити комбінації 4-х елементів, встановивши параметр, який говорить, що він може використовувати кожен елемент лише один раз.

У цьому сценарії кожен параметр діє індивідуально :

  • Колекція джерела не є нульовою і містить елементи
  • Запитаний розмір комбінацій - це додатне ненулеве ціле число
  • Запитаний режим (використовуйте кожен елемент лише один раз) є правильним вибором

Однак стан параметрів при їх узятті створює проблеми.

У цьому сценарії ви очікуєте, що метод викине виняток (наприклад InvalidOperationException) або поверне порожню колекцію? Для мене це видається дійсним:

  • Ви не можете створити комбінації n елементів із набору m елементів, де n> m, якщо вам дозволяється використовувати кожен елемент лише один раз, тому цю операцію можна вважати неможливою InvalidOperationException.
  • Набір комбінацій розміру n, які можуть бути створені з m елементів, коли n> m - порожній набір; ніяких комбінацій не може бути вироблено.

Аргумент для порожнього набору

Перше моє занепокоєння полягає в тому, що виняток заважає ідіоматичному ланцюжковій схемі в стилі LINQ, коли ви маєте справу з наборами даних, які можуть мати невідомий розмір. Іншими словами, ви можете зробити щось подібне:

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

Якщо ваш набір змінних розмірів, поведінка цього коду непередбачувана. Якщо .CombinationsOf()викид має виняток, коли в someInputSetньому менше 4 елементів, то цей код іноді виходить з ладу під час виконання без попередньої перевірки. У наведеному вище прикладі ця перевірка є тривіальною, але якщо ви називаєте її на півдорозі довшою ланцюжком LINQ, це може стати втомливим. Якщо він поверне порожній набір, то resultвін буде порожнім, чим ви можете бути цілком задоволені.

Аргумент на виняток

Моє друге занепокоєння полягає в тому, що повернення порожнього набору може приховати проблеми - якщо ви викликаєте цей метод на півдорозі ланцюжка LINQ і він непомітно повертає порожній набір, то ви можете наткнутися на проблеми кілька кроків пізніше або опинитися з порожнім набір результатів, і це може бути не очевидно, як це сталося, враховуючи, що ви напевно щось мали у вхідному наборі.

Що б ви очікували, і який ваш аргумент для цього?


66
Оскільки порожній набір математично правильний, є ймовірність, що коли ви його отримаєте, це насправді те, що ви хочете. Математичні визначення та конвенції, як правило, вибираються для послідовності та зручності, так що речі просто виходять з ними.
асмеурер

5
@asmeurer Вони вибрані так, щоб теореми були послідовними та зручними. Вони не вибрані для полегшення програмування. (Це іноді є побічною перевагою, але іноді вони також ускладнюють програмування.)
jpmc26

7
@ jpmc26 "Вони вибрані так, щоб теореми були послідовними та зручними" - переконання, що програма завжди працює так, як очікувалося, по суті еквівалентно доказуванню теореми.
Артем

2
@ jpmc26 Я не розумію, чому ви згадали про функціональне програмування. Довести правильність для імперативних програм цілком можливо, завжди вигідно, і це можна зробити і неформально - просто подумайте трохи і використовуйте здоровий математичний сенс, коли ви пишете свою програму, і ви витратите менше часу на тестування. Статистично доведено на зразку одного ;-)
artem

5
@DmitryGrigoryev 1/0 як невизначений набагато більш математично правильний, ніж 1/0 як нескінченність.
асмеурер

Відповіді:


145

Поверніть порожній набір

Я б очікував порожнього набору, оскільки:

З набору 3 є 0 комбінацій 4 чисел, коли я можу використовувати кожне число лише один раз


5
Математично так, але це також дуже ймовірно джерело помилок. Якщо очікується такий тип введення, можливо, буде краще вимагати, щоб користувач вловлював виняток у бібліотеці загального призначення.
Кейсі Кубалл

56
Я не згоден, що це "дуже ймовірна" причина помилки. Припустимо, наприклад, ви реалізовували наївний алгоритм "matchmaknig" з великого вхідного набору. Ви можете попросити всі комбінації двох предметів, знайти єдиний "найкращий" матч серед них, а потім видалити обидва елементи і почати все з нового, меншого набору. Врешті-решт ваш набір буде порожнім, і не залишиться жодних пар для розгляду: виняток на даний момент є обструктивним. Я думаю, що існує багато способів опинитися в подібній ситуації.
amalloy

11
Насправді ви не знаєте, чи це помилка в очах користувача. Порожній набір - це те, що користувач повинен перевірити, якщо це необхідно. if (result.Any ()) DoSomething (результат.Перший ()); else DoSomethingElse (); набагато краще, ніж спробувати {result.first (). dosomething ();} зловити {DoSomethingElse ();}
Гуран

4
@TripeHound Ось що підсумувало рішення в кінцевому підсумку: вимагаючи від розробника за допомогою цього методу перевірити, а потім кинути, має набагато менший вплив, ніж кидати виняток, якого вони не хочуть, з точки зору зусиль із розробки, продуктивності програми та простота програмного потоку.
anaximander

6
@Darthfett Спробуйте порівняти це з існуючим методом розширення LINQ: Where (). Якщо у мене є пункт: Де (x => 1 == 2) я не отримую винятку, я отримую порожній набір.
Некорас

79

Якщо сумніваєтесь, запитайте когось іншого.

Ваша прикладна функція має дуже подібну функцію в Python : itertools.combinations. Подивимося, як це працює:

>>> 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))
[]

І мені це чудово чудово. Я очікував результату, який я міг би повторити, і отримав такий.

Але, очевидно, якби ти запитав щось дурне:

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

Тож я б сказав, якщо всі ваші параметри підтверджені, але результат порожній набір поверне порожній набір, ви не єдиний, хто це робить.


Як сказав @Bakuriu в коментарях, це також те саме, що і для SQLзапиту SELECT <columns> FROM <table> WHERE <conditions>. Поки <columns>, <table>, <conditions>добре сформовані і формуються посилання на існуючі імена, ви можете створити набір умов , що виключають одна одну. Отриманий запит просто не дасть рядків замість того, щоб викидати an InvalidConditionsError.


4
Зрозуміло, тому що це не ідіоматично в мовному просторі C # / Linq (див. Відповідь sbecker щодо того, як подібні проблеми поза межами обробляються в мові). Якби це питання Python, це було б від мене +1.
Джеймс Снелл

32
@JamesSnell Я ледве бачу, як це стосується проблеми поза межами. Ми не підбираємо елементи за індексами для їх впорядкування, ми збираємо nелементи в колекції для підрахунку кількості способів їх вибору. Якщо в якийсь момент під час їх вибору не залишилося елементів, то немає (0) способу вибору nелементів із зазначеної колекції.
Матіас Еттінгер

1
Тим не менш, Python не є хорошим оракулом для питань C #: наприклад, C # дозволив би 1.0 / 0, Python цього не зробив.
Дмитро Григор’єв

2
@MathiasEttinger Настанови Python щодо того, як і коли використовувати винятки, сильно відрізняються від C #, тому використання поведінки модулів Python як настанови не є хорошою метрикою для C #.
Мураха P

2
Замість вибору Python ми могли просто вибрати SQL. Чи добре сформований SELECT запит над таблицею створює виняток, коли набір результатів порожній? (спойлер: ні!). "Добре сформованість" запиту залежить лише від самого запиту та деяких метаданих (наприклад, чи існує таблиця і чи є це поле (з цим типом)? (можливо, порожній) дійсний результат або виняток, оскільки у "сервера" виникли проблеми (наприклад, сервер db впав чи щось).
Бакуріу

72

Простіше кажучи:

  • Якщо є помилка, вам слід створити виняток. Це може включати виконання кроків замість одного ланцюгового дзвінка, щоб точно знати, де сталася помилка.
  • Якщо помилок немає, але отриманий набір порожній, не створюйте винятків, повертайте порожній набір. Порожній набір - це дійсний набір.

23
+1, 0 - цілком дійсне і дійсно надзвичайно важливе число. Є так багато випадків, коли запит може законно повернути нульові звернення, що збільшення винятку буде сильно дратувати.
Кіліан Фот

19
хороша порада, але чи зрозуміла ваша відповідь?
Еван

54
Я все ще не мудріший, якщо ця відповідь підказує, що ОП повинна throwповернути порожній набір ...
Джеймс Снелл

7
У вашій відповіді сказано, що я можу зробити і те, і в залежності від того, чи є помилка, але ви не згадуєте, чи вважали б ви приклад сценарію помилкою. У коментарі вище ви говорите, що я повинен повернути порожній набір "якщо немає проблем", але знову ж таки, незадовільні умови можна вважати проблемою, і ви продовжуєте говорити, що я не створюю винятків, коли повинен. То що мені робити?
anaximander

3
Ах, бачу - ти, можливо, неправильно зрозумів; Я нічого не прикував, я пишу метод, який, як очікується, буду використовувати в ланцюжку. Мені цікаво, чи хотів би гіпотетичний майбутній розробник, який використовує цей метод, кинути його у вищезазначений сценарій.
anaximander

53

Я згоден з відповіддю Юана, але хочу додати конкретні міркування.

Ви маєте справу з математичними операціями, тому може бути корисною порадою дотримуватися однакових математичних визначень. З математичної точки зору кількість r- наборів n- набору (тобто nCr ) добре визначена для всіх r> n> = 0. Воно дорівнює нулю. Тому повернення порожнього набору було б очікуваним випадком з математичної точки зору.


9
Це хороший момент. Якщо бібліотека була вищого рівня, як підбір комбінацій кольорів, щоб зробити палітру - тоді є сенс кинути помилку. Тому що ви знаєте, палітра без кольорів - це не палітра. Але набір без записів все ще є набором, і математика визначає його як рівний порожній набір.
Капітан Людина

1
Набагато краще, ніж відповіді Еванса, бо це цитує математичну практику. +1
user949300

24

Я знаходжу хороший спосіб визначити, чи слід використовувати виняток, - це уявити людей, які беруть участь у трансакції.

Візьмемо приклад вмісту файлу:

  1. Будь ласка, донесіть до мене вміст файлу "не існує.txt"

    а. "Ось вміст: порожня колекція символів"

    б. "Ем, є проблема, що файл не існує. Я не знаю, що робити!"

  2. Будь ласка, донесіть до мене вміст файлу, "існує, але порожній.txt"

    а. "Ось вміст: порожня колекція символів"

    б. "Ем, є проблема. У цьому файлі нічого немає. Я не знаю, що робити!"

Без сумніву, деякі не погоджуються, але для більшості людей "Ерм, є проблема" має сенс, коли файл не існує, і повертається "порожня колекція символів", коли файл порожній.

Тож застосовуючи такий же підхід до вашого прикладу:

  1. Будь ласка, дайте мені всі комбінації 4 предметів для {1, 2, 3}

    а. Немає таких, ось порожній набір.

    б. Є проблема, я не знаю, що робити.

Знову ж таки, "Є проблема" було б має сенс, якби, наприклад null, пропонували як набір елементів, але "ось порожній набір" здається розумною відповіддю на вищезазначений запит.

Якщо повернення порожнього значення маскує проблему (наприклад, файл, який відсутній, а null), то замість цього слід використовувати виключення (якщо ваша обрана мова підтримує option/maybeтипи, вони іноді мають більше сенсу). В іншому випадку повернення порожнього значення, ймовірно, спростить вартість і краще відповідає принципу найменшого здивування.


4
Ця порада хороша, але не стосується цієї справи. Кращий приклад: Скільки днів після 30-го січня ?: 1 Скільки днів після 30-го лютого ?: 0 Скільки днів після 30-го лютого?: Виняток Скільки робочих годин на 30 : 1 лютого: Виняток
Гуран

9

Оскільки це для бібліотеки загального призначення, моїм інстинктом було б Нехай кінцевий користувач вибере.

Так само, як у нас є Parse()і TryParse()доступні нам, ми можемо мати вибір, який ми будемо використовувати, залежно від того, який висновок нам потрібен для функції. Ви витрачаєте менше часу на написання та підтримку обгортки функцій, щоб кинути виняток, ніж сперечатися над вибором однієї версії функції.


3
+1 тому, що мені завжди подобається ідея вибору і тому, що ця модель має тенденцію заохочувати програмістів до валідації власного вкладу. Просте існування функції TryFoo дає зрозуміти, що певні комбінації вводу можуть спричинити проблеми, і якщо документація пояснює, що це за потенційні проблеми, кодер може перевірити їх та обробляти недійсні входи більш чітко, ніж вони могли, просто зафіксувавши виняток або у відповідь на порожній набір. Або вони можуть бути лінивими. Так чи інакше, рішення - їхнє.
aleppke

19
Ні, порожній набір - це математично правильна відповідь. Робити щось інше - переосмислювати чітко узгоджену математику, що не слід робити на примху. Поки ми переходимо до цього, чи будемо ми переосмислювати функцію експоненції, щоб скинути помилку, якщо показник дорівнює нулю, тому що ми вирішили, що нам не подобається умова про те, що будь-яке число, підняте на "нульову" потужність, дорівнює 1?
Уайлдкард

1
Я збирався відповісти на щось подібне. Методи розширення Linq Single()і SingleOrDefault()негайно виникли на увазі. Одномісний кидає виняток, якщо результат нульовий або> 1, тоді як SingleOrDefault не кине, а натомість повернеться default(T). Можливо, ОП міг би використати CombinationsOf()і CombinationsOfOrThrow().
RubberDuck

1
@Wildcard - ви говорите про два різних результатах на один і той же вхід, що не одне і те ж. ОП зацікавлений лише у виборі а) результату або б) кидання виключення, яке не є результатом.
Джеймс Снелл

4
Singleі SingleOrDefault(або Firstі FirstOrDefault) - зовсім інша історія, @RubberDuck. Підбираючи мій приклад з попереднього коментаря. Це прекрасно, щоб AnSer ні на запит «Що навіть примирити більше , ніж два існують?» , оскільки це математично обгрунтована і розумна відповідь. У будь-якому випадку, якщо ви запитуєте: "Який перший навіть прем'єр більший, ніж два?" природної відповіді немає. Ви просто не можете відповісти на це запитання, оскільки ви просите ввести одне число. Це не НЕ ніхто (це не єдине число, а набір), а не 0. Отже , ми викидаємо виняток.
Пол Кертчер

4

Вам потрібно перевірити аргументи, надані під час виклику вашої функції. Власне кажучи, ви хочете знати, як поводитися з невірними аргументами. Те, що кілька аргументів залежать один від одного, не компенсує того, що ви підтверджуєте аргументи.

Таким чином, я хотів би проголосувати за аргументException, надавши необхідну інформацію, щоб користувач зрозумів, що пішло не так.

Як приклад, перевірте public static TSource ElementAt<TSource>(this IEnumerable<TSource>, Int32)функцію в Linq. Який кидає ArgumentOutOfRangeException, якщо індекс менше 0 або більший або дорівнює кількості елементів у джерелі. Таким чином, індекс підтверджується щодо кількості, наданої абонентом.


3
+1 для наведення прикладу подібної ситуації в мові.
Джеймс Снелл

13
Як не дивно, ваш приклад змусив мене задуматися і спонукав мене до чогось, що, на мою думку, робить сильний контрприклад. Як я зазначив, цей метод розроблений так, щоб він був подібний до LINQ, так що користувачі можуть ланцюговувати його разом з іншими методами LINQ. Якщо ви new[] { 1, 2, 3 }.Skip(4).ToList();отримаєте порожній набір, це змушує мене думати, що повернення порожнього набору є, можливо, кращим варіантом тут.
анаксимандр

14
Це не питання мовної семантики, семантика проблемної області вже дає правильну відповідь, тобто повернути порожній набір. Робити що-небудь інше порушує принцип найменшого здивування для того, хто працює в області комбінаторної проблеми.
Укко

1
Я починаю розуміти аргументи повернення порожнього набору. Чому це було б сенсом. Принаймні для цього конкретного прикладу.
sbecker

2
@sbecker, щоб принести додому точку: Якщо питання було «Як багато комбінацій там з ...» , то «нуль» буде повністю дійсний відповідь. До того ж, «Що є комбінації , такі , що ...» має порожня множина повністю дійсний відповідь. Якби питання було: "Яке перше поєднання таке, що ...", тоді і лише тоді було б винятком виняток, оскільки питання не відповідає. Дивіться також коментар Павла Д.К. .
Wildcard

3

Ви повинні виконати одне з наступних дій (хоча і надалі послідовно вирішувати основні проблеми, такі як негативна кількість комбінацій):

  1. Забезпечте дві реалізації: одна, яка повертає порожній набір, коли входи разом є безглуздими, і одна, яка кидає. Спробуйте зателефонувати їм CombinationsOfі CombinationsOfWithInputCheck. Або все, що вам подобається. Ви можете змінити це, щоб перевіркою введення було коротше ім'я та список CombinationsOfAllowInconsistentParameters.

  2. Для методів Linq поверніть порожнє IEnumerableв точне приміщення, яке ви окреслили. Потім додайте ці методи Linq до своєї бібліотеки:

    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;
          }
       }
    }

    Нарешті, використовуйте це так:

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

    або так:

    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();

    Зауважте, що приватний метод, який відрізняється від загальнодоступного, потрібен для того, щоб поведінка метання чи дії відбувалася при створенні ланцюга linq, а не через деякий час, коли він перераховується. Ви хочете, щоб це відразу кинуло.

    Однак зауважте, що, звичайно, він повинен перерахувати хоча б перший пункт, щоб визначити, чи є якісь елементи. Це потенційний недолік , який я думаю , що в основному знижується за рахунок того , що будь-які майбутні глядачі можуть досить легко міркувати про те , що ThrowIfEmptyметод повинен перерахувати принаймні один елемент, тому слід НЕ здивуєш це робити. Але ти ніколи не знаєш. Ви можете зробити це більш явним ThrowIfEmptyByEnumeratingAndReEmittingFirstItem. Але це здається гігантським зайвим.

Я думаю, що №2 є цілком, ну, приголомшливим! Тепер влада в коді виклику, і наступний читач коду зрозуміє, що саме він робить, і не доведеться мати справу з несподіваними винятками.


Поясніть, будь ласка, що ви маєте на увазі. Як щось у моєму дописі "не поводиться як набір" і чому це погано?
ErikE

Ви в основному виконуєте те ж саме, що і в моїй відповіді, замість того, щоб
обгортати

Я не бачу, як моя відповідь "в основному робить те саме" вашої відповіді. Вони здаються мені зовсім іншими.
ErikE

В основному ви просто виконуєте ту саму умову "мого поганого класу". Але ти не кажеш, що ^^. Чи погоджуєтесь ви, що ви нав'язуєте існування 1 елемента в результаті запиту?
GameDeveloper

Вам доведеться говорити чіткіше за очевидно дурних програмістів, які все ще не розуміють того, про що ви говорите. Який клас поганий? Чому це погано? Я нічого не нав'язую. Я дозволяю користувачеві класу вирішувати остаточну поведінку замість ТВОРЧОГО класу. Це дозволяє використовувати послідовну парадигму кодування, де методи Linq не викидаються випадковим чином через обчислювальний аспект, який насправді не є порушенням звичайних правил, наприклад, якщо ви запитували -1 предмет одночасно.
ErikE

2

Я можу бачити аргументи для обох випадків використання - виняток є великим, якщо код нижче за потоком очікує набори, які містять дані. З іншого боку, просто порожній набір чудовий, якщо цього очікується.

Я думаю, це залежить від очікувань абонента, якщо це помилка чи прийнятний результат - тому я б передавав вибір абоненту. Може ввести варіант?

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


10
Хоча я дістаюсь, куди ти йдеш із цим, я намагаюся уникати методів, які мають багато варіантів, які змінюють їх поведінку. Я все ще не на 100% задоволений переглядом режиму, який вже є; Мені дуже не подобається ідея параметра параметра, який змінює поведінку винятку методу. Я відчуваю, що якщо методу потрібно кинути виняток, його потрібно кинути; якщо ви хочете приховати цей виняток, це ваше рішення як код виклику, а не те, що ви можете змусити мій код робити з правильним параметром введення.
anaximander

Якщо абонент очікує набору, який не є порожнім, то абонент повинен або обробити порожній набір, або викинути виняток. Що стосується виклику, то порожній набір - це прекрасна відповідь. І ви можете мати більше одного абонента з різними думками щодо того, чи добре пусті набори чи ні.
gnasher729

2

Є два підходи, щоб вирішити, чи немає явної відповіді:

  • Напишіть код, припускаючи спочатку один варіант, а потім інший. Поміркуйте, який із них найкраще працює на практиці.

  • Додайте "суворий" булевий параметр, щоб вказати, хочете ви, щоб параметри були суворо перевірені чи ні. Наприклад, у Java SimpleDateFormatє setLenientметод спроби розбору входів, які не повністю відповідають формату. Звичайно, вам доведеться вирішити, що таке за замовчуванням.


2

Виходячи з власного аналізу, повернення порожнього набору здається правильним - ви навіть визначили це як щось, що деякі користувачі можуть насправді захотіти, і не потрапили в пастку заборонити якесь використання, тому що ви не можете уявити, щоб користувачі хотіли ним користуватися. цей шлях.

Якщо ви дійсно відчуваєте, що деякі користувачі можуть захотіти примусово повертатись, тоді дайте їм можливість попросити такої поведінки, а не примушувати її до всіх. Наприклад, ви можете:

  • Зробіть його параметром конфігурації для будь-якого об’єкта, що виконує дію для користувача.
  • Зробити це прапором, який користувач може додатково перейти у функцію.
  • Надайте AssertNonemptyчек, який вони можуть поставити у свої ланцюги.
  • Зробіть дві функції, таку, яка стверджує, що не порожня, та та, що не відповідає.

це, здається, не пропонує нічого суттєвого щодо пунктів, викладених та пояснених у попередніх 12 відповідях
ґнат

1

Це дійсно залежить від того, що очікують отримати ваші користувачі. На прикладі (дещо неспорідненого) прикладу, якщо ваш код виконує поділ, ви можете або викинути виняток, або повернутись, Infабо NaNподілити на нуль. Однак це ні правильно, ні неправильно:

  • якщо ви повернетесь Infдо бібліотеки Python, люди нападуть на вас за приховування помилок
  • якщо ви виявите помилку в бібліотеці Matlab, люди нападуть на вас за те, що не обробляли дані з відсутніми значеннями

У вашому випадку я б обрав рішення, яке буде найменш вражаючим для кінцевих користувачів. Оскільки ви розробляєте бібліотеку, яка займається наборами, порожній набір здається чимось, що очікували б ваші користувачі, тому повернення звучить як розумна справа. Але я можу помилятися: ви набагато краще розумієте контекст, ніж будь-хто інший, тому, якщо ви очікуєте, що ваші користувачі покладаються на те, що набір завжди не порожній, ви повинні кинути виняток відразу.

Рішення, які дозволяють користувачеві вибрати (наприклад, додавання "строгого" параметра), не є остаточним, оскільки вони замінюють вихідний питання новим еквівалентним: "Яке значення strictмає бути за замовчуванням?"


2
Я думав про використання аргументу NaN у своїй відповіді, і я поділяю вашу думку. Ми не можемо сказати більше, не знаючи домену OP
GameDeveloper

0

Загальноприйнята думка (з математики), що при виборі елементів на множині ви не можете знайти жодного елемента, а значить, ви отримаєте порожній набір . Звичайно, ви повинні відповідати математиці, якщо ви йдете цим шляхом:

Загальні правила встановлення:

  • Set.Foreach (присудок); // завжди повертає true для порожніх наборів
  • Set.Exists (присудок); // завжди повертає false для порожніх наборів

Ваше запитання дуже тонке:

  • Можливо, вхід вашої функції повинен дотримуватись контракту : у цьому випадку будь-який недійсний вхід повинен створювати виняток, ось це, функція не працює за регулярними параметрами.

  • Можливо, вхід вашої функції повинен вести себе точно так само, як і множина , а тому має бути в змозі повернути порожній набір.

Тепер, якби я був у вас, я б пішов шляхом "Встановити", але з великим "АЛЕ".

Припустимо, у вас є колекція, яка "за допомогою гіпотезу" повинна мати лише студенток:

class FemaleClass{

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

Тепер ваша колекція вже не є "чистим набором", оскільки у вас є контракт на неї, а значить, ви повинні застосувати свій контракт за винятком.

Коли ви чисто використовуєте свої функції "set", вам не слід викидати винятки у випадку порожнього набору, але якщо у вас є колекції, які більше не є "чистими наборами", ви повинні викидати винятки там, де це належно .

Ви завжди повинні робити те, що вважається більш природним і послідовним: для мене набір повинен дотримуватися встановлених правил, тоді як речі, які не є наборами, повинні мати свої правильно продумані правила.

У вашому випадку здається, що це зробити добре:

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;
}

Але це не дуже гарна ідея, тепер у нас є недійсний стан, який може виникати під час виконання у випадкові моменти, однак це неявне в проблемі, тому ми не можемо його усунути, впевнений, що ми можемо перемістити виняток всередині якогось утилітного методу (саме так той самий код, переміщений у різних місцях), але це неправильно, і в основному найкраще, що ви можете зробити, це дотримуватися правил встановлення звичайних правил .

Infact додає нової складності лише для того, щоб показати, що ви можете писати методи запитів linq, здається, не вашій проблемі , я впевнений, що якщо OP може розповісти нам більше про її домен, можливо, ми зможемо знайти місце, де виняток справді потрібен (якщо взагалі, можливо, проблема взагалі не вимагає жодного винятку).


Проблема полягає в тому, що ви використовуєте математично визначені об'єкти для вирішення проблеми, але сама проблема може не вимагати математично визначеного об'єкта як відповіді (тут фактично повертається список). Все залежить від проблеми вищого рівня, незалежно від того, як ви її вирішите, це називається інкапсуляція / приховування інформації.
GameDeveloper

Причиною, через яку ваш "SomeMethod" більше не повинен поводитись як набір, полягає в тому, що ви запитуєте інформацію "поза наборами", зокрема перегляньте коментовану частину "&& someInputSet.NotEmpty ()"
GameDeveloper

1
НЕМАЄ! Ви не повинні кидати виняток у своєму жіночому класі. Ви повинні використовувати шаблон Builder, який накладає на те, що ви навіть не можете отримати екземпляр, FemaleClassякщо тільки у нього є жінки (це не загальнодоступна публікація; лише клас Builder може роздати екземпляр), і, можливо, має принаймні одну жінку . Тоді ваш код у всьому місці, яке береться FemaleClassза аргумент, не повинен обробляти винятки, оскільки об’єкт знаходиться в неправильному стані.
ErikE

Ви припускаєте, що клас настільки простий, що ви можете реалізувати його передумови просто конструктором, насправді. Ваш аргумент хибний. Якщо ви забороняєте більше не мати жінок, то або ви не можете використовувати метод для видалення студентів з класу, або ваш метод повинен кинути виняток, коли залишився 1 студент. Ви просто перенесли мою проблему в інше місце. Справа в тому, що завжди буде деяка передумова / стан функцій, яку не можна підтримувати "задумом", і що користувач збирається порушити: ось чому Винятки існують! Це досить теоретичні речі, я сподіваюся, що голосування не за вами.
GameDeveloper

Оцінка не є моєю, але якби вона була, я би пишався цим. Я виступаю обережно, але з переконанням. Якщо правило вашого класу полягає в тому, що він ОБОВ'ЯЗКОВО має містити в ньому предмет, і всі елементи повинні відповідати певній умові, то дозволяти класу перебувати в непослідовному стані - це погана ідея. Переміщення проблеми в інше місце - це саме мій намір! Я хочу, щоб проблема виникала в час будівництва, а не в час використання. Сенс використання класу Builder замість метання конструктора полягає в тому, що ви можете створити дуже складний стан у багатьох частинах і частинах через невідповідності.
ErikE
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.