Null, великий и ужасный
Именно так и никак иначе: null в C# — однозначно ошибочное решение, бездумно скопированное из более ранних языков.
Этот ящик Пандоры был открыт еще при создании языка ALGOL W великим Хоаром, который позднее назвал собственную идею ошибкой на миллиард долларов.
Лучшая историческая альтернатива
Разумеется, она была, причем очевидная по современным меркам
Самое трагичное, что все это не было откровением и даже новинкой уже к моменту проектирования первой версии языка. Увы, тогда матерых функциональщиков в команде Хейлсберга не было.
Лекарства для текущей реальности
Хотя прогноз очень серьезный, летального исхода можно избежать за счет применения различных практик и инструментов. Способы и их особенности пронумерованы для удобства ссылок.
Явные проверки на null в операторе if. Очень прямолинейный способ с массой серьезных недостатков.
Атрибут NotNull. Немного упрощает использование явных проверок
Паттерн проектирования Null object. Очень хороший способ, но с ограниченной сферой применения.
Конвенция о возврате живых объектов по умолчанию. Очень просто и эффективно.
Любой метод или свойство, для которых явно не заявлена возможность возвращать null, должны всегда предоставлять полноценный объект. Для поддержания достаточно выработки хорошей привычки, например, посредством ревью кода.
Конвенция о стандартных способах явно указать что свойство или метод может вернуть null: например, префикс Try или суффикс OrDefault в имени метода. Органичное дополнение к возврату полноценных объектов по умолчанию. Достоинства и недостатки те же.
Атрибут CanBeNull. Добрый антипод-близнец атрибута NotNull.
Операторы C# (тернарный, Элвиса, coalesce)
Тип Optional. Позволяет явно поддержать отсутствие объекта.
Монада Maybe. LINQ для удобной обработки случаев как наличия, так и отсутствия объекта.
Пакет Fody/NullGuard. Автоматические проверки на null на стероидах.
Ссылочные типы без возможности присвоения null (если добавят в одну из будущих версий C#)
Итоги
Буду краток — все выводы в таблице:
| Настоятельная рекомендация | Антипаттерн | На ваш вкус и потребности |
|---|---|---|
| 4, 5, 7, 11, 12 (когда и если будет реализовано) | 1, 2 | 3, 6, 8, 9, 10 |
На предвосхищение ООП через 20 лет не претендую, но дополнениям и критике буду очень рад.
Обновление
добавил примеры кода к утопической альтернативе.
Что необходимо возвращать из функции: null или пустой объект?
Что является лучшей практикой при возвращении данных из функции. Лучше возвратить null или пустой объект? И почему необходимо использовать один вариант по сравнению с другим.
Рассмотрим следующий вариант:
5 ответов 5
Выбор между null и null-объектом зависит от того, как метод будет использоваться.
Примеры
Получение коллекции пользователей
Как этот метод будет использоваться? Скорее всего, примерно так:
Получение одного пользователя
Как этот метод будет использоваться? Скорее всего, примерно так:
Чего точно не следует делать — это возвращать new User() : это не позволит нормально проверить, существует запрошенный пользователь или нет. Если уж делать null-объекты, то они по возможности должны быть в единственном экземпляре и неизменяемы.
Получение текущего пользователя
Как этот метод будет использоваться? Скорее всего, примерно так:
Планы на будущее
В C# 7 планируется добавить разделение между nullable и non-nullable для reference-типов (в дополнение к value-типам). Тогда методы выше будут выглядеть так:
ReSharper
Если у вас есть R#, то вы можете добавить аннотации уже сейчас:
Если вы под пустым объектом понимаете Null Object pattern, я бы практически никогда не рекомендовал его использовать. Проблема в том, что это позволяет подавить ошибку вместо того, чтобы проверить её. Этот паттерн по сути не особенно отличается от возвращения кода ошибки, только код ошибки при этом совмещён с возвращаемым объектом.
В случае возвращения null код, который по ошибке не проверяет это, немедленно упадёт в первом же тесте. Если же возвращать искусственный «пустой объект», код, который не проверяет его, будет считать, что он правильно работает, и вы никогда не найдёте ошибку.
Заметка про NULL
Основные положения
Для удобства сделаем процедуру, печатающую состояние булевого параметра:
и включим опцию печати сообщений на консоль:
Привычные операторы сравнения пасуют перед NULLом:
Сравнение с NULLом
Соответственно, IS NOT NULL действует наоборот: вернёт истину, если значение операнда отлично от NULLа и ложь, если он является NULLом:
DECODE идёт против системы:
Пример с составными индексами находится в параграфе про индексы.
Логические операции и NULL
В большинстве случаев неизвестный результат обрабатывается как ЛОЖЬ :
Отрицание неизвестности даёт неизвестность:
Операторы IN и NOT IN
Для начала сделаем несколько предварительных действий. Для тестов создадим таблицу T с одним числовым столбцом A и четырьмя строками: 1, 2, 3 и NULL
Включим трассировку запроса (для этого надо обладать ролью PLUSTRACE ).
В листингах от трассировки оставлена только часть filter, чтобы показать, во что разворачиваются указанные в запросе условия.
Предварительные действия закончены, давайте теперь поработаем с операторами. Попробуем выбрать все записи, которые входят в набор (1, 2, NULL) :
Попробуем теперь с NOT IN :
Вообще ни одной записи! Давайте разберёмся, почему тройка не попала в результаты запроса. Посчитаем вручную фильтр, который применила СУБД, для случая A=3 :
Из-за особенностей трёхзначной логики NOT IN вообще не дружит с NULLами: как только NULL попал в условия отбора, данных не ждите.
NULL и пустая строка
Здесь Oracle отходит от стандарта ANSI SQL и провозглашает эквивалентность NULLа и пустой строки. Это, пожалуй, одна из наиболее спорных фич, которая время от времени рождает многостраничные обсуждения с переходом на личности, поливанием друг друга фекалиями и прочими непременными атрибутами жёстких споров. Судя по документации, Oracle и сам бы не прочь изменить эту ситуацию (там сказано, что хоть сейчас пустая строка и обрабатывается как NULL, в будущих релизах это может измениться), но на сегодняшний день под эту СУБД написано такое колоссальное количество кода, что взять и поменять поведение системы вряд ли реально. Тем более, говорить об этом они начали как минимум с седьмой версии СУБД (1992-1996 годы), а сейчас уже двенадцатая на подходе.
NULL и пустая строка эквивалентны:
непременный атрибут жёсткого спора:
Длина пустой строки не определена:
Сравнение с пустой строкой невозможно:
Критики подхода, предлагаемого Ораклом, говорят о том, что пустая строка не обязательно обозначает неизвестность. Например, менеджер по продажам заполняет карточку клиента. Он может указать его контактный телефон (555-123456), может указать, что он неизвестен (NULL), а может и указать, что контактный телефон отсутствует (пустая строка). С оракловым способом хранения пустых строк реализовать последний вариант будет проблемно. С точки зрения семантики довод правильный, но у меня на него всегда возникает вопрос, полного ответа на который я так и не получил: как менеджер введёт в поле «телефон» пустую строку и как он в дальнейшем отличит его от NULLа? Варианты, конечно, есть, но всё-таки…
Вообще-то, если говорить про PL/SQL, то где-то глубоко внутри его движка пустая строка и NULL различаются. Один из способов увидеть это связан с тем, что ассоциативные коллекции позволяют сохранить элемент с индексом » (пустая строка), но не позволяют сохранить элемент с индексом NULL:
Использовать такие финты ушами на практике не стоит. Во избежание проблем лучше усвоить правило из доки: пустая строка и NULL в оракле неразличимы.
Математика NULLа
Этот маленький абзац писался пятничным вечером под пиво, на фоне пятничного РЕН-ТВшного фильма. Переписывать его лень, уж извините.
Очевидно, что мы ничем не сможем помочь Коле: неизвестное количество любовников Маши до замужества сводит все расчёты к одному значению — неизвестно. Oracle, хоть и назвался оракулом, в этом вопросе уходит не дальше, чем участники битвы экстрасенсов: он даёт очевидные ответы только на очевидные вопросы. Хотя, надо признать, что Oracle гораздо честнее: в случае с Колей он не будет заниматься психоанализом и сразу скажет: «я не знаю»:
С конкатенацией дела обстоят по другому: вы можете добавить NULL к строке и это её не изменит. Такая вот политика двойных стандартов.
NULL и агрегатные функции
Таблица с данными. Используется ниже много раз:
Пустые значения игнорируются агрегатами:
Набор данных только из NULLов:
Пустой набор данных:
NULL в OLAP
Удобная фишка sqlplus: при выводе данных заменяет NULL на указанную строку:
Проверяем дуализм NULLа в многомерном кубе:
Почему NULL это плохо?
Простой пример использования NULL в Java:
Что не так с этим методом?
Он может вернуть NULL вместо объекта — вот что не так. NULL — ужасная практика в объектно-ориентированной парадигме, и ее следует избегать любой ценой. Уже было опубликовано несколько мнений об этом, в том числе « Пустые ссылки», презентация « Ошибка в миллиард долларов » Тони Хоара и вся книга « Думая об объекте » Дэвида Уэста.
Здесь я попытаюсь обобщить все аргументы и показать примеры того, как можно избежать использования NULL и заменить его на соответствующие объектно-ориентированные конструкции.
Первый — это шаблон проектирования Null Object (лучший способ сделать его постоянным):
Помимо презентации Тони Хоара и книги Дэвида Уэста, упомянутой выше, я прочитал эти публикации перед тем, как написать этот пост: « Чистый код » Роберта Мартина, « Код завершен » Стивом Макконнеллом, скажите «Нет» «Нулевому» Джона Сонмеза, Возвращает ли ноль плохой дизайн? обсуждение в StackOverflow.
Специальная обработка ошибок
Каждый раз, когда вы получаете объект в качестве входных данных, вы должны проверить, является ли он NULL или действительной ссылкой на объект. Если вы забудете проверить, NullPointerException (NPE) может прервать выполнение во время выполнения. Таким образом, ваша логика загрязняется несколькими проверками, и если / then / else разветвляется:
Вот как исключительные ситуации должны обрабатываться в Си и других императивных процедурных языках. ООП ввел обработку исключений, прежде всего, чтобы избавиться от этих специальных блоков обработки ошибок. В ООП мы позволяем исключениям пузыриться, пока они не достигнут обработчика ошибок всего приложения, и наш код становится намного чище и короче:
Представьте, что NULL ссылается на наследование процедурного программирования, и вместо этого используйте 1) Нулевые объекты или 2) Исключения.
Двусмысленная семантика
Чтобы избавиться от этой неоднозначности, всегда возвращайте реальный объект, нулевой объект или генерируйте исключение.
Некоторые могут возразить, что нам иногда приходится возвращать NULL ради производительности. Например, метод get() интерфейса Map в Java возвращает NULL если на карте нет такого элемента:
Очевидно, что этот метод в два раза медленнее, чем первый. Что делать?
Интерфейс Map (не в обиду его авторам) имеет недостаток дизайна. Его метод get() должен был возвращать Iterator чтобы наш код выглядел так:
Мышление компьютера против мышления объекта
Оператор if (employee == null) понимается кем-то, кто знает, что объект в Java является указателем на структуру данных, а NULL — указателем на ничто ( 0x00000000 в процессорах Intel x86).
Однако, если вы начинаете думать как объект, это утверждение имеет гораздо меньше смысла. Вот как выглядит наш код с точки зрения объекта:
Последний вопрос в этом разговоре звучит странно, не так ли?
Вместо этого, если они повесят трубку после того, как мы попросим поговорить с Джеффри, это станет для нас проблемой (исключение). В этот момент мы пытаемся снова позвонить или сообщить нашему руководителю, что мы не можем связаться с Джеффри и совершить более крупную транзакцию.
Кроме того, они могут позволить нам поговорить с другим человеком, который не является Джеффри, но который может помочь с большинством наших вопросов или отказать в помощи, если нам нужно что-то «специфичное для Джеффри» (Нулевой объект).
Медленная неудача
Вместо быстрого провала приведенный выше код пытается медленно умирать, убивая других на своем пути. Вместо того чтобы сообщать всем, что что-то пошло не так и что обработка исключений должна начаться немедленно, она скрывает этот сбой от своего клиента.
Этот аргумент близок к «специальной обработке ошибок», описанной выше.
Хорошей практикой является сделать ваш код как можно более хрупким, позволяя ему ломаться при необходимости.
Сделайте ваши методы чрезвычайно требовательными к данным, которыми они манипулируют. Позвольте им жаловаться, выдавая исключения, если предоставленные данные недостаточны или просто не соответствуют сценарию основного использования метода.
В противном случае, верните Null Object, который демонстрирует некоторое общее поведение и генерирует исключения для всех других вызовов:
Что вернуть, если некоторые ругают за возврат null?
ну вот один чел спорит со мной, что null возвращать в методе get нельзя, так как метод работает с массивом, поэтому должен возвращать вместо null пустой массив []
я ему аргументирую, что мы работаем не с массивом, а с ключом массива и по ключу там может быть не массив 
кто прав и как вообще тогда правильно сделать return?
Помощь в написании контрольных, курсовых и дипломных работ здесь.
за что ругают MFC?
Сами то вы работаете на MFC, уважаемые коллеги? Все же эта MFC заложена в VC++ уже довольно давно.

Добрый день! Например я хочу чтобы донное выражение возвращало мне 0 или скажем 1, если.

Всем доброго времени суток. Никак не могу найти ответ на вопрос в теме. Грубо говоря нужно.





