нулевые объекты против пустых объектов - PullRequest
13 голосов
/ 27 октября 2009

[Это результат Рекомендация: Должны ли функции возвращать ноль или пустой объект? , но я пытаюсь быть очень общим. ]

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

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

В C # присутствует та же «проблема» C ++: желание проверить каждую неизвестную ссылку на null (ArgumentNullException) и быстро исправить NullReferenceException s, добавив проверку null.

Мне кажется, что один из способов предотвратить это - избегать пустых объектов, используя вместо этого пустые объекты (String.Empty, EventArgs.Empty). Другим было бы выбрасывать исключение, а не возвращать null.

Я только начинаю изучать F #, но кажется, что в этой среде гораздо меньше нулевых объектов. Так что, может быть, вам не нужно, чтобы вокруг было много null ссылок?

Я лаю не на том дереве?

Ответы [ 11 ]

25 голосов
/ 27 октября 2009

Передача ненулевого значения только для того, чтобы избежать исключения NullReferenceException, торгует простой и легко решаемой проблемой («она взрывается, потому что она нулевая») для гораздо более тонкой, трудной для отладки проблемы («что-то, вызываемое несколькими вызовами вниз по стеку ведет себя не так, как ожидалось, потому что намного раньше он получил некоторый объект, который не имеет значимой информации, но не является нулевым ").

NullReferenceException - это замечательная вещь ! Он терпит неудачу, громко, быстро и почти всегда быстро и легко идентифицировать и исправить. Это мое любимое исключение, потому что я знаю, что когда увижу это, моя задача займет около 2 минут. Сравните это с запутанным QA или отчетом клиента, пытающимся описать странное поведение, которое должно быть воспроизведено и прослежено до источника. Тьфу.

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

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

7 голосов
/ 27 октября 2009

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

Шаблон «Ввести нулевой объект» в рефакторинге Мартина Фаулера *1004* (стр. 260) также может быть полезен. Нулевой объект отвечает на все методы реального объекта, но способом, который «делает правильные вещи». Поэтому, вместо того, чтобы всегда проверять Order, чтобы увидеть, имеет ли order.getDiscountPolicy () значение NULL, убедитесь, что у Order есть NullDiscountPolicy в этих случаях. Это оптимизирует логику управления.

4 голосов
/ 27 октября 2009

Нуль получает мой голос. С другой стороны, я настроен «быстро и без сбоев».

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

3 голосов
/ 27 октября 2009

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

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

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

2 голосов
/ 27 октября 2009

Смысл null в том, что оно не имеет смысла. Это просто отсутствие объекта.

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

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

1 голос
/ 02 июня 2015

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

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

Если законно не возвращать ни одного объекта, но подходящих заменителей с точки зрения Null Object или Special Case нет, то я обычно использую функциональный тип Option - тогда я могу вернуть пустой параметр когда нет юридического результата. Затем клиент должен выяснить, как лучше всего обращаться с пустым параметром.

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

1 голос
/ 27 октября 2009

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

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

В вашем случае, и это не опыт Фаулера, эффективная идиома «пустой объект» возможна только в C ++ благодаря доступному механизму приведения (возможно, но не всегда с помощью доминирования ) .. С другой стороны, в вашем нулевом типе вы можете генерировать исключения и делать все, что захотите, при этом сохраняя чистый сайт вызовов и структуру кода.

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

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

Чтобы добавить, серебряной пули не существует, и сбой по нулевой ссылке или использование нулевой ссылки в управляемом пространстве, или выброс и исключение обработки исключения - идентичная проблема, несмотря на то, что мир управляемых и исключений попытается продать вас. Любая приличная среда предлагает защиту от них (черт возьми, вы можете установить любой фильтр на любую ОС, какую хотите, что еще, по вашему мнению, делают виртуальные машины), и существует так много других векторов атак, что этот был разбит на куски. Еще раз введите подтверждение x86 от Google, их собственный способ сделать намного быстрее и лучше «IL», «динамический» дружественный код и т. Д.

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

1 голос
/ 27 октября 2009

Если вы серьезно относитесь к желанию программировать в среде "null less", рассмотрите возможность использования методов расширения чаще, они неуязвимы для NullReferenceExceptions и, по крайней мере, "делают вид", что null больше не существует:

public static GetExtension(this string s)
{
    return (new FileInfo(s ?? "")).Extension;
}

, который можно назвать:

// this code will never throw, not even when somePath becomes null
string somePath = GetDataFromElseWhereCanBeNull();
textBoxExtension.Text = somePath.GetExtension(); 

Я знаю, это всего лишь удобство, и многие люди правильно считают это нарушением принципов ОО (хотя «основатель» ОО Бертран Мейер считает null злом и полностью изгнал его из своего ОО-проекта, который применяется к на эйфелевом языке, но это уже другая история). РЕДАКТИРОВАТЬ: Дэн упоминает, что Билл Вагнер ( Более эффективный C # ) считает это плохой практикой, и он прав. Вы когда-нибудь рассматривали метод расширения IsNull ;-)?

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

// load settings
WriteSettings(currentUser.Settings ?? new Settings());

// example of some readonly property
public string DisplayName
{
    get 
    {
         return (currentUser ?? User.Guest).DisplayName
    }
}

Ни один из них не берет случайную проверку на наличие null?? является не чем иным, как скрытой ветвью if). Я предпочитаю в своем коде как можно меньше null просто потому, что считаю, что это делает код более читабельным. Когда мой код перегружен операторами if для null, я знаю, что в дизайне что-то не так, и я занимаюсь рефакторингом. Я предлагаю любому сделать то же самое, но я знаю, что мнения по этому вопросу сильно различаются.

(обновление) Сравнение с исключениями

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

try 
{
    //...code here...
}
catch (Exception) {}

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

1 голос
/ 27 октября 2009

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

0 голосов
/ 27 октября 2009

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

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

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

...