Я использую исключения, чтобы поймать проблемы рано. Например:
public int getAverageAge(Person p1, Person p2){
if(p1 == null || p2 == null)
throw new IllegalArgumentException("One or more of input persons is null").
return (p1.getAge() + p2.getAge()) / 2;
}
Моя программа никогда не должна переходить null
в эту функцию. Я никогда не собираюсь этого делать. Однако, как мы все знаем, непредвиденные вещи случаются в программировании.
Выдав исключение, если возникает эта проблема, я могу обнаружить и исправить ее, прежде чем она вызовет больше проблем в других местах программы. Исключение останавливает программу и сообщает мне, что «здесь произошло что-то плохое, исправьте это». Вместо этого null
перемещение по программе вызывает проблемы в других местах.
Теперь, вы правы, в этом случае null
это просто вызвало бы NullPointerException
сразу, так что это может быть не лучшим примером.
Но рассмотрим метод, такой как этот, например:
public void registerPerson(Person person){
persons.add(person);
notifyRegisterObservers(person); // sends the person object to all kinds of objects.
}
В этом случае null
параметр a будет передаваться по программе и может привести к ошибкам намного позже, что будет трудно отследить до их происхождения.
Меняем функцию так:
public void registerPerson(Person person){
if(person == null) throw new IllegalArgumentException("Input person is null.");
persons.add(person);
notifyRegisterObservers(person); // sends the person object to all kinds of objects.
}
Позволяет мне обнаружить проблему намного раньше, чем она вызовет странные ошибки в других местах.
Кроме того, null
ссылка в качестве параметра является лишь примером. Это может быть много разных проблем, от недопустимых аргументов до чего-то еще. Всегда лучше заметить их рано.
Поэтому мой вопрос прост: это хорошая практика? Хорошо ли мое использование исключений в качестве инструментов для предотвращения проблем? Это законное применение исключений или это проблематично?
Ответы:
Да, «провалить рано» - очень хороший принцип, и это просто один из возможных способов его реализации. А в методах, которые должны возвращать определенное значение, на самом деле не так уж много другого, что вы можете сделать для намеренного сбоя - это либо выбросить исключения, либо вызвать утверждения. Предполагается, что исключения указывают на «исключительные» условия, и обнаружение ошибки программирования, безусловно, является исключительным.
источник
Да, выбрасывать исключения - хорошая идея. Брось их рано, бросай их часто, бросай их с нетерпением.
Я знаю, что есть дебаты «исключения против утверждений», с некоторыми видами исключительного поведения (в частности, те, которые, как считается, отражают ошибки программирования), обрабатываемые утверждениями, которые можно «компилировать» для времени выполнения, а не для отладки / тестирования сборок. Но объем производительности, потребляемой в нескольких дополнительных проверках правильности, минимален на современном оборудовании, и любые дополнительные расходы значительно перевешиваются ценностью получения правильных, не поврежденных результатов. Я на самом деле никогда не встречал кодовую базу приложения, для которой я хотел бы (большинство) проверок удалять во время выполнения.
У меня возникает соблазн сказать, что я не хотел бы много дополнительных проверок и условных выражений в узких циклах численно интенсивного кода ... но на самом деле это то, где генерируется много числовых ошибок, и если они не найдены, они будут распространяться за пределы повлиять на все результаты. Так что даже там проверки стоит делать. На самом деле, некоторые из лучших, наиболее эффективных численных алгоритмов основаны на оценке ошибок.
Последнее, что нужно знать о дополнительном коде, - это код, чувствительный к задержкам, где дополнительные условия могут привести к остановке конвейера. Итак, в середине операционной системы, СУБД и других промежуточных ядер и низкоуровневой обработки связи / протокола. Но, опять же, именно в этих местах чаще всего наблюдаются ошибки, а их последствия (безопасность, правильность и целостность данных) наиболее вредны.
Одно из улучшений, которое я обнаружил, - это не выбрасывать только исключения базового уровня.
IllegalArgumentException
это хорошо, но это может прийти по существу откуда угодно. Для добавления пользовательских исключений в большинстве языков не требуется много. Для вашего модуля управления персоналом, скажите:Затем, когда кто-то видит
PersonArgumentException
Понятно откуда. Существует балансирование относительно того, сколько пользовательских исключений вы хотите добавить, потому что вы не хотите без необходимости умножать сущности (Occam's Razor). Часто достаточно нескольких пользовательских исключений, чтобы сигнализировать «этот модуль не получает правильные данные!» или "этот модуль не может делать то, что должен!" таким образом, чтобы он был конкретным и адаптированным, но не настолько точным, чтобы вам пришлось заново реализовать всю иерархию исключений. Я часто прихожу к этому небольшому набору пользовательских исключений, начиная с фондовых исключений, затем сканируя код и осознавая, что «эти N мест порождают фондовые исключения, но они сводятся к идее более высокого уровня, что они не получают данные им нужно, пустьисточник
I've never actually met an application codebase for which I'd want (most) checks removed at runtime.
Тогда вы не сделали код, который критичен к производительности. Сейчас я работаю над чем-то, что делает 37M операций в секунду с скомпилированными утверждениями и 42M без них. Утверждения не проверяют внешний ввод, они проверяют правильность кода. Мои клиенты более чем счастливы получить увеличение на 13%, как только я убедился, что мои вещи не сломаны.PersonArgumentException
это не так ясно, какIllegalArgumentException
. Последний общеизвестно, что его бросают, когда передается незаконный аргумент. Я бы на самом деле ожидал, что первый будет брошен, если aPerson
был в недопустимом состоянии для вызова (аналогичноInvalidOperationException
C #).Сбой в кратчайшие сроки велик при отладке приложения. Я помню конкретную ошибку сегментации в устаревшей программе на C ++: место, где обнаружена ошибка, не имело ничего общего с местом ее появления (нулевой указатель был благополучно перемещен из одного места в другое в памяти, прежде чем он, наконец, вызвал проблему ). Следы стека не могут помочь вам в этих случаях.
Так что да, защитное программирование - это действительно эффективный подход для быстрого обнаружения и исправления ошибок. С другой стороны, это может быть преувеличено, особенно с нулевыми ссылками.
Например, в вашем конкретном случае: если какая-либо из ссылок имеет значение null,
NullReferenceException
при следующем утверждении будет выброшено значение при попытке определить возраст одного человека. Вам на самом деле не нужно проверять вещи здесь самостоятельно: пусть базовая система перехватывает эти ошибки и генерирует исключения, поэтому они существуют .Для более реалистичного примера вы можете использовать
assert
операторы, которые:Короче писать и читать:
Специально разработаны для вашего подхода. В мире, где у вас есть как утверждения, так и исключения, вы можете выделить их следующим образом:
Таким образом, предоставляя свои предположения о входных данных и / или состоянии вашего приложения с помощью утверждений, следующий разработчик сможет немного лучше понять цель вашего кода.
Статический анализатор (например, компилятор) тоже может быть счастливее.
Наконец, утверждения могут быть удалены из развернутого приложения с помощью одного переключателя. Но, вообще говоря, не ожидайте повышения эффективности с этим: проверки утверждений во время выполнения незначительны.
источник
Насколько я знаю, разные программисты предпочитают одно или другое решение.
Первое решение обычно предпочтительнее, потому что оно более лаконично, в частности, вам не нужно проверять одно и то же условие снова и снова в разных функциях.
Я нахожу второе решение, например
тверже, потому что
registerPerson()
вызывается, а не когда исключение пустого указателя генерируется где-то в стеке вызовов. Отладка становится намного проще: мы все знаем, как далеко может пройти недопустимое значение в коде, прежде чем оно проявится как ошибка.registerPerson()
не делает никаких предположений о том, какие другие функции в конечном итоге будут использоватьperson
аргумент и как они будут его использовать: решение, котороеnull
является ошибкой, принимается и реализуется локально.Поэтому, особенно если код довольно сложный, я предпочитаю этот второй подход.
источник
В общем, да, это хорошая идея "рано провалиться". Однако в вашем конкретном примере явное
IllegalArgumentException
не дает существенного улучшения по сравнению сNullReferenceException
- поскольку оба оперируемых объекта уже передаются в качестве аргументов функции.Но давайте посмотрим на немного другой пример.
Если бы в конструкторе не было проверки аргументов, вы бы получили
NullReferenceException
при вызовеCalculate
.Но сломанный кусок кода не был ни
Calculate
функцией, ни потребителемCalculate
функции. Разбитым фрагментом кода был код, который пытаетсяPersonCalculator
создать нулевое значение,Person
поэтому мы хотим, чтобы исключение происходило.Если мы уберем эту явную проверку аргументов, вам придется выяснить, почему
NullReferenceException
произошла ошибка приCalculate
вызове. И отслеживание того, почему объект был создан сnull
человеком, может стать хитрым, особенно если код, создающий калькулятор, не близок к коду, который фактически вызываетCalculate
функцию.источник
Не в приведенных вами примерах.
Как вы говорите, бросание явно не принесет вам много пользы, когда вы вскоре получите исключение. Многие утверждают, что лучше иметь явное исключение с хорошим сообщением, хотя я не согласен. В предварительно выпущенных сценариях трассировка стека достаточно хороша. В сценариях после выпуска сайт вызовов часто может предоставлять более качественные сообщения, чем внутри функции.
Вторая форма предоставляет слишком много информации для функции. Эта функция не обязательно должна знать, что другие функции будут выдавать нулевой ввод. Даже если они делают бросок на входе нулевой Теперь , он становится очень хлопотно реорганизовать следует , что остановка является случай с проверкой нулевой является распространение по всему коду.
Но в общем, вы должны бросить рано, как только вы определите, что что-то пошло не так (при соблюдении DRY). Это, возможно, не очень хорошие примеры этого.
источник
В вашем примере функции я бы предпочел, чтобы вы не делали никаких проверок и просто позволяли
NullReferenceException
происходить.С одной стороны, в любом случае нет смысла пропускать ноль, поэтому я сразу выясню проблему, основываясь на метании a
NullReferenceException
.Во-вторых, если каждая функция выдает немного разные исключения в зависимости от того, какие явно вводимые данные были предоставлены, то довольно скоро у вас появятся функции, которые могут генерировать 18 различных типов исключений, и довольно скоро вы обнаружите, что это слишком много работы, и в любом случае просто подавление всех исключений.
На самом деле вы ничего не можете сделать, чтобы помочь в ситуации ошибки времени разработки в вашей функции, так что просто дайте сбое произойти, не меняя его.
источник
RuntimeException
, не обязательно на правильное предположение.