Лучше ли возвращать NULL или пустые значения из функций /методов, где возвращаемое значение отсутствует?

Я ищу рекомендацию здесь. Я борюсь с тем, лучше ли возвращать NULL или пустое значение из метода, когда возвращаемое значение отсутствует или не может быть определено.

В качестве примера возьмем следующие два метода:

 string ReverseString(string stringToReverse) // takes a string and reverses it.
Person FindPerson(int personID)    // finds a Person with a matching personID.

В ReverseString(), я бы сказал, возвращать пустую строку, потому что возвращаемый тип является строкой, поэтому вызывающий абонент ожидает этого. Кроме того, вызывающему абоненту не нужно будет проверять, был ли возвращен NULL.

В FindPerson() возвращение NULL кажется более подходящим. Независимо от того, возвращается ли NULL или пустой объект Person (new Person()), вызывающему нужно будет проверить, не является ли объект Person NULL или пустым, прежде чем что-либо делать с ним ( например, вызов UpdateName()). Так почему бы просто не вернуть NULL здесь, а затем вызывающему только нужно проверить NULL.

Кто-нибудь еще борется с этим? Любая помощь или понимание оценены.

117 голосов | спросил P B 17 42011vEurope/Moscow11bEurope/MoscowThu, 17 Nov 2011 22:34:50 +0400 2011, 22:34:50

17 ответов


66

StackOverflow хорошо обсуждает эту точную тему в этом Q & . В рейтинге вопрос, kronoz отмечает:

  

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

     

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

     

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

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

Однако, как отметил плакат в ответе SO, нулевые значения, вероятно, должны быть возвращены, если ожидается объект, поэтому нет сомнений в том, возвращаются ли данные.

В конце концов, нет лучшего способа сделать что-то. Построение консенсуса в команде в конечном итоге приведет к лучшим практикам вашей команды.

ответил JW8 17 42011vEurope/Moscow11bEurope/MoscowThu, 17 Nov 2011 22:42:35 +0400 2011, 22:42:35
72

Во всем коде, который я пишу, я не возвращаю функцию null из функции. Я прочитал это в Очистить код .

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

В F # вы можете вернуть тип option, который может быть some(Person) или none, поэтому для вызывающего они должны проверить.

Аналогичным шаблоном C # (anti-) является метод Try...:

 public bool TryFindPerson(int personId, out Person result);

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

 class FindResult<T>
{
   public FindResult(bool found, T result)
   {
       this.Found = found;
       this.Result = result;
   }

   public bool Found { get; private set; }
   // Only valid if Found is true
   public T Result { get; private set;
}

public FindResult<Person> FindPerson(int personId);

... и, честно говоря, вы можете предположить, что каждый .NET-программист знает о шаблоне Try..., потому что он используется внутри .NET Framework. Это означает, что им не нужно читать документацию , чтобы понять, что она делает, что для меня важнее, чем придерживаться взглядов некоторых пуристов на функции (понимание того, что result параметр out, а не параметр ref).

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

Если, с другой стороны, нет логической причины, что вызывающий пользователь когда-либо предоставил personId, который не существовал, я бы, вероятно, сделал следующее:

 public Person GetPerson(int personId);

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

ответил Scott Whitlock 17 42011vEurope/Moscow11bEurope/MoscowThu, 17 Nov 2011 22:58:47 +0400 2011, 22:58:47
28

Вы можете попробовать шаблон специального случая от Paterns архитектуры корпоративных приложений :

  

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

     

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

     

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

     

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

ответил Thomas Owens 17 42011vEurope/Moscow11bEurope/MoscowThu, 17 Nov 2011 22:48:54 +0400 2011, 22:48:54
14

Я бы подумал, что ReverseString() вернет обратную строку и будет вызывать IllegalArgumentException, если он передан в Null.

Я думаю, что FindPerson() должен следовать шаблону NullObject или поднять непроверенное исключение из-за того, что вы не нашли что-то, если вы всегда сможете что-то найти.

Имея дело с Null - это то, чего следует избегать. Наличие Null в языках называется Billion Dollar Mistake своим изобретателем!

  

Я называю это своей ошибкой в ​​миллиард долларов. Это было изобретение нулевого   в 1965 году. В то время я разрабатывал первый   всеобъемлющая система типов для ссылок в объектно-ориентированном   языка (ALGOL W).

     

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

     

Это привело к бесчисленным ошибкам, уязвимостям и системе   аварии, которые, вероятно, вызвали миллиард долларов боли и   ущерб за последние сорок лет.

     

В последние годы ряд программных анализаторов, таких как PREfix и PREfast   в Microsoft были использованы для проверки ссылок и предупреждения, если   существует риск, что они могут быть не нулевыми. Более позднее программирование   Языки, такие как Spec #, ввели объявления для непустых   Рекомендации. Это решение, которое я отклонил в 1965 году.

     

Тони Хоар

ответил Jarrod Roberson 18 52011vEurope/Moscow11bEurope/MoscowFri, 18 Nov 2011 00:24:33 +0400 2011, 00:24:33
11

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

ответил hugomg 18 52011vEurope/Moscow11bEurope/MoscowFri, 18 Nov 2011 08:10:58 +0400 2011, 08:10:58
7

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

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

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

Теперь рассмотрим альтернативу, в которой вы вернете действительный объект Person ..., в котором нет ничего. Вы помещаете нули во все свои значения (имя, адрес, favoriteDrink), или теперь вы заполняете те, у которых есть действительные, но пустые объекты? Как ваш клиент теперь определяет, что фактическое лицо не найдено? Нужно ли проверять, является ли имя пустой строкой вместо нуля? Разве это не значит, что на самом деле это приведет к тому, что слишком много или более беспорядочных операторов и условных выражений, чем если бы мы просто проверяли значение null и перемещались?

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

ответил RonU 18 52011vEurope/Moscow11bEurope/MoscowFri, 18 Nov 2011 01:12:19 +0400 2011, 01:12:19
6

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

Мне сложно разобраться, как использовать этот код, только чтобы он не срабатывал:

for thing in get_list_of_things() {
    do_something_clever(thing)
}

Мне не нужно добавлять специальный случай для случая, когда нет вещей .

ответил Bryan Oakley 17 42011vEurope/Moscow11bEurope/MoscowThu, 17 Nov 2011 22:43:01 +0400 2011, 22:43:01
5

Это зависит от семантики метода. Вы можете указать, ваш метод принимает только «Не null». В Java вы можете объявить, что используя некоторые аннотации метаданных:

 string ReverseString(@NotNull String stringToReverse)

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

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

 Person FindPerson(int personID)

Если вы ищете человека по некоторому догадку id (что кажется мне странным), вы бы лучше объявили этот метод как @Nullable.

ответил Martin Vejmelka 17 42011vEurope/Moscow11bEurope/MoscowThu, 17 Nov 2011 22:44:13 +0400 2011, 22:44:13
3

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

if (someObject != null && someObject.someMethod () != whateverValue)

Например, если метод возвращает коллекцию объектов, которые соответствуют некоторому шаблону, то возврат пустой коллекции имеет больше смысла, чем возврат null, и повторение этой пустой коллекции будет практически неэффективным , Другим случаем был бы метод, возвращающий экземпляр класса, используемый для записи данных, и, по-моему, предпочтительнее вернуть объект Null, а не null, поскольку он не заставляет пользователей всегда проверять, является ли возвращенная ссылка null.

В случаях, когда возвращение null имеет смысл (например, вызов метода findPerson ()), я попытался бы хотя бы предоставить метод, который возвращает, если объект (personExists (int personId)), другим примером может быть метод containsKey () на Map в Java). Это делает код вызывающих более чистым, так как вы можете легко увидеть, что существует вероятность того, что желаемый объект может быть недоступен (человек не существует, ключ отсутствует на карте). Постоянно проверяя, является ли ссылка null, смущает код, на мой взгляд.

ответил Laf 18 52011vEurope/Moscow11bEurope/MoscowFri, 18 Nov 2011 02:49:32 +0400 2011, 02:49:32
3

null - лучшая вещь для возврата, если и только если применяются следующие условия:

  • нулевой результат ожидается при нормальной работе . Можно было ожидать, что вы не сможете найти человека в некоторых разумных обстоятельствах, поэтому findPerson () возвращает null в порядке. Однако, если это действительно неожиданный сбой (например, в функции, называемой saveMyImportantData ()), вы должны выбросить исключение. В названии есть намек - Исключения для исключительных обстоятельств!
  • null используется для обозначения "not found /no value" . Если вы имеете в виду что-то еще, то верните что-то еще! Хорошими примерами были бы операции с плавающей запятой, возвращающие Infinity или NaN - это значения со специфическими значениями, поэтому было бы очень сложно, если бы вы вернули здесь null.
  • Функция предназначена для возврата одного значения , например findPerson (). Если он был предназначен для возврата коллекции, например. findAllOldPeople (), тогда пустая коллекция лучше всего. Следствием этого является то, что функция, возвращающая коллекцию , никогда не должна возвращать null .

Кроме того, убедитесь, что документируйте факт , что функция может вернуть значение null.

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

Наконец, если вы примените эти правила к двум функциям, перечисленным в вопросе:

  • FindPerson - было бы уместно, чтобы эта функция возвращала значение null, если человек не был найден.
  • ReverseString - похоже, что он никогда не сможет найти результат, если передать строку (так как все строки могут быть отменены, включая пустую строку). Поэтому он никогда не должен возвращать null. Если что-то пойдет не так (из памяти?), Тогда оно должно вызвать исключение.
ответил mikera 12 Jam1000000amThu, 12 Jan 2012 09:59:39 +040012 2012, 09:59:39
1

По-моему, существует разница между возвратом NULL, возвратом некоторого пустого результата (например, пустой строки или пустого списка) и выбросом исключения.

Обычно я использую следующий подход. Я рассматриваю функцию или метод f (v1, ..., vn) как применение функции

f : S x T1 x ... x Tn -> T

где S это «состояние мира» T1, ..., Tn - типы входных параметров, а T - тип возврата.

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

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

Я предпочитаю использовать исключения для ошибок, которые не позволяют выполнить вычисление (т. е. не удалось найти ответ).

Например, предположим, что у меня есть класс Customer, и я хочу реализовать метод

 Customer findCustomer(String customerCode)

для поиска клиента в базе данных приложения по его коду. В этом методе я бы

  • Возвращает объект класса Customer, если запрос успешный,
  • Возвращает null, если запрос не находит клиента.
  • Выбросить исключение, если невозможно подключиться к базе данных.

Дополнительные проверки для null, например

 Customer customer = findCustomer("...");
if (customer != null && customer.getOrders() > 0)
{
    ...
}

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

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

Я бы также подумал об использовании шаблона Null Object (как предложено Laf), если я могу отличить нулевой объект класса от всех других объектов.

ответил Giorgio 18 52011vEurope/Moscow11bEurope/MoscowFri, 18 Nov 2011 16:56:27 +0400 2011, 16:56:27
0

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

ответил Phani 18 52011vEurope/Moscow11bEurope/MoscowFri, 18 Nov 2011 13:50:28 +0400 2011, 13:50:28
0

Для меня здесь два случая. Если вы возвращаете какой-то список, вы всегда должны возвращать список независимо от того, пустым, если вам нечего в него вставлять.

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

Если вы возвращаете нулевой объект, а вызывающему нужно реальный объект, он может пойти дальше и попытаться использовать нулевой объект, создающий неожиданное поведение. Я думаю, что лучше, если рутина начнет бум, если они забудут разобраться с ситуацией. Если что-то не так, я хочу исключение ASAP. Вы вряд ли отправите ошибку таким образом.

ответил Loren Pechtel 6 Mayam12 2012, 00:37:59
0

Добавление к тому, что уже говорили о конструкторе типа Maybe:

Еще один большой выигрыш, помимо риска для NPE, является семантическим. В функциональном мире, где мы имеем дело с чистыми функциями, нам нравится пытаться создавать функции, которые при применении в точности эквивалентны их возвращаемому значению . Рассмотрим случай String.reverse(): это функция, которая задает определенную строку, представляет собой обратную версию этой строки. Мы знаем, что эта строка существует, потому что каждая строка может быть отменена (строки являются в основном упорядоченными наборами символов).

Теперь, что насчет findCustomer(int id)? Эта функция представляет клиента, у которого есть данный идентификатор. Это то, что может или не может существовать. Но помните, что функции имеют одно возвращаемое значение, поэтому вы не можете сказать «эта функция возвращает клиента с данным ID OR , он возвращает null.

Это моя главная проблема как с возвратом null, так и с возвратом нулевого объекта. Они вводят в заблуждение. null не является клиентом, и на самом деле это не «отсутствие клиента». Это отсутствие НИЧЕГО. Это просто бессмысленный указатель на НИЧЕГО. Очень низкоуровневый и очень уродливый и очень подверженный ошибкам. Я думаю, что нулевой объект тоже довольно вводит в заблуждение. Нулевой клиент - клиент. Это не отсутствие одного, это не клиент с запросом идентификатора, поэтому возвращение его просто НЕПРАВИЛЬНО. Это НЕ клиент, которого я просил, но все же вы утверждаете, что вернули клиента. У него неправильный идентификатор, очевидно, это ошибка. Он нарушает контракт, предложенный именем метода и подписью. Это требует, чтобы клиентский код принимал какие-либо сведения о вашем дизайне или читал документацию.

Обе эти проблемы решаются с помощью Maybe Customer. Внезапно мы не говорим, что «эта функция представляет клиента с данным идентификатором». Мы говорим: «Эта функция представляет клиента с данным ID , если он существует . Теперь очень ясно и ясно, что он делает. Если вы попросите клиента с несуществующим ID, вы получит Nothing .Как это лучше, чем null? Не только для риска NPE. Но также потому, что тип Nothing не просто Maybe .Это Maybe Customer. Он имеет смысловое значение. Это означает «отсутствие клиента», что указывает на то, что такого клиента не существует.

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

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

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

ответил kai 29 Mayam16 2016, 09:02:28
0

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

2) Хорошо использовать функции, которые семантически всегда возвращают что-то (или бросают).

С logger имеет смысл вернуть журнал, который не регистрируется, если не определен журнал. При возврате коллекции почти никогда не имеет смысла (за исключением «достаточно низкого уровня», где сама коллекция - это данные, а не то, что она содержит), чтобы ничего не возвращать, потому что пустой набор - это набор, а не ничего.

С примером person я бы пошел «спящим» способом (получить vs load, IIRC, но там сложнее прокси-объекты для ленивой загрузки) наличия двух функций, которые возвращают нулевой и второй, который бросает.

ответил user470365 18 52011vEurope/Moscow11bEurope/MoscowFri, 18 Nov 2011 13:30:15 +0400 2011, 13:30:15
-1
  • NULL должен быть возвращен, если приложение ожидает, что данные будут доступны, но данные недоступны. Например, служба, которая возвращает CITY на основе zipcode, должна вернуть значение null, если город не найден. Затем вызывающий может решить обработать нуль или взорвать.

  • Пустой список должен быть возвращен, если есть две возможности. Данные доступных или НЕТ данных. Например, служба, которая возвращает ГОРОД (ы), если население больше определенного числа. Оно может возвращает пустой список, если нет данных, удовлетворяющих заданным критериям.

ответил java_mouse 4 Maypm12 2012, 19:40:59
-2

Возвращаемый NULL - это ужасный дизайн , в объектно-ориентированном мире. Вкратце, использование NULL приводит к:

  • обработка ошибок ad-hoc (вместо исключений)
  • неоднозначная семантика
  • медленный, а не быстрый сбой
  • компьютерное мышление против объектного мышления
  • изменяемые и неполные объекты

Проверьте это сообщение в блоге для подробного объяснения: http : //www.yegor256.com/2014/05/13/why-null-is-bad.html

ответил yegor256 13 Mayam14 2014, 11:57:25

Похожие вопросы

Популярные теги

security × 330linux × 316macos × 2827 × 268performance × 244command-line × 241sql-server × 235joomla-3.x × 222java × 189c++ × 186windows × 180cisco × 168bash × 158c# × 142gmail × 139arduino-uno × 139javascript × 134ssh × 133seo × 132mysql × 132