Я занимаюсь разработкой библиотеки, предназначенной для публичного выпуска. Он содержит различные методы для работы с наборами объектов - генерация, проверка, разбиение и проецирование наборов в новые формы. Если это уместно, это библиотека классов 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 и он спокойно возвращает пустой набор, то вы можете столкнуться с проблемами через несколько шагов или оказаться с пустым набор результатов, и может не быть очевидным, как это произошло, учитывая, что у вас определенно было что-то во входном наборе.
Чего бы вы ожидали, и каков ваш аргумент для этого?
источник
Ответы:
Вернуть пустой набор
Я ожидал бы пустой набор, потому что:
Есть 0 комбинаций из 4 чисел из набора 3, когда я могу использовать каждый номер только один раз
источник
Если есть сомнения, спросите кого-нибудь еще.
Ваш пример функция имеет очень похожие один в Python:
itertools.combinations
. Давайте посмотрим, как это работает:И это прекрасно для меня. Я ожидал результата, который мог бы повторить, и я получил его.
Но, очевидно, если бы вы спросили что-то глупое:
Так что я бы сказал, что если все ваши параметры проверены, но результатом является пустой набор, возвращающий пустой набор, вы не единственный, кто делает это.
Как сказал @Bakuriu в комментариях, то же самое относится и к
SQL
запросу типаSELECT <columns> FROM <table> WHERE <conditions>
. Пока<columns>
,<table>
,<conditions>
хорошо сформированы и формируются ссылки на существующие имена, вы можете создать набор условий , которые исключают друг друга. Результирующий запрос просто не даст никаких строк вместо броскаInvalidConditionsError
.источник
n
элементы в коллекции, чтобы подсчитать количество способов их выбора. Если в какой-то момент при выборе элементов не осталось элементов, то нет способа выбораn
элементов из указанной коллекции (0) .1.0 / 0
, Python не позволит.SELECT
запрос к таблице создает исключение, когда набор результатов пуст? (спойлер: нет!). «Правильность» запроса зависит только от самого запроса и некоторых метаданных (например, существует ли таблица и имеет ли это поле (с этим типом)?) Как только запрос сформирован правильно и вы выполните его, вы ожидаете либо (возможно пустой) допустимый результат или исключение, потому что у «сервера» возникла проблема (например, произошел сбой сервера БД или что-то в этом роде).С точки зрения непрофессионала:
источник
throw
или должен вернуть пустой набор ...Я согласен с ответом Эвана, но хочу добавить конкретную аргументацию.
Вы имеете дело с математическими операциями, поэтому может быть хорошим советом придерживаться тех же математических определений. С математической точки зрения число r- множеств n- множества (т. Е. NCr ) хорошо определено для всех r> n> = 0. Оно равно нулю. Поэтому возвращение пустого набора было бы ожидаемым случаем с математической точки зрения.
источник
Я нахожу хороший способ определить, следует ли использовать исключение, это представить себе людей, вовлеченных в транзакцию.
Взяв в качестве примера извлечение содержимого файла:
Пожалуйста, принесите мне содержимое файла, "не существует. Текст"
а. «Вот содержимое: пустая коллекция символов»
б. «Хм, проблема в том, что этот файл не существует. Я не знаю, что делать!»
Пожалуйста, извлеките мне содержимое файла, "существует, но является пустым.txt"
а. «Вот содержимое: пустая коллекция символов»
б. «Хм, есть проблема, в этом файле ничего нет. Я не знаю, что делать!»
Без сомнения, некоторые не согласятся, но для большинства людей «Эрм, есть проблема» имеет смысл, когда файл не существует, и возвращает «пустую коллекцию символов», когда файл пуст.
Таким образом, применяя тот же подход к вашему примеру:
Пожалуйста, дайте мне все комбинации из 4 предметов для
{1, 2, 3}
а. Их нет, вот пустой набор.
б. Есть проблема, я не знаю, что делать.
Опять же, «есть проблема» будет иметь смысл, если, например, они
null
будут предложены в качестве набора элементов, но «вот пустой набор» кажется разумным ответом на вышеуказанный запрос.Если возвращение пустого значения маскирует проблему (например, отсутствующий файл, a
null
), то вместо этого обычно следует использовать исключение (если выбранный вами язык не поддерживаетoption/maybe
типы, тогда они иногда имеют больше смысла). В противном случае возврат пустого значения, вероятно, упростит стоимость и будет лучше соответствовать принципу наименьшего удивления.источник
Поскольку это для библиотеки общего назначения, мой инстинкт был бы Пусть конечный пользователь выбирает.
Так же, как мы имеем
Parse()
иTryParse()
доступны для нас, у нас может быть опция, которую мы используем в зависимости от того, какой вывод нам нужен из функции. Вы бы потратили меньше времени на написание и поддержание оболочки функции, чтобы вызвать исключение, чем спорить о выборе одной версии функции.источник
Single()
иSingleOrDefault()
сразу возникли на ум. Single генерирует исключение, если результат равен нулю или> 1, в то время как SingleOrDefault не выбрасывает, а вместо этого возвращаетdefault(T)
. Возможно, ОП мог бы использоватьCombinationsOf()
иCombinationsOfOrThrow()
.Single
иSingleOrDefault
(илиFirst
иFirstOrDefault
) - это совершенно другая история, @RubberDuck. Собираю мой пример из моего предыдущего комментария. Это прекрасно, чтобы AnSer ни на запрос «Что даже примирует больше , чем два существуют?» , так как это математически обоснованный и разумный ответ. Во всяком случае, если вы спрашиваете: «Какое первое четное простое больше двух?» нет естественного ответа. Вы просто не можете ответить на вопрос, потому что вы просите одно число. Это не ноль (это не одно число, а множество), а не 0. Поэтому мы бросаем исключение.Вам необходимо проверить аргументы, предоставленные при вызове вашей функции. И на самом деле, вы хотите знать, как обрабатывать недопустимые аргументы. Тот факт, что несколько аргументов зависят друг от друга, не компенсирует тот факт, что вы проверяете аргументы.
Таким образом, я бы проголосовал за ArgumentException, предоставив необходимую информацию для пользователя, чтобы понять, что пошло не так.
В качестве примера, проверьте
public static TSource ElementAt<TSource>(this IEnumerable<TSource>, Int32)
функцию в Linq. Который выдает исключение ArgumentOutOfRangeException, если индекс меньше 0 или больше или равен количеству элементов в источнике. Таким образом, индекс проверяется в отношении перечисляемого, предоставленного вызывающей стороной.источник
new[] { 1, 2, 3 }.Skip(4).ToList();
получаете пустой набор, это заставляет меня думать, что возвращение пустого набора, возможно, является лучшим вариантом здесь.Вы должны выполнить одно из следующих действий (хотя продолжайте последовательно бросать вызов базовым проблемам, таким как отрицательное количество комбинаций):
Предоставьте две реализации, одну, которая возвращает пустой набор, когда входные данные вместе бессмысленны, и одну, которая выбрасывает. Попробуйте позвонить им
CombinationsOf
иCombinationsOfWithInputCheck
. Или как угодно. Вы можете изменить это так, что проверяющий ввод - это более короткое имя, а список - одинCombinationsOfAllowInconsistentParameters
.Для методов Linq возвращайте пустое значение
IEnumerable
в том виде, в котором вы указали. Затем добавьте эти методы Linq в вашу библиотеку:Наконец, используйте это так:
или вот так:
Обратите внимание, что закрытый метод, отличный от публичного, необходим для того, чтобы поведение throw или action происходило при создании цепочки linq, а не через некоторое время при ее перечислении. Вы хотите, чтобы это выбросило прямо сейчас.
Однако обратите внимание, что, конечно, он должен перечислять по крайней мере первый элемент, чтобы определить, есть ли какие-либо элементы. Это потенциальный недостаток, который, я думаю, в основном смягчается тем фактом, что любые будущие зрители могут довольно легко обосновать, что
ThrowIfEmpty
метод должен перечислить хотя бы один элемент, поэтому его это не должно удивлять. Но ты никогда не знаешь. Вы могли бы сделать это более явнымThrowIfEmptyByEnumeratingAndReEmittingFirstItem
. Но это похоже на гигантское излишество.Я думаю, что # 2 довольно, ну, круто! Теперь сила в вызывающем коде, и следующий читатель кода точно поймет, что он делает, и ему не придется иметь дело с неожиданными исключениями.
источник
Я вижу аргументы для обоих вариантов использования - исключение велико, если нижестоящий код ожидает наборы, которые содержат данные. С другой стороны, просто пустой набор отлично подходит, если это ожидается.
Я думаю, это зависит от ожиданий вызывающего абонента, если это ошибка или приемлемый результат - поэтому я перенесу выбор вызывающему. Может быть, ввести вариант?
.CombinationsOf(4, CombinationsGenerationMode.Distinct, Options.AllowEmptySets)
источник
Есть два подхода, чтобы решить, если нет очевидного ответа:
Напишите код, предполагая сначала одну опцию, а затем другую. Подумайте, какой из них будет работать лучше всего на практике.
Добавьте «строгий» логический параметр, чтобы указать, хотите ли вы, чтобы параметры были строго проверены или нет. Например, в Java
SimpleDateFormat
естьsetLenient
метод для попытки анализа входных данных, которые не полностью соответствуют формату. Конечно, вам придется решить, что по умолчанию.источник
Основываясь на вашем собственном анализе, возвращение пустого набора кажется совершенно правильным - вы даже определили его как то, что некоторые пользователи могут на самом деле хотеть, и не попали в ловушку запрета на какое-либо использование, потому что вы не можете представить, что пользователи когда-либо захотят его использовать туда.
Если вы действительно чувствуете, что некоторые пользователи могут захотеть вызвать непустую отдачу, то дайте им способ попросить об этом поведении, а не навязывать его всем. Например, вы можете:
AssertNonempty
чек, который они могут положить в свои цепи.источник
Это действительно зависит от того, что ожидают получить ваши пользователи. Для (несколько не связанного) примера, если ваш код выполняет деление, вы можете либо сгенерировать исключение, либо вернуть,
Inf
илиNaN
когда вы делите на ноль. Однако, ни то, ни другоеInf
в библиотеку Python, люди будут нападать на вас за сокрытие ошибокВ вашем случае я бы выбрал решение, которое будет наименее удивительным для конечных пользователей. Поскольку вы разрабатываете библиотеку, имеющую дело с наборами, пустой набор кажется тем, с чем ваши пользователи ожидают иметь дело, поэтому возвращение звучит как разумная вещь. Но я могу ошибаться: вы понимаете контекст намного лучше, чем кто-либо еще здесь, поэтому, если вы ожидаете, что ваши пользователи будут полагаться на то, что набор всегда будет не пустым, вы должны сразу выбросить исключение.
Решения, которые позволяют пользователю выбирать (например, добавление «строгого» параметра), не являются окончательными, поскольку они заменяют исходный вопрос новым эквивалентным: «Какое значение
strict
должно быть значением по умолчанию?»источник
В математике распространено мнение, что когда вы выбираете элементы над набором, вы не можете найти элемент и, следовательно, получаете пустой набор . Конечно, вы должны быть согласны с математикой, если вы идете по этому пути:
Общие правила набора:
Ваш вопрос очень тонкий:
Может случиться так, что ввод вашей функции должен соответствовать контракту : в этом случае любой неверный ввод должен вызывать исключение, вот и все, функция не работает с обычными параметрами.
Может случиться так, что ввод вашей функции должен вести себя точно так же, как набор , и поэтому должен иметь возможность возвращать пустой набор.
Теперь, если бы я был в тебе, я бы пошел «Сет», но с большим «НО».
Предположим, у вас есть коллекция, в которой «по гипотезе» должны быть только ученицы:
Теперь ваша коллекция больше не является «чистым набором», потому что у вас есть контракт на нее, и, следовательно, вы должны принудительно применять свой контракт с исключением.
Когда вы используете свои «set» функции в чистом виде, вы не должны генерировать исключения в случае пустого набора, но если у вас есть коллекции, которые больше не являются «чистыми наборами», тогда вы должны генерировать исключения там, где это необходимо .
Вы всегда должны делать то, что кажется более естественным и последовательным: для меня набор должен придерживаться правил набора, в то время как вещи, которые не являются наборами, должны иметь свои правильно продуманные правила.
В вашем случае это кажется хорошей идеей сделать:
Но на самом деле это не очень хорошая идея, теперь у нас есть недопустимое состояние, которое может возникать во время выполнения в случайные моменты времени, однако это подразумевается в проблеме, поэтому мы не можем ее удалить, конечно, мы можем переместить исключение в некоторый служебный метод (это точно один и тот же код, перемещенный в разные места), но это неверно, и в основном лучшее, что вы можете сделать, - это придерживаться обычных установленных правил .
Фактически добавление новой сложности просто для того, чтобы показать, что вы можете писать методы запросов linq, кажется, не стоит вашей проблемы , я уверен, что если OP сможет рассказать нам больше о своем домене, возможно, мы сможем найти место, где действительно необходимо исключение (если вообще, возможно, проблема вообще не требует никаких исключений).
источник
FemaleClass
если у него нет только самок (он не доступен для публичного использования, только класс Builder может раздавать экземпляр), и, возможно, в нем есть как минимум одна женщина. , Тогда ваш код повсюду, который принимаетFemaleClass
в качестве аргумента, не должен обрабатывать исключения, потому что объект находится в неправильном состоянии.