На прошлой неделе у нас был горячий спор об обработке нулей в слое обслуживания нашего приложения. Вопрос в контексте .NET, но он будет таким же в Java и многих других технологиях.
Вопрос заключался в следующем: следует ли вам всегда проверять наличие нулевых значений и заставлять ваш код работать, несмотря ни на что, или позволить исключению всплыть при неожиданном получении нулевого значения?
С одной стороны, проверка на null там, где вы этого не ожидаете (т. Е. У вас нет пользовательского интерфейса для обработки), на мой взгляд, такая же, как написание блока try с пустым catch. Вы просто скрываете ошибку. Ошибка может заключаться в том, что что-то изменилось в коде, и теперь null является ожидаемым значением, или произошла какая-то другая ошибка, и в метод передан неправильный идентификатор.
С другой стороны, проверка на нули может быть хорошей привычкой в целом. Кроме того, если есть проверка, приложение может продолжать работать, только небольшая часть функциональности не оказывает никакого влияния. Затем клиент может сообщить о небольшой ошибке, такой как «невозможно удалить комментарий» вместо гораздо более серьезной ошибки, такой как «невозможно открыть страницу X».
Какую практику вы придерживаетесь и каковы ваши аргументы за или против любого подхода?
Обновить:
Я хочу добавить некоторые подробности о нашем конкретном случае. Мы извлекали некоторые объекты из базы данных и обрабатывали их (скажем, создавали коллекцию). Разработчик, который написал код, не ожидал, что объект может быть нулевым, поэтому он не включил никаких проверок, и при загрузке страницы произошла ошибка, и вся страница не загрузилась.
Очевидно, в этом случае должна была быть проверка. Затем мы вступили в спор о том, следует ли проверять каждый обрабатываемый объект, даже если ожидается, что он не будет отсутствовать, и следует ли окончательную обработку прервать молча.
Гипотетическим преимуществом будет то, что страница будет продолжать работать. Подумайте о результатах поиска на Stack Exchange в разных группах (пользователи, комментарии, вопросы). Метод может проверять наличие нуля и прерывать обработку пользователей (которая из-за ошибки равна нулю), но возвращает разделы «комментарии» и «вопросы». Страница продолжит работать, за исключением того, что раздел «пользователи» будет отсутствовать (что является ошибкой). Должны ли мы рано провалиться и сломать всю страницу или продолжить работу и ждать, пока кто-нибудь заметит, что раздел «пользователи» отсутствует?
источник
assert(foo != null, "foo is web control within the repeater, there's no reason to expect it to be null, etc, etc...");
Ответы:
Вопрос не столько в том, следует ли вам проверять
null
или позволять среде выполнения генерировать исключение; это то, как вы должны реагировать на такую неожиданную ситуацию.Ваши варианты тогда:
NullReferenceException
) и позволить ему всплыть; если вы не делаетеnull
проверку самостоятельно, это происходит автоматически.null
чека, либо путем перехватаNullReferenceException
и выброса более конкретного исключения.Не существует общего правила, какое из них является лучшим решением. Лично я бы сказал:
источник
ИМХО, попытка обработать нулевые значения, которые вы не ожидаете, приводит к чрезмерно сложному коду. Если вы не ожидаете null, проясните это, бросая
ArgumentNullException
. Я очень расстраиваюсь, когда люди проверяют, является ли значение нулевым, а затем пытаются написать какой-то код, который не имеет никакого смысла. То же самое относится к использованиюSingleOrDefault
(или, что еще хуже, к сбору), когда кто-то действительно ожидает,Single
и ко многим другим случаям, когда люди боятся (я не знаю, о чем) четко заявить о своей логике.источник
ArgumentNullException
следует использовать кодовые контракты (если только это не исходный код до 2010 года, который не поддерживает кодовые контракты).ArgumentNullException
счет как «обработку» нулевого значения: это предотвращает последующее исключение нулевой ссылки.ArgumentNullException
очень ясно дает понять, в чем проблема и как ее исправить. C # не склонен использовать «undefined». Если вы называете что-то неопределенным способом, то способ, которым вы называете это, не имеет смысла. Исключением является правильный способ указать это. Теперь иногда я делаю методы синтаксического анализа, которые возвращают ноль, еслиint
(например) невозможно проанализировать. Но это тот случай, когда непарсируемый результат является законной возможностью, а не ошибкой использования. Смотрите также эту статью .Привычка проверять
null
мой опыт исходит от бывших разработчиков на C или C ++, на этих языках у вас есть хороший шанс скрыть серьезную ошибку, когда не проверяетеNULL
. Как вы знаете, в Java или C # все по-другому, использование нулевого ref без проверкиnull
приведет к исключению, поэтому ошибка не будет тайно скрыта, если вы не игнорируете ее на вызывающей стороне. Поэтому в большинстве случаев проверка явноnull
не имеет большого смысла и усложняет код более, чем необходимо. Вот почему я не считаю нулевую проверку хорошей привычкой в этих языках (по крайней мере, не в целом).Конечно, есть исключения из этого правила, вот те, о которых я могу думать:
Вам нужно более удобное для восприятия человеком сообщение об ошибке, сообщающее пользователю, в какой части программы произошла неправильная нулевая ссылка. Таким образом, ваш тест на нулевое значение выдает просто другое исключение с другим текстом ошибки. Очевидно, что никакая ошибка не будет замаскирована таким образом.
Вы хотите, чтобы ваша программа "провалилась раньше". Например, вы хотите проверить нулевое значение параметра конструктора, где в противном случае ссылка на объект будет храниться только в переменной-члене и использоваться позже.
Вы можете безопасно справиться с ситуацией нулевого реферирования без риска последующих сбоев и без маскировки серьезной ошибки.
вам нужен совершенно другой вид сигнализации об ошибках (например, возвращая код ошибки) в вашем текущем контексте
источник
Использование утверждает , чтобы проверить и указать заранее / постусловия и инварианты для вашего кода.
Это значительно облегчает понимание того, что ожидает код и что с ним можно ожидать.
Утверждение, IMO, является подходящей проверкой, потому что это:
Я думаю, что самодокументированный код важен, поэтому иметь чек - это хорошо. Защитное, отказоустойчивое программирование облегчает обслуживание, на которое обычно тратится большинство времени.
Обновить
По поводу вашей разработки. Обычно хорошо дать конечному пользователю приложение, которое хорошо работает при ошибках, то есть показывает как можно больше. Надежность хороша, потому что небеса знают только то, что сломается в критический момент.
Тем не менее, разработчики и тестеры должны знать об ошибках как можно скорее, поэтому вы, вероятно, захотите каркас журналирования с ловушкой, которая может отображать предупреждение пользователю о наличии проблемы. Предупреждение, вероятно, следует показывать всем пользователям, но, вероятно, его можно настроить по-разному в зависимости от среды выполнения.
источник
Ошибка рано, часто неудача.
null
это одно из лучших неожиданных значений, которые вы можете получить, потому что вы можете быстро потерпеть неудачу, как только вы попытаетесь его использовать. Другие неожиданные значения не так легко обнаружить. Посколькуnull
для вас автоматически происходит сбой всякий раз, когда вы пытаетесь его использовать, я бы сказал, что он не требует явной проверки.источник
Проверка на ноль должна быть вашей второй натурой
Ваш API будет использоваться другими людьми, и их ожидания могут отличаться от ваших
Тщательные юнит-тесты обычно подчеркивают необходимость нулевой контрольной проверки
Проверка на нулевые значения не подразумевает условных операторов. Если вы беспокоитесь о том, что нулевая проверка делает ваш код менее читабельным, вы можете рассмотреть возможность заключения контрактов на код .NET.
Попробуйте установить ReSharper (или аналогичный) для поиска в вашем коде пропущенных проверок нулевых ссылок.
источник
Я хотел бы рассмотреть этот вопрос с точки зрения семантики, т.е. задать себе вопрос, что представляет собой нулевой указатель или нулевой ссылочный аргумент.
Если функция или метод foo () имеет аргумент x типа T, x может быть обязательным или необязательным .
В C ++ вы можете передать обязательный аргумент как
В обоих случаях у вас нет проблемы проверки нулевого указателя. Так, во многих случаях вам не нужен нулевой чек указатель на все обязательные аргументы.
Если вы передаете необязательный аргумент, вы можете использовать указатель и использовать значение NULL, чтобы указать, что значение не было задано:
Альтернативой является использование умного указателя, такого как
В этом случае вы, вероятно, захотите проверить указатель в коде, потому что для метода foo () имеет значение, содержит ли x значение или его нет вообще.
Третий и последний случай заключается в том, что вы используете указатель для обязательного аргумента. В этом случае вызов foo (x) с x == NULL является ошибкой, и вы должны решить, как с этим справиться (так же, как с индексом за пределами допустимого диапазона или любым неверным вводом):
Помимо более приятного способа сбоев (предоставления пользователю дополнительной информации), преимущество второго подхода состоит в том, что более вероятно, что ошибка будет обнаружена: программа будет давать сбой каждый раз, когда метод вызывается с неправильными аргументами, тогда как с При подходе 1 ошибка появится только в том случае, если выполняется оператор, разыменовывающий указатель (например, если введена правая ветвь оператора if). Таким образом, подход 2 предлагает более сильную форму «провал рано».
В Java у вас меньше вариантов, потому что все объекты передаются с использованием ссылок, которые всегда могут быть нулевыми. Опять же, если значение NULL представляет значение NONE необязательного аргумента, то проверка значения NULL (вероятно) будет частью логики реализации.
Если значение NULL является недопустимым, у вас есть ошибка, и я бы применил те же соображения, что и в случае указателей C ++. Поскольку в Java вы можете перехватывать исключения нулевого указателя, описанный выше подход 1 эквивалентен подходу 2, если метод foo () разыменовывает все входные аргументы во всех путях выполнения (что, вероятно, происходит очень часто для небольших методов).
подведение
РЕДАКТИРОВАТЬ
Спасибо Стилгару за более подробную информацию.
В вашем случае кажется, что у вас есть null как результат метода. Опять же, я думаю, вы должны сначала уточнить (и исправить) семантику ваших методов, прежде чем вы сможете принять решение.
Итак, у вас есть метод m1 (), вызывающий метод m2 (), и m2 () возвращает ссылку (в Java) или указатель (в C ++) на некоторый объект.
Какова семантика m2 ()? Должен ли m2 () всегда возвращать ненулевой результат? Если это так, то нулевой результат является внутренней ошибкой (в м2 ()). Если вы проверите возвращаемое значение в m1 (), вы можете иметь более надежную обработку ошибок. Если вы этого не сделаете, у вас, вероятно, будет исключение, рано или поздно. Возможно, нет. Надеюсь, что исключение выдается во время тестирования, а не после развертывания. Вопрос : если m2 () никогда не должен возвращать ноль, почему он возвращает ноль? Может быть, вместо этого следует выдать исключение? m2 () вероятно глючит.
Альтернативой является то, что возвращение null является частью семантики метода m2 (), то есть имеет необязательный результат, который должен быть задокументирован в документации Javadoc метода. В этом случае все в порядке, чтобы иметь нулевое значение. Метод m1 () должен проверить его как любое другое значение: здесь нет ошибки, но, вероятно, проверка, является ли результат нулевым, является лишь частью логики программы.
источник
Мой общий подход заключается в том, чтобы никогда не проверять состояние ошибки, с которым вы не знаете, как справиться . Тогда возникает вопрос, на который нужно ответить в каждом конкретном случае: можете ли вы сделать что-то разумное в присутствии неожиданного
null
значения?Если вы можете продолжить безопасно, подставив другое значение (скажем, пустую коллекцию, или значение или экземпляр по умолчанию), то обязательно сделайте это. Пример @ Shahbaz из World of Warcraft о замене одного изображения другим попадает в эту категорию; само изображение, скорее всего, просто украшение, и не оказывает никакого функционального воздействия. (В отладочной сборке я бы использовал кричащий цвет, чтобы привлечь внимание к тому факту, что произошла замена, но это сильно зависит от природы приложения.) Тем не менее, регистрируйте подробности об ошибке, чтобы вы знали, что она происходит; в противном случае вы будете скрывать ошибку, о которой, вероятно, хотите знать. Скрывать это от пользователя - это одно (опять же, при условии, что обработка может продолжаться безопасно); скрывать это от разработчика - совсем другое.
Если вы не можете продолжить безопасно при наличии нулевого значения, то произойдет сбой с явной ошибкой; в общем, я предпочитаю потерпеть неудачу как можно скорее, когда очевидно, что операция не может быть успешной, что подразумевает проверку предварительных условий. Бывают моменты, когда вы не хотите сразу же потерпеть неудачу, а откладываете неудачу как можно дольше; Это соображение возникает, например, в приложениях, связанных с криптографией, где атаки по побочным каналам могут в противном случае разгласить информацию, которую вы не хотите знать. В конце позвольте ошибке всплыть в некотором общем обработчике ошибок, который, в свою очередь, регистрирует соответствующие подробности и отображает приятное (как бы это ни было приятно) сообщение об ошибке для пользователя.
Также стоит отметить, что правильный ответ может очень сильно отличаться между сборками тестирования / QA / отладки и сборками выпуска / выпуска. В отладочных сборках я бы пошел на ранние и серьезные сбои с очень подробными сообщениями об ошибках, чтобы было совершенно очевидно, что есть проблема, и позволить после вскрытия определить, что нужно сделать, чтобы ее исправить. Производственные сборки, когда они сталкиваются с безопасными восстанавливаемыми ошибками, вероятно, должны способствовать восстановлению.
источник
Конечно
NULL
, не ожидается , но всегда есть ошибки!Я считаю, что вы должны проверить
NULL
, не ожидаете ли вы этого. Позвольте мне привести вам примеры (хотя они не на Java).В World of Warcraft из-за ошибки изображение одного из разработчиков появилось на кубе для многих объектов. Представьте, что если бы графический движок не ожидал
NULL
указателей, произошел бы сбой, в то время как он превратился в не столь фатальный (даже забавный) опыт для пользователя. Самое меньшее, вы бы не потеряли соединение или точки сохранения.Это пример, когда проверка на NULL предотвратила сбой, и случай был обработан без уведомления.
Представьте себе программу кассира банка. Интерфейс выполняет некоторые проверки и отправляет входные данные в базовую часть для выполнения денежных переводов. Если основная часть ожидает, что не получит
NULL
, то ошибка в интерфейсе может вызвать проблему в транзакции, возможно, некоторые деньги в середине теряются (или, что еще хуже, дублируются).Под «проблемой» я имею в виду сбой (как в случае сбоя (скажем, программа написана на C ++), а не исключение нулевого указателя). В этом случае транзакция должна откатиться, если встретится NULL (что, конечно, было бы невозможно, если бы вы не проверяли переменные на NULL!)
Я уверен, что вы поняли идею.
Проверка правильности аргументов ваших функций не загромождает ваш код, так как это пара проверок в верхней части вашей функции (не распространяется по середине) и значительно повышает надежность.
Если вам нужно выбрать между «программа сообщает пользователю, что произошел сбой, и она корректно завершает работу или откатывается до согласованного состояния, возможно создавая журнал ошибок» и «Нарушение прав доступа», разве вы не выбрали бы первое? Это означает, что каждая функция должна быть подготовлена для вызова с помощью глючного кода, а не ожидать, что все правильно, просто потому, что ошибки существуют всегда.
источник
Как указано в других ответах. Если вы не ожидаете
NULL
, проясните это, бросаяArgumentNullException
. На мой взгляд, когда вы отлаживаете проект, это помогает вам быстрее обнаружить ошибки в логике вашей программы.Таким образом, вы собираетесь выпустить свое программное обеспечение, если вы восстановите эти
NullRefrences
проверки, вы ничего не пропустите в логике вашего программного обеспечения, это лишь вдвойне гарантирует, что серьезная ошибка не появится.Другой момент заключается в том, что если
NULL
проверка выполняется для параметров функции, то вы можете решить, является ли функция подходящим местом для этого или нет; если нет, найдите все ссылки на функцию, а затем перед передачей параметра в функцию просмотрите возможные сценарии, которые могут привести к нулевой ссылке.источник
Каждый
null
в вашей системе - бомба замедленного действия, ожидающая своего открытия. Проверьтеnull
на границах, где ваш код взаимодействует с кодом, который вы не контролируете, и запретитеnull
в своем собственном коде. Это избавляет вас от проблемы, не наивно доверяя чужому коду. Используйте исключения или своего родаMaybe
/Nothing
типа вместо.источник
Хотя исключение нулевого указателя в конечном итоге будет выдано, оно часто будет отбрасываться далеко от точки, в которой вы получили нулевой указатель. Сделайте себе одолжение и сократите время, необходимое для исправления ошибки, по крайней мере, регистрируя тот факт, что вы получаете нулевой указатель как можно скорее.
Даже если вы не знаете, что делать сейчас, регистрация события сэкономит ваше время позже. И, возможно, парень, который получит билет, сможет использовать сэкономленное время, чтобы выяснить правильный ответ.
источник
Поскольку,
Fail early, fail fast
как сказано в @DeadMG (@empi), это невозможно, так как веб-приложение должно хорошо работать при ошибках, вы должны применить правилонет ловли исключений без регистрации
так что вы, как разработчики, знаете о потенциальных проблемах, просматривая журналы.
Если вы используете каркасы журналирования, такие как log4net или log4j, вы можете настроить дополнительный специальный вывод журналирования (он же appender),
который будет отправлять вам сообщения об ошибках и ошибках. так что вы в курсе.[Обновить]
если конечный пользователь страницы не должен знать об ошибке, вы можете настроить приложение, которое будет отправлять вам сообщения об ошибках и сообщениях об ошибках.
если все в порядке, что конечный пользователь страницы видит загадочные сообщения об ошибках, вы можете настроить приложение, которое помещает журнал ошибок для обработки страницы в конце страницы.
Независимо от того, как вы используете ведение журнала, если вы проглотили исключение, программа продолжит работу с данными, которые имеют смысл, и проглатывание больше не будет молчать.
источник
На этот вопрос уже есть хорошие ответы, может быть одно из них: я предпочитаю утверждения в своем коде. Если ваш код может работать только с допустимыми объектами, но с нулевым значением, вы должны установить нулевое значение.
Утверждения в такой ситуации позволят любому модульному и / или интеграционному тесту провалиться с точным сообщением, так что вы можете довольно быстро решить проблему до начала производства. Если такая ошибка проскальзывает через тесты и поступает в производство (где утверждения должны быть деактивированы), вы получите NpEx и серьезную ошибку, но это лучше, чем какая-то незначительная ошибка, которая гораздо более нечеткая и появляется где-то еще в код, и было бы дороже, чтобы исправить.
Более того, если кто-то должен работать с вашими утверждениями в коде, он расскажет ему о ваших предположениях, которые вы сделали во время разработки / написания кода (в данном случае: Привет, чувак, этот объект должен быть представлен!), Что облегчает поддержку.
источник
Обычно я проверяю наличие нулевых значений, но я «обрабатываю» неожиданные нулевые значения, выдавая исключение, которое дает немного больше информации о том, где именно произошло нулевое значение.
Изменить: Если вы получаете ноль, когда вы не ожидаете, что что-то не так - программа должна умереть.
источник
Это зависит от языковой реализации исключений. Исключения позволяют вам предпринять некоторые действия, чтобы предотвратить повреждение данных или аккуратно высвободить ресурсы, если ваша система войдет в непредвиденное состояние или состояние. Это отличается от обработки ошибок, которая является условием, которое вы можете ожидать. Моя философия заключается в том, что у вас должно быть как можно меньше обработки исключений. Это шум. Может ли ваш метод сделать что-то разумное, обработав исключение? Это может восстановиться? Это может восстановить или предотвратить дальнейшее повреждение? Если вы просто собираетесь поймать исключение, а затем вернетесь из метода или ошиблись каким-либо другим безобидным способом, то поймать исключение было бессмысленно. Вы бы только добавили шум и сложность в свой метод, и вы можете скрывать источник ошибки в вашем дизайне. В этом случае я бы позволил ошибке всплыть и пойматься внешней петлей. Если вы можете сделать что-то, например, восстановить объект или пометить его как поврежденный или освободить какой-либо критически важный ресурс, такой как файл блокировки, или просто закрыв сокет, тогда это хорошее использование исключений. Если вы действительно ожидаете, что NULL будет часто отображаться как допустимый крайний случай, то вы должны обрабатывать это в обычном логическом потоке с помощью операторов if-the-else или switch-case или чего-либо еще, а не с обработкой исключений. Например, поле, отключенное в форме, может иметь значение NULL, которое отличается от пустой строки, представляющей поле, оставленное пустым. Это та область, где вы должны использовать здравый смысл и здравый смысл. Я никогда не слышал о хороших эмпирических правилах о том, как справиться с этой проблемой в любой ситуации. Если вы можете сделать что-то, например, восстановить объект или пометить его как поврежденный или освободить какой-либо критически важный ресурс, такой как файл блокировки, или просто закрыв сокет, тогда это хорошее использование исключений. Если вы действительно ожидаете, что NULL будет часто отображаться как допустимый крайний случай, то вы должны обрабатывать это в обычном логическом потоке с помощью операторов if-the-else или switch-case или чего-либо еще, а не с обработкой исключений. Например, поле, отключенное в форме, может иметь значение NULL, которое отличается от пустой строки, представляющей поле, оставленное пустым. Это та область, где вы должны использовать здравый смысл и здравый смысл. Я никогда не слышал о хороших эмпирических правилах о том, как справиться с этой проблемой в любой ситуации. Если вы можете сделать что-то, например, восстановить объект или пометить его как поврежденный или освободить какой-либо критически важный ресурс, такой как файл блокировки, или просто закрыв сокет, тогда это хорошее использование исключений. Если вы действительно ожидаете, что NULL будет часто отображаться как допустимый крайний случай, то вы должны обрабатывать это в обычном логическом потоке с помощью операторов if-the-else или switch-case или чего-либо еще, а не с обработкой исключений. Например, поле, отключенное в форме, может иметь значение NULL, которое отличается от пустой строки, представляющей поле, оставленное пустым. Это та область, где вы должны использовать здравый смысл и здравый смысл. Я никогда не слышал о хороших эмпирических правилах о том, как справиться с этой проблемой в любой ситуации.
Java терпит неудачу в своей реализации обработки исключений с «проверенными исключениями», где каждое возможное исключение, которое может быть вызвано объектами, используемыми в методе, должно быть перехвачено или объявлено в предложении «throws» метода. Это противоположность C ++ и Python, где вы можете обрабатывать исключения по своему усмотрению. В Java, если вы модифицируете тело метода для включения вызова, который может вызвать исключение, вы сталкиваетесь с выбором добавления явной обработки для исключения, которое может вас не волновать, таким образом добавляя шум и сложность вашему коду, или вы должны добавьте это исключение к условию throws модифицируемого вами метода, который не только добавляет шум и беспорядок, но и меняет сигнатуру вашего метода. Затем вы должны перейти к коду модификации, где бы ни использовался ваш метод, для обработки нового исключения, которое ваш метод теперь может вызвать, или добавить исключение к предложению «throws» этих методов, вызывая эффект домино изменений кода. «Поймать или указать требование» должно быть одним из самых раздражающих аспектов Java. Исключение исключения для RuntimeExceptions и официальное объяснение Java для этой ошибки проектирования - хромое.
Последний абзац имел мало общего с ответом на ваш вопрос. Я просто ненавижу Java.
- Ной
источник