Лучше вернуть нулевую или пустую коллекцию?

Как вы уже отметили, fork () следует определить в unistd.h - по крайней мере, согласно страницам man, которые поставляются с Ubuntu 11.10. Минимальный:

#include <unistd.h>

int main( int argc, char* argv[])
{
    pid_t procID;

    procID = fork();
    return procID;
}

... строит без предупреждений в 11.10.

Говоря о том, какой дистрибутив UNIX / Linux вы используете? Например, я нашел несколько не замечательных функций, которые должны быть определены в заголовках Ubuntu 11.10, нет. Например:

// string.h
char* strtok_r( char* str, const char* delim, char** saveptr);
char* strdup( const char* const qString);

// stdio.h
int fileno( FILE* stream);

// time.h
int nanosleep( const struct timespec* req, struct timespec* rem);

// unistd.h
int getopt( int argc, char* const argv[], const char* optstring);
extern int opterr;
int usleep( unsigned int usec);

Пока они определены в вашей библиотеке C, это не будет большой проблемой. Просто определите свои собственные прототипы в заголовке совместимости и сообщите о стандартных проблемах с заголовками тем, кто поддерживает ваш дистрибутив ОС.

384
задан Mark Bell 28 June 2016 в 11:20
поделиться

14 ответов

Пустая коллекция. Всегда.

Это отстой:

if(myInstance.CollectionProperty != null)
{
  foreach(var item in myInstance.CollectionProperty)
    /* arrgh */
}

Считается лучшей практикой НИКОГДА не возвращать null при возврате коллекции или перечисляемых. ВСЕГДА возвращает пустое перечисление/сбор. Это предотвратит вышеупомянутую ерунду и предотвратит попадание вашего автомобиля в яйца сотрудников и пользователей ваших классов.

Говоря о свойствах, всегда устанавливайте свойство один раз и забудьте о нем

public List<Foo> Foos {public get; private set;}

public Bar() { Foos = new List<Foo>(); }

В .NET 4.6. 1, вы можете сгущать это довольно много:

public List<Foo> Foos { get; } = new List<Foo>();

Говоря о методах, возвращающих перечисления, вы можете легко вернуть пустое перечисление вместо null...

public IEnumerable<Foo> GetMyFoos()
{
  return InnerGetFoos() ?? Enumerable.Empty<Foo>();
}

Использование Enumerable.Empty() можно считать более эффективным, чем возврат, например, новой пустой коллекции или массива.

.
475
ответ дан 22 November 2019 в 23:42
поделиться

Есть еще один момент, который еще не упоминался. Рассмотрим следующий код:

    public static IEnumerable<string> GetFavoriteEmoSongs()
    {
        yield break;
    }

Язык C# при вызове этого метода вернет пустой числитель. Следовательно, чтобы быть связанным с дизайном языка (а значит, и ожиданиями программиста), должна быть возвращена пустая коллекция

.
36
ответ дан 22 November 2019 в 23:42
поделиться

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

.
2
ответ дан 22 November 2019 в 23:42
поделиться

Всегда думайте в пользу своих клиентов (которые используют api):

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

.
4
ответ дан 22 November 2019 в 23:42
поделиться

Я бы возразил, что null - это не то же самое, что пустая коллекция, и вы должны выбрать, какая из них наилучшим образом представляет то, что вы возвращаете. В большинстве случаев null - ничто (кроме как в SQL). Пустая коллекция - это что-то, хотя и пустое.

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

4
ответ дан 22 November 2019 в 23:42
поделиться

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

На самом деле, в этом случае я обычно предпочитаю бросать исключение, чтобы убедиться, что оно РЕАЛЬНО не проигнорировано :)

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

.
4
ответ дан 22 November 2019 в 23:42
поделиться

Можно утверждать, что аргументация Null Object Pattern аналогична аргументации в пользу возвращения пустой коллекции.

.
6
ответ дан 22 November 2019 в 23:42
поделиться

Из Framework Design Guidelines 2nd Edition (стр. 256):

НЕ возвращать нулевую величину из коллекторские свойства или методы возвращение коллекций. Вернуть пустые или пустой массив.

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

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

.
150
ответ дан 22 November 2019 в 23:42
поделиться

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

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

Меня часто разочаровывали системы, которые не могут различать нуль и нет ответа. У меня было несколько раз, когда система просила меня ввести какое-то число, я вводил ноль, и я получал сообщение об ошибке, говорящее, что я должен ввести значение в этом поле. Я только что это сделал: Я ввел ноль! Но он не примет ноль, потому что не может отличить его от отсутствия ответа.


Ответьте Сондерсу:

Да, я предполагаю, что есть разница между "Человек не ответил на вопрос" и "Ответ был нулевым". В этом был смысл последнего абзаца моего ответа. Многие программы не в состоянии отличить "не знаю" от пустого или нулевого, что, как мне кажется, является потенциально серьезным недостатком. Например, год или около того назад я покупал дом. Я зашел на сайт недвижимости и там было много домов с запрашиваемой ценой в $0. Звучало для меня довольно неплохо: Они раздают эти дома бесплатно! Но я уверен, что печальная реальность заключалась в том, что они просто не ввели цену. В таком случае вы можете сказать: "Ну, ОБЪЯВЛЕННЫЙ ноль означает, что они не ввели цену - никто не собирается отдавать дом бесплатно". Но на сайте также приведены средние цены спроса и продажи домов в разных городах. Не могу не задаться вопросом, не было ли в среднем нулей, что дает неправильно низкое среднее значение для некоторых мест, т.е. какова средняя цена в 100 000 долларов; 120 000 долларов; и "не знаю"? Технически ответ - "не знаю". То, что мы, вероятно, действительно хотим увидеть, это $110,000. Но то, что мы, вероятно, получим, это 73 333 доллара, что было бы совершенно неправильно. Кроме того, что если бы у нас была эта проблема на сайте, где пользователи могут делать онлайн-заказы? (Вряд ли для недвижимости, но я уверен, что вы видели, как это делается для многих других товаров). Действительно ли мы хотели бы, чтобы "цена еще не указана" интерпретировалась как "бесплатная"?

RE имеет две отдельные функции, "есть ли она?" и "если да, то что это?". Да, вы, конечно, могли бы это сделать, но зачем? Теперь вызывающей программе приходится делать два вызова вместо одного. Что произойдет, если программист не вызовет "любой?" и сразу перейдет к "что это?". ? Вернётся ли программа неверно введённый ноль? Бросить исключение? Вернет неопределенное значение? Это создает больше кода, больше работы и больше потенциальных ошибок.

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


Ответ на Jammycakes:

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

С нулевым возвратом:

HospList list=patient.getHospitalizationList(patientId);
if (list==null)
{
   // ... handle missing list ...
}
else
{
  for (HospEntry entry : list)
   //  ... do whatever ...
}

С отдельной функцией:

if (patient.hasHospitalizationList(patientId))
{
   // ... handle missing list ...
}
else
{
  HospList=patient.getHospitalizationList(patientId))
  for (HospEntry entry : list)
   // ... do whatever ...
}

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

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

Конечно, если вызывающий абонент не будет проверять на нулевой результат, то программа может выйти из строя с исключением из null-интерфейса. Но если есть отдельная функция "got any" и вызывающий не вызывает эту функцию, а слепо вызывает функцию "get list", то что произойдет? Если она бросит исключение или по каким-то другим причинам провалится, то это то же самое, что и то, что случилось бы, если бы она вернула ноль и не проверила ее. Если она вернет пустой список, это просто неправильно. Вы не можете отличить "У меня есть список с нулевыми элементами" от "У меня нет списка". Это все равно, что возвращать ноль по цене, когда пользователь не вводил никакой цены: это просто неправильно.

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

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

.
18
ответ дан 22 November 2019 в 23:42
поделиться

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

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

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

.
6
ответ дан 22 November 2019 в 23:42
поделиться

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

2
ответ дан 22 November 2019 в 23:42
поделиться

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

.
10
ответ дан 22 November 2019 в 23:42
поделиться

Пустота гораздо более удобна для потребителя.

Существует четкий метод составления пустого пересчета:

Enumerable.Empty<Element>()
30
ответ дан 22 November 2019 в 23:42
поделиться

Зависит от вашего контракта и вашего конкретного случая. Обычно лучше всего возвращать пустые коллекции , но иногда ( редко ):

  • null может означать что-то более конкретное;
  • ваш API (контракт) может заставить вас вернуть null.

Некоторые конкретные примеры:

  • компонент пользовательского интерфейса (из библиотеки вне вашего контроля), может быть отрисовывает пустую таблицу, если передана пустая коллекция, или вообще никакую таблицу, если передан ноль.
  • в объекте-XML (JSON/weverever), где null означал бы, что элемент отсутствует, в то время как пустая коллекция сделает избыточной (и, возможно, некорректной) <коллекцию />
  • <коллекцию/>
88
ответ дан 22 November 2019 в 23:42
поделиться
Другие вопросы по тегам:

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