Должны ли мы разрешать нулевые/пустые параметры?

Недавно я обсуждал с коллегой вопрос о том, следует ли разрешать пустые или пустые коллекции, которые должны передаваться в качестве параметров метода. Я чувствую, что это должно вызвать исключение, поскольку оно нарушает метод "контракт", даже если он не обязательно нарушает выполнение метода. Это также имеет то преимущество, что "быстро не работает". Мой коллега утверждает, что это приводит к засориванию кода с помощью проверок "не null/not empty", даже если это не имеет особого значения.

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

Возьмем два конкретных примера:

1) Учитывая, что у нас есть класс Interval с методом перекрытий (Interval), что должно произойти, если в качестве параметра передан null? Я чувствую, что мы должны бросить исключение IllegalArgumentException, чтобы позвонивший знал, что что-то, вероятно, ошибочно, но мой коллега чувствует, что возвращает false, достаточно, как в сценариях, где он его использует, просто неважно, будет ли второй интервал null или нет (все, что имеет значение, касается того, перекрываются ли они).

2) Учитывая такой метод, как fetchByIds (Идентификаторы коллекции), что должно произойти, если предоставляется пустая коллекция? Еще раз я хочу предупредить вызывающего, что происходит что-то ненормальное, но мой коллега в порядке, просто получая пустой список, так как еще раз ему все равно, есть ли какие-либо идентификаторы или нет.

В чем заканчивается ответственность вызываемого кода? В обоих случаях вызывающий код не возражал против того, был ли параметр пустым или пустым, но в других сценариях это может указывать на вероятную ошибку. Должен ли метод гарантировать, что он не сломается до тех пор, пока будут соблюдаться предварительные условия или он также попытается определить потенциальные вызовы с ошибкой?

РЕДАКТОР: Я вижу много хороших ответов, и большинство из них склонны утверждать это как контракт/в документации и придерживаться его, но я бы хотел, чтобы ваше мнение о том, когда разрешить его, а когда нет (если когда-либо), В конкретных примерах, что бы вы сделали? Учитывая, что для 90% использования не будет проверять входные данные, вы по-прежнему будете проверять, чтобы сбросить ошибки в оставшихся 10%, или вы скорее рассмотрите их по мере их появления и избегаете ненужных проверок null/empty?

Ответ 1

Ваши дела - это две разные ситуации, которые хорошо смотрятся в реальном мире:

1) Учитывая, что у нас есть класс Interval с методом перекрытий (интервал) что должно произойти, если null как параметр? Я чувствую, что мы должны бросить IllegalArgumentException, чтобы звонивший знает что-то, вероятно, неправильно, но мой коллега чувствует возвращение false достаточно...

Передача null здесь похожа на вопрос "Какая разница между утиной?", на которую вы не можете ответить, потому что там отсутствует информация. Вы не можете пнуть и сказать "нет никакой разницы", потому что вы не знаете, является ли недостающая информация другой утиной (без разницы) или водным буйволом (большая разница). Если в контракте предусмотрено, что вызывающий должен предоставить что-то для сравнения, и вызывающий абонент не задерживал его окончание сделки, это хорошая причина, поскольку любой может исключить исключение.

2) Для такого метода, как fetchByIds (Идентификаторы коллекции), что должно происходить, если пустая коллекция предоставляется?

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

Если метод гарантирует только его не будет ломаться, пока соблюдаются предпосылки или если он попытается определить потенциал багги-вызовы?

Как говорили другие, метод должен гарантировать, что он будет вести себя, однако в его документации говорится, что он будет вести себя. Если в контракте указано, что метод вернет определенный результат при наличии аргумента null, он будет до вызывающего, чтобы убедиться, что он знает, что он передает null и сможет обрабатывать результаты.

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

В тех случаях, когда вы предпочитаете исключать исключение (например, возвращая false для overlaps(null)), у вас всегда есть возможность регистрировать тот факт, что вы видели что-то сомнительное вместе с любой другой имеющейся у вас информацией (стек след и т.д.). Это дает вам возможность работать с ним вне диапазона, чтобы программа продолжала функционировать.

Ответ 2

Основной недостаток с многочисленными основными языками -— включая Java, С# и большинство языков сценариев -— это невозможность сказать: "Должен передать значение здесь!" Я смутно вспоминаю интервью с Хейльсбергом, в котором он даже признался, что это было одно, о чем он сожалел о дизайне С# (пропуская константу - еще одно бедствие, ИМО, но не отвлекся).

Эта муха в мазке - причина, по которой вы и ваш коллега так рассердились на этот вопрос. Для этого нет решения (кроме того, если разработчики языка могут как-то модифицировать исправление). Любой выбор предполагает компромисс. Как только вы оба поймете это, тем скорее вы сможете расслабиться и просто выбрать то, что вызывает наименьшее количество боли.

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

Ответ 3

Это действительно проблема контекста, и нет хорошего ответа. Я бы выбрал принцип "наименьшего количества сюрпризов". Рассмотрим людей, использующих код:

  • если код должен использоваться внутри, подумайте о команде, которая будет ее использовать: знают ли они, как обрабатывать потенциальные исключения? делают ли рамки удобной обработку исключений? использует ли команда? Если вы беспокоитесь только об этих проблемах, возможно, вам лучше свернуть с ним и обработать пустые параметры/списки

  • Если вы создаете API, вы в значительной степени ответственны, но вы должны быть последовательными. Если некоторые методы допускают нулевые параметры, а другие - нет, это может быть признаком плохой конструкции.

  • если вы создаете код для взаимодействия с пользовательским интерфейсом, убедитесь, что пользовательский интерфейс имеет способы отбросить ошибку до чего-то понятного и убедиться, что разработчик пользовательского интерфейса знает, чего ожидать. Если они не отключили кнопку "Отправить пользователям", когда список пользователей пуст, как они должны его обрабатывать?

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

Ответ 4

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

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

Ответ 5

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

Например, если у вас есть метод, называемый PrintList(), и у вас есть аргумент, который может быть пустым, отвечает ли этот метод за получение списка, а затем распечатывает его или он должен его печатать? Возможно, имя метода должно быть изменено, чтобы включить эту ответственность: GetAndPrintList().

Также спросите себя, что произойдет, если новый член добавится в команду: найдет ли он код легко понятным и прочитанным без дополнительной документации?