Ваш вопрос.
Но, но - из-за моей наивности, позвольте мне закричать - иногда значение МОЖЕТ быть значимо отсутствовать!
Полностью на борту с вами там. Тем не менее, есть несколько предостережений, о которых я расскажу через секунду. Но сначала:
Нули - это зло, и их следует избегать всякий раз, когда это возможно.
Я думаю, что это благими намерениями, но чрезмерно обобщенное заявление.
Нули не являются злом. Они не антипаттерн. Их нельзя избегать любой ценой. Тем не менее, нулевые значения подвержены ошибкам (из-за невозможности проверки на нулевые значения).
Но я не понимаю, как отказ от проверки на ноль является каким-то доказательством того, что нуль по своей сути неправильно использовать.
Если null является злом, то то же самое относится и к индексам массивов и указателям на C ++. Они не. С ними легко выстрелить себе в ногу, но их не следует избегать любой ценой.
В остальной части этого ответа я собираюсь адаптировать намерение «нули - это зло» к более нюансам « нули должны обрабатываться ответственно ».
Ваше решение
Хотя я согласен с вашим мнением об использовании null как глобального правила; Я не согласен с тем, что вы используете null в данном конкретном случае.
Подводя итог приведенным ниже отзывам: вы неправильно понимаете цель наследования и полиморфизма. В то время как ваш аргумент в пользу использования null имеет смысл, ваше дальнейшее «доказательство» того, почему другой путь плохой, основано на неправильном использовании наследования, что делает ваши точки несколько не относящимися к нулевой проблеме.
У большинства обновлений, естественно, есть связанный с ними игрок - в конце концов, необходимо знать, какой игрок сделал ход на этом ходу. Однако некоторые обновления не могут иметь игрока, значимо связанного с ними.
Если эти обновления значимо отличаются (какими они являются), то вам нужны два отдельных типа обновлений.
Рассмотрим сообщения, например. У вас есть сообщения об ошибках и сообщения чата. Но хотя они могут содержать очень похожие данные (строковое сообщение и отправитель), они не ведут себя одинаково. Их следует использовать отдельно, как разные классы.
То, что вы делаете сейчас, - это эффективная дифференциация между двумя типами обновлений, основанная на нулевом свойстве Player. Это эквивалентно выбору между сообщением IM и сообщением об ошибке на основе наличия кода ошибки. Это работает, но это не лучший подход.
- Код JS теперь просто получит объект без Player в качестве определенного свойства, которое будет таким же, как если бы оно было нулевым, поскольку оба случая требуют проверки, присутствует ли значение или отсутствует;
Ваш аргумент опирается на ошибки полиморфизма. Если у вас есть метод, который возвращает GameUpdate
объект, он вообще не должен делать различий между GameUpdate
, PlayerGameUpdate
или NewlyDevelopedGameUpdate
.
Базовый класс должен иметь полностью рабочий контракт, то есть его свойства определены в базовом классе, а не в производном классе (при этом значение этих базовых свойств, конечно, может быть переопределено в производных классах).
Это делает ваш аргумент спорным. В хорошей практике вы никогда не должны заботиться о том, что на вашем GameUpdate
объекте есть или нет игрока. Если вы пытаетесь получить доступ к Player
свойству GameUpdate
, то вы делаете что-то нелогичное.
Типизированные языки, такие как C #, даже не позволят вам получить доступ к Player
свойству a, GameUpdate
потому что GameUpdate
просто не имеют этого свойства. Javascript значительно слабее в своем подходе (и не требует предварительной компиляции), поэтому он не пытается исправить вас и взорвет его во время выполнения.
- Если к классу GameUpdate будет добавлено еще одно обнуляемое поле, мне придется иметь возможность использовать множественное наследование для продолжения этого дизайна - но MI само по себе зло (по мнению более опытных программистов) и, что более важно, C # не делает так что я не могу его использовать.
Вы не должны создавать классы, основанные на добавлении обнуляемых свойств. Вы должны создавать классы на основе функционально уникальных типов обновлений игры .
Как избежать проблем с нулем вообще?
Я думаю, что важно уточнить, как вы можете осмысленно выразить отсутствие ценности. Это набор решений (или плохих подходов) в произвольном порядке.
1. В любом случае используйте нуль.
Это самый простой подход. Тем не менее, вы подписываетесь на тонну нулевых проверок и (в случае неудачи) устраняете исключительные ситуации нулевых ссылок.
2. Используйте ненулевое бессмысленное значение
Простой пример здесь indexOf()
:
"abcde".indexOf("b"); // 1
"abcde".indexOf("f"); // -1
Вместо того null
, чтобы получить -1
. На техническом уровне это допустимое целочисленное значение. Однако отрицательное значение является бессмысленным ответом, так как ожидается, что индексы будут положительными целыми числами.
По логике это можно использовать только в тех случаях, когда возвращаемый тип допускает больше возможных значений, чем может быть возвращено осмысленно (indexOf = положительные целые числа; int = отрицательные и положительные целые числа)
Для ссылочных типов вы все равно можете применить этот принцип. Например, если у вас есть объект с целочисленным идентификатором, вы можете вернуть фиктивный объект с идентификатором, установленным на -1, в отличие от нуля.
Вам просто нужно определить «фиктивное состояние», с помощью которого вы можете измерить, является ли объект действительно пригодным для использования или нет.
Обратите внимание, что вы не должны полагаться на предопределенные строковые значения, если вы не контролируете строковое значение. Возможно, вы захотите установить имя пользователя на «ноль», если хотите вернуть пустой объект, но у вас возникнут проблемы, когда Кристофер Налл подпишется на ваш сервис .
3. Бросьте исключение вместо.
Нет, просто нет. Исключения не должны обрабатываться для логического потока.
При этом в некоторых случаях вы не хотите скрывать исключение (nullreference), а вместо этого показывать соответствующее исключение. Например:
var existingPerson = personRepository.GetById(123);
Это может выдать PersonNotFoundException
(или аналогичный), когда нет человека с идентификатором 123.
Совершенно очевидно, что это приемлемо только в тех случаях, когда отсутствие предмета делает невозможным дальнейшую обработку. Если не удается найти элемент, и приложение может продолжаться, то НИКОГДА не следует выбрасывать исключение . Я не могу подчеркнуть это достаточно.