Исключение по сравнению с пустым набором результатов, когда входные данные технически действительны, но не удовлетворяют

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
Так как пустой набор математически корректен, есть вероятность, что когда вы его получите, это именно то, что вы хотите. Математические определения и условные обозначения обычно выбираются для согласованности и удобства, чтобы с ними что-то получалось.
asmeurer
5
@asmeurer Они выбраны так, чтобы теоремы были последовательными и удобными. Они не выбраны, чтобы облегчить программирование. (Это иногда является побочным эффектом, но иногда они также
усложняют
7
@ jpmc26 «Они выбраны таким образом, чтобы теоремы были последовательными и удобными» - убедиться, что ваша программа всегда работает так, как ожидается, по существу эквивалентно доказательству теоремы.
Артем
2
@ jpmc26 Я не понимаю, почему ты упомянул функциональное программирование. Доказательство правильности для императивных программ вполне возможно, всегда выгодно, и может быть сделано и неофициально - просто подумайте немного и используйте здравый математический смысл при написании вашей программы, и вы будете тратить меньше времени на тестирование. Статистически доказано на примере одного ;-)
Артем
5
@DmitryGrigoryev 1/0 как неопределенное - намного более математически правильно, чем 1/0 как бесконечность.
asmeurer

Ответы:

145

Вернуть пустой набор

Я ожидал бы пустой набор, потому что:

Есть 0 комбинаций из 4 чисел из набора 3, когда я могу использовать каждый номер только один раз

Ewan
источник
5
Математически, да, но это также очень вероятно источник ошибки. Если ожидается такой тип ввода, может быть лучше потребовать, чтобы пользователь перехватил исключение в библиотеке общего назначения.
Кейси Кубалл
56
Я не согласен, что это «очень вероятная» причина ошибки. Предположим, например, что вы реализовали наивный алгоритм «matchmaknig» из большого набора входных данных. Вы можете запросить все комбинации двух предметов, найти одно «лучшее» совпадение среди них, а затем удалить оба элемента и начать заново с новым, меньшим набором. В конце концов ваш набор будет пустым, и не останется никаких пар для рассмотрения: исключение на этом этапе является препятствующим. Я думаю, что есть много способов оказаться в подобной ситуации.
Амаллой
11
На самом деле вы не знаете, если это ошибка в глазах пользователя. Пустой набор - это то, что пользователь должен проверить, если это необходимо. if (result.Any ()) DoSomething (result.First ()); еще DoSomethingElse (); намного лучше, чем try {result.first (). dosomething ();} catch {DoSomethingElse ();}
Гуран,
4
@TripeHound Вот что в конце концов помогло принять решение: требование, чтобы разработчик использовал этот метод для проверки, а затем выброса, оказало гораздо меньшее влияние, чем создание исключения, которое им не нужно, с точки зрения усилий по разработке, производительности программы и простота выполнения программы.
Анаксимандр
6
@Darthfett Попробуйте сравнить это с существующим методом расширения LINQ: где (). Если у меня есть предложение: Где (x => 1 == 2) я не получаю исключения, я получаю пустой набор.
Necoras
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>хорошо сформированы и формируются ссылки на существующие имена, вы можете создать набор условий , которые исключают друг друга. Результирующий запрос просто не даст никаких строк вместо броска InvalidConditionsError.

Матиас Эттингер
источник
4
Понижено, потому что это не идиоматично в языковом пространстве C # / Linq (см. Ответ sbecker о том, как схожие проблемы решаются в языке). Если бы это был вопрос по Python, я бы получил +1.
Джеймс Снелл
32
@JamesSnell Я едва вижу, как это связано с проблемой «за пределами». Мы не выбираем элементы по индексам, чтобы переупорядочить их, мы выбираем nэлементы в коллекции, чтобы подсчитать количество способов их выбора. Если в какой-то момент при выборе элементов не осталось элементов, то нет способа выбора nэлементов из указанной коллекции (0) .
Матиас Эттингер
1
Тем не менее, Python не является хорошим оракулом для вопросов C #: например, C # позволит 1.0 / 0, Python не позволит.
Дмитрий Григорьев
2
@MathiasEttinger Рекомендации Python относительно того, как и когда использовать исключения, сильно отличаются от C #, поэтому использование поведения модулей Python в качестве руководства не является хорошим показателем для C #.
Муравей P
2
Вместо выбора Python мы могли бы просто выбрать SQL. Правильно ли сформированный SELECT запрос к таблице создает исключение, когда набор результатов пуст? (спойлер: нет!). «Правильность» запроса зависит только от самого запроса и некоторых метаданных (например, существует ли таблица и имеет ли это поле (с этим типом)?) Как только запрос сформирован правильно и вы выполните его, вы ожидаете либо (возможно пустой) допустимый результат или исключение, потому что у «сервера» возникла проблема (например, произошел сбой сервера БД или что-то в этом роде).
Бакуриу
72

С точки зрения непрофессионала:

  • Если есть ошибка, вы должны вызвать исключение. Это может включать в себя действия по шагам, а не по одному цепочечному вызову, чтобы точно знать, где произошла ошибка.
  • Если ошибки нет, но результирующий набор пуст, не вызывайте исключение, возвращайте пустой набор. Пустой набор является допустимым набором.
Тулаинс Кордова
источник
23
+1, 0 - это совершенно верное и очень важное число. Есть так много случаев, когда запрос может законно вернуть ноль хитов, что создание исключения будет очень раздражающим.
Килиан Фот
19
хороший совет, но твой ответ ясен?
Эван
54
Я все еще не знаю, может ли этот ответ предположить, что OP должен throwили должен вернуть пустой набор ...
Джеймс Снелл
7
В вашем ответе говорится, что я могу сделать и то, и другое, в зависимости от того, есть ли ошибка, но вы не упоминаете, считаете ли вы пример сценария ошибкой. В приведенном выше комментарии вы говорите, что я должен вернуть пустой набор «если проблем нет», но опять же неудовлетворительные условия могут считаться проблемой, и вы продолжаете говорить, что я не поднимаю исключения, когда должен. Так что мне делать?
Анаксимандр
3
Ах, я вижу - вы, возможно, неправильно поняли; Я ничего не связываю, я пишу метод, который я ожидаю использовать в цепочке. Мне интересно, захочет ли гипотетический будущий разработчик, использующий этот метод, в приведенном выше сценарии.
Анаксимандр
53

Я согласен с ответом Эвана, но хочу добавить конкретную аргументацию.

Вы имеете дело с математическими операциями, поэтому может быть хорошим советом придерживаться тех же математических определений. С математической точки зрения число r- множеств n- множества (т. Е. NCr ) хорошо определено для всех r> n> = 0. Оно равно нулю. Поэтому возвращение пустого набора было бы ожидаемым случаем с математической точки зрения.

sigy
источник
9
Это хороший момент. Если библиотека была более высокого уровня, как выбор комбинации цветов для создания палитры - тогда имеет смысл выдать ошибку. Потому что вы знаете, что палитра без цветов не палитра. Но набор без записей все еще является набором, и математика определяет его как равный пустому набору.
Капитан Мэн
1
Гораздо лучше, чем ответ Ewans, потому что он цитирует математическую практику. +1
user949300
24

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

Взяв в качестве примера извлечение содержимого файла:

  1. Пожалуйста, принесите мне содержимое файла, "не существует. Текст"

    а. «Вот содержимое: пустая коллекция символов»

    б. «Хм, проблема в том, что этот файл не существует. Я не знаю, что делать!»

  2. Пожалуйста, извлеките мне содержимое файла, "существует, но является пустым.txt"

    а. «Вот содержимое: пустая коллекция символов»

    б. «Хм, есть проблема, в этом файле ничего нет. Я не знаю, что делать!»

Без сомнения, некоторые не согласятся, но для большинства людей «Эрм, есть проблема» имеет смысл, когда файл не существует, и возвращает «пустую коллекцию символов», когда файл пуст.

Таким образом, применяя тот же подход к вашему примеру:

  1. Пожалуйста, дайте мне все комбинации из 4 предметов для {1, 2, 3}

    а. Их нет, вот пустой набор.

    б. Есть проблема, я не знаю, что делать.

Опять же, «есть проблема» будет иметь смысл, если, например, они nullбудут предложены в качестве набора элементов, но «вот пустой набор» кажется разумным ответом на вышеуказанный запрос.

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

Дэвид Арно
источник
4
Этот совет хорош, но к этому делу не относится. Лучший пример: сколько дней после 30-го января ?: 1 сколько дней после 30-го февраля ?: 0 сколько дней после 30-го месяца ?: Исключение Сколько рабочих часов 30 : февраль: Исключение
Гуран,
9

Поскольку это для библиотеки общего назначения, мой инстинкт был бы Пусть конечный пользователь выбирает.

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

Джеймс Снелл
источник
3
+1, потому что мне всегда нравится идея выбора, и потому что этот шаблон имеет тенденцию побуждать программистов проверять свои собственные данные. Само существование функции TryFoo делает очевидным, что определенные комбинации ввода могут вызывать проблемы, и если документация объясняет, каковы эти потенциальные проблемы, кодировщик может проверять их и обрабатывать неверные вводы более чистым способом, чем они могли, просто перехватывая исключение или отвечая на пустой набор. Или они могут быть ленивыми. В любом случае, это их решение.
алепке
19
Нет, пустой набор - математически правильный ответ. Делать что-то еще - значит переосмыслить твердо согласованную математику, что не следует делать по прихоти. В то время как мы находимся на этом, мы должны переопределить функцию возведения в степень, чтобы выдать ошибку, если показатель степени равен нулю, потому что мы решили, что нам не нравится соглашение, что любое число, возведенное в "нулевую" степень, равно 1?
Подстановочный
1
Я собирался ответить на что-то подобное. Методы расширения Linq Single()и SingleOrDefault()сразу возникли на ум. Single генерирует исключение, если результат равен нулю или> 1, в то время как SingleOrDefault не выбрасывает, а вместо этого возвращает default(T). Возможно, ОП мог бы использовать CombinationsOf()и CombinationsOfOrThrow().
RubberDuck
1
@Wildcard - вы говорите о двух разных результатах для одного и того же ввода, что не одно и то же. ОП заинтересован только в выборе а) результата или б) выдачи исключения, которое не является результатом.
Джеймс Снелл
4
Singleи SingleOrDefault(или Firstи FirstOrDefault) - это совершенно другая история, @RubberDuck. Собираю мой пример из моего предыдущего комментария. Это прекрасно, чтобы AnSer ни на запрос «Что даже примирует больше , чем два существуют?» , так как это математически обоснованный и разумный ответ. Во всяком случае, если вы спрашиваете: «Какое первое четное простое больше двух?» нет естественного ответа. Вы просто не можете ответить на вопрос, потому что вы просите одно число. Это не ноль (это не одно число, а множество), а не 0. Поэтому мы бросаем исключение.
Пол Керчер
4

Вам необходимо проверить аргументы, предоставленные при вызове вашей функции. И на самом деле, вы хотите знать, как обрабатывать недопустимые аргументы. Тот факт, что несколько аргументов зависят друг от друга, не компенсирует тот факт, что вы проверяете аргументы.

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

В качестве примера, проверьте public static TSource ElementAt<TSource>(this IEnumerable<TSource>, Int32)функцию в Linq. Который выдает исключение ArgumentOutOfRangeException, если индекс меньше 0 или больше или равен количеству элементов в источнике. Таким образом, индекс проверяется в отношении перечисляемого, предоставленного вызывающей стороной.

sbecker
источник
3
+1 за приведение примера похожей ситуации на языке.
Джеймс Снелл
13
Как ни странно, твой пример заставил меня задуматься и привел меня к тому, что, как мне кажется, является сильным контрпримером. Как я уже отмечал, этот метод разработан так, чтобы быть похожим на LINQ, так что пользователи могут связывать его вместе с другими методами LINQ. Если вы new[] { 1, 2, 3 }.Skip(4).ToList();получаете пустой набор, это заставляет меня думать, что возвращение пустого набора, возможно, является лучшим вариантом здесь.
Анаксимандр
14
Это не проблема языковой семантики, семантика проблемной области уже дает правильный ответ, то есть вернуть пустой набор. Выполнение чего-либо еще нарушает принцип наименьшего удивления для человека, работающего в области комбинаторных задач.
Ukko
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();

    Обратите внимание, что закрытый метод, отличный от публичного, необходим для того, чтобы поведение throw или action происходило при создании цепочки linq, а не через некоторое время при ее перечислении. Вы хотите, чтобы это выбросило прямо сейчас.

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

Я думаю, что # 2 довольно, ну, круто! Теперь сила в вызывающем коде, и следующий читатель кода точно поймет, что он делает, и ему не придется иметь дело с неожиданными исключениями.

ErikE
источник
Пожалуйста, объясните, что вы имеете в виду. Как что-то в моем посте "не ведет себя как набор" и почему это плохо?
ErikE
Вы в основном делаете то же самое, что и мой ответ, вместо того, чтобы обернуть запрос, который вы произвели, чтобы запросить условие If, результат не изменится uu
GameDeveloper
Я не вижу, как мой ответ "в основном делает то же самое" с вашим ответом. Они кажутся мне совершенно другими.
ErikE
По сути, вы просто выполняете то же условие «мой плохой класс». Но вы не говорите, что ^^. Согласны ли вы с тем, что вы указываете наличие 1 элемента в результате запроса?
GameDeveloper
Вам придется говорить более четко за явно глупых программистов, которые все еще не понимают, о чем вы говорите. Какой класс плохой? Почему это плохо? Я ничего не навязываю. Я позволяю ПОЛЬЗОВАТЕЛЮ класса решать окончательное поведение вместо СОЗДАТЕЛЯ класса. Это позволяет использовать согласованную парадигму кодирования, при которой методы Linq не генерируют случайным образом из-за вычислительного аспекта, который на самом деле не является нарушением нормальных правил, например, если вы запрашиваете -1 элемент за раз.
ErikE
2

Я вижу аргументы для обоих вариантов использования - исключение велико, если нижестоящий код ожидает наборы, которые содержат данные. С другой стороны, просто пустой набор отлично подходит, если это ожидается.

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

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

Кристиан Зауэр
источник
10
Хотя я понимаю, куда вы идете с этим, я стараюсь избегать методов, передающих множество опций, которые изменяют их поведение. Я все еще не на 100% доволен, что enum mode уже есть; Мне действительно не нравится идея параметра option, который изменяет поведение исключения метода. Я чувствую, что если метод должен выдать исключение, он должен бросить; если вы хотите скрыть это исключение, это ваше решение в качестве вызывающего кода, а не то, что вы можете заставить мой код делать с правильной опцией ввода.
Анаксимандр
Если вызывающий объект ожидает набор, который не является пустым, то он должен либо обработать пустой набор, либо выдать исключение. Что касается вызываемого абонента, пустой набор - отличный ответ. И вы можете иметь более одного абонента, с различными мнениями о том, хорошо ли пустые наборы или нет.
gnasher729
2

Есть два подхода, чтобы решить, если нет очевидного ответа:

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

  • Добавьте «строгий» логический параметр, чтобы указать, хотите ли вы, чтобы параметры были строго проверены или нет. Например, в Java SimpleDateFormatесть setLenientметод для попытки анализа входных данных, которые не полностью соответствуют формату. Конечно, вам придется решить, что по умолчанию.

JollyJoker
источник
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;
}

Но на самом деле это не очень хорошая идея, теперь у нас есть недопустимое состояние, которое может возникать во время выполнения в случайные моменты времени, однако это подразумевается в проблеме, поэтому мы не можем ее удалить, конечно, мы можем переместить исключение в некоторый служебный метод (это точно один и тот же код, перемещенный в разные места), но это неверно, и в основном лучшее, что вы можете сделать, - это придерживаться обычных установленных правил .

Фактически добавление новой сложности просто для того, чтобы показать, что вы можете писать методы запросов linq, кажется, не стоит вашей проблемы , я уверен, что если OP сможет рассказать нам больше о своем домене, возможно, мы сможем найти место, где действительно необходимо исключение (если вообще, возможно, проблема вообще не требует никаких исключений).

Разработчик игр
источник
Проблема в том, что вы используете математически определенные объекты для решения проблемы, но сама проблема, возможно, не требует математически определенного объекта в качестве ответа (на самом деле здесь возвращается список). Все зависит от более высокого уровня проблемы, независимо от того, как вы ее решаете, это называется инкапсуляция / сокрытие информации.
GameDeveloper
Причина, по которой ваш «SomeMethod» больше не должен вести себя как набор, заключается в том, что вы запрашиваете часть информации «вне наборов», особенно посмотрите на комментируемую часть «&& someInputSet.NotEmpty ()»
GameDeveloper
1
НЕТ! Вы не должны бросать исключения в своем классе женщин. Вы должны использовать шаблон Builder, который гарантирует, что вы даже не можете получить экземпляр объекта, FemaleClassесли у него нет только самок (он не доступен для публичного использования, только класс Builder может раздавать экземпляр), и, возможно, в нем есть как минимум одна женщина. , Тогда ваш код повсюду, который принимает FemaleClassв качестве аргумента, не должен обрабатывать исключения, потому что объект находится в неправильном состоянии.
ErikE
Вы предполагаете, что класс настолько прост, что вы можете реализовать его предварительные условия просто конструктором в реальности. Ваш аргумент неверен. Если вы запрещаете больше не иметь девочек, то или у вас не может быть метода для удаления учеников из класса, или ваш метод должен вызвать исключение, когда остался 1 студент. Вы просто перенесли мою проблему в другое место. Дело в том, что всегда будет какое-то предусловие / состояние функции, которое не может быть поддержано «по замыслу» и что пользователь собирается нарушить: вот почему существуют исключения! Это довольно теоретический материал, я надеюсь, что отрицательное мнение не ваше.
GameDeveloper
Понижение не мое, но если бы это было так, я бы гордился этим. Я осторожно, но с убеждением. Если правило вашего класса таково, что в нем ДОЛЖЕН быть элемент, и все элементы должны соответствовать определенному условию, то позволить классу находиться в несогласованном состоянии - плохая идея. Перемещение проблемы в другое место - это как раз мое намерение! Я хочу, чтобы проблема возникала во время строительства, а не во время использования. Смысл использования класса Builder вместо создания конструктора состоит в том, что вы можете создать очень сложное состояние из множества частей и частей из-за несоответствий.
ErikE