Null объектов против пустых объектов

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

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

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

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

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

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

Я лаяю здесь неправильное дерево?

Ответы

Ответ 1

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

Образец "Введение нулевого объекта" в Martin Fowler Рефакторинг (стр. 260) также может быть полезен. Объект Null отвечает всем методам реального объекта, но таким образом, что "делает правильную вещь". Поэтому, а не всегда проверяйте ордер, чтобы увидеть, является ли order.getDiscountPolicy() NULL, убедитесь, что в этом случае Order имеет NullDiscountPolicy. Это упрощает логику управления.

Ответ 2

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

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

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

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

Ответ 3

Нуль получает мой голос. Опять же, я из "неудачного" мышления.

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

Ответ 4

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

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

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

Ответ 5

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

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

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

Ответ 6

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

Ответ 7

Если вы серьезно относитесь к желанию программировать в среде "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(); 

Я знаю, это только удобство, и многие люди правильно считают это нарушением принципов OO (хотя "основатель" OO, Бертран Мейер, считает "t20" злом и полностью изгнал его из своего проекта OO, который применяется к Эйфелевой язык, но это другая история). EDIT: Дэн упоминает, что Билл Вагнер (более эффективный С#) считает это плохой практикой, и он прав. Когда-либо рассматривался метод расширения IsNull;-)?

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

// 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-statements для null, я знаю, что что-то не так в дизайне и рефакторе. Я предлагаю всем сделать то же самое, но я знаю, что мнения сильно различаются по этому вопросу.

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

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

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

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

Ответ 8

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

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

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

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

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

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

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

Ответ 9

Вы не можете всегда возвращать пустой объект, потому что 'empty' не всегда определяется. Например, что означает, что для int, float или bool должно быть пустым?

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

И недавно я часто использовал класс Fallible:

Fallible<std::string> theName = obj.getName();
if (theName)
{
    // ...
}

Для такого класса доступны различные реализации (проверьте Google Code Search), Я также создал свой собственный.

Ответ 10

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

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

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

Ответ 11

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

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

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

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