Как игнорировать исключения в java

Игнорировать исключения не стоит, даже если есть уверенность, что такой кейс невозможен, это может привести к неожиданному поведению в будущем. Но если очень хочется, можно сделать это с конструкцией try-catch , где в блоке catch нет логики:
Опять же так делать не стоит. Исключение можно обработать и залоггировать, например, с помощью фреймворка Log4j , добавив зависимость в Gradle .
Также мы можем пробросить исключение дальше, теперь задача по обработке этого исключения будет у метода (или другого человека), вызвавшего данный метод с исключением:
Игнорирование исключений в Java
В этой статье я покажу, как игнорировать проверенные исключения в Java. Я начну с описания обоснования этого и общей схемы решения этой проблемы. Затем я представлю некоторые библиотеки для этой цели. С тегами java, обработка исключений, sneakythrows, lombok.
- Автор записи
В этой статье я покажу, как игнорировать проверенные исключения в Java. Я начну с описания обоснования этого и общей схемы решения этой проблемы. Затем я представлю некоторые библиотеки для этой цели.
Проверенные и Непроверенные исключения
В Java метод может заставить вызывающий его объект иметь дело с возникновением потенциальных исключений. Вызывающий может использовать предложение try/catch, где try содержит фактический код, а catch содержит код для выполнения при возникновении исключения.
В качестве альтернативы вызывающий абонент может передать это бремя своему “родительскому вызывающему абоненту”. Это может идти вверх до тех пор, пока не будет достигнут основной метод. Если основной метод также передает исключение, приложение завершит работу при возникновении исключения.
В случае исключения существует множество сценариев, в которых приложение не может продолжать работать и его необходимо остановить. Альтернативных путей нет. К сожалению, это означает, что Java заставляет нас писать код для ситуации, когда приложение больше не должно запускаться. Совершенно бесполезно!
Один из вариантов – свести к минимуму этот шаблонный код. Мы можем обернуть исключение в RuntimeException, которое является непроверенным исключением. Это приводит к тому, что, даже если приложение по-прежнему выходит из строя, нам не нужно предоставлять какой-либо код обработки.
Мы ни в коем случае не регистрируем исключение и не позволяем приложению продолжать работу, как будто ничего не произошло. Это возможно, но похоже на открытие ящика Пандоры.
Мы вызываем исключения, мы должны написать дополнительный код для “проверенных исключений”, а другие исключения типа RuntimeException “непроверенные исключения”.
Зачем вообще проверять исключения?
Мы можем найти множество проверенных исключений в сторонних библиотеках и даже в самой библиотеке классов Java. Причина вполне очевидна. Поставщик библиотеки не может предсказать, в каком контексте разработчик будет использовать свой код.
По логике вещей, они не знают, есть ли у нашего приложения альтернативные пути. Поэтому они оставляют решение за нами. Их ответственность заключается в том, чтобы “помечать” методы, которые потенциально могут вызывать исключения. Эти ярлыки дают нам возможность предпринять ответные действия.
Хорошим примером является подключение к базе данных. Поставщик библиотеки помечает метод извлечения соединения исключением. Если мы используем базу данных в качестве кэша, мы можем отправлять ваши запросы непосредственно в нашу основную базу данных. Это альтернативный путь.
Если наша база данных не является кэшем, приложение не сможет продолжать работать. И это нормально, если приложение выходит из строя:
Потерянная база данных
Давайте применим наш теоретический пример к реальному коду:
База данных не используется в качестве кэша. В случае потери соединения нам необходимо немедленно остановить приложение.
Как описано выше, мы преобразуем исключение Db ConnectionException в исключение RuntimeException.
Требуемый код относительно подробный и всегда один и тот же. Это создает много дублирования и снижает удобочитаемость.
Оболочка RuntimeException
Мы можем написать функцию, чтобы упростить это. Он должен обернуть исключение RuntimeException поверх некоторого кода и вернуть значение. Мы не можем просто передавать код на Java. Функция должна быть частью класса или интерфейса. Что-то вроде этого:
Перенос RuntimeException был извлечен в свой собственный класс. С точки зрения разработки программного обеспечения это может быть более элегантным решением. Тем не менее, учитывая объем кода, мы вряд ли можем сказать, что ситуация улучшилась.
С лямбдами Java 8 все стало проще. Если у нас есть интерфейс только с одним методом, то мы просто пишем конкретный код этого метода. Компилятор делает все остальное за нас. Ненужный или “синтаксический сахарный код” для создания определенного или анонимного класса больше не требуется. Это основной вариант использования лямбд.
В Java 8 наш приведенный выше пример выглядит следующим образом:
Разница совершенно очевидна. Код более лаконичен.
Исключения в Streams & Co.
RuntimeException Wrappable – это очень общий интерфейс. Это просто функция, которая возвращает значение. Варианты использования этой функции или ее вариаций появляются повсюду. Для нашего удобства Библиотека классов Java имеет встроенный набор таких общих интерфейсов. Они находятся в пакете java.util.function |/и более известны как «Функциональные интерфейсы». Наш RuntimeException Wrappable аналогичен java.util.function. Поставщик .
Эти интерфейсы являются предпосылкой мощного потока, дополнительных и других функций, которые также являются частью Java 8. В частности, Stream поставляется с множеством различных методов обработки коллекций. Многие из этих методов имеют “Функциональный интерфейс” в качестве параметра.
Давайте быстро поменяем вариант использования. У нас есть список строк URL, которые мы хотим отобразить в список объектов типа java.net.URL.
Следующий код не компилируется :
Существует большая проблема, когда дело доходит до исключений. Интерфейсы, определенные в java.util.function , не генерируют исключений. Вот почему наш метод createUrl не имеет той же сигнатуры, что и java.util.function. Функция , которая является параметром метода map.
Что мы можем сделать, так это записать блок try/catch внутри лямбда-выражения:
Это компилируется, но тоже выглядит не очень хорошо. Теперь мы можем сделать еще один шаг и написать функцию-оболочку с новым интерфейсом, похожим на RuntimeException Wrappable :
И примените его к нашему примеру потока:
Отлично! Теперь у нас есть решение, где мы можем:
- запуск кода без перехвата проверенных исключений,
- используйте лямбды, вызывающие исключения, в потоке, необязательно и т.д. ##SneakyThrow в помощь Библиотека SneakyThrow позволяет пропустить копирование и вставку фрагментов кода сверху. Полное раскрытие информации: я автор.
SneakyThrow поставляется с двумя статическими методами. Один запускает код без перехвата проверенных исключений. Другой метод переносит лямбда-выражение, вызывающее исключение, в один из функциональных интерфейсов:
Альтернативные библиотеки
Функция Метания
В отличие от SneakyThrow, функция Throwing не может выполнять код напрямую. Вместо этого мы должны обернуть его в Поставщика и впоследствии позвонить Поставщику. Этот подход может быть более подробным, чем SneakyThrow.
Если у вас есть несколько непроверенных функциональных интерфейсов в одном классе, то вы должны написать полное имя класса с каждым статическим методом. Это связано с тем, что unchecked не работает с перегрузкой метода.
С другой стороны, функция метания предоставляет вам больше возможностей, чем SneakyThrow. Вы можете определить конкретное исключение, которое хотите обернуть. Также возможно, что ваша функция возвращает необязательный параметр, он же “подъем”.
Я разработал SneakyThrow как самоуверенную оболочку функции Throwing.
Vavr, он же JavaSlang, является еще одной альтернативой. В отличие от функции SneakyThrow и Throwing, она предоставляет полный набор полезных функций, которые расширяют функциональность Java.
Например, он поставляется с сопоставлением шаблонов, кортежами, собственным потоком и многим другим. Если вы еще не слышали о нем, на него определенно стоит посмотреть. Приготовьтесь потратить некоторое время на то, чтобы полностью понять его потенциал.
Проект Ломбок
Такой список библиотек не будет полным без упоминания Ломбока. Как и Vavr, он предлагает гораздо больше функциональности, чем просто перенос проверенных исключений. Это генератор кода для шаблонного кода, который создает полноценные Java-компоненты, объекты Builder, экземпляры logger и многое другое.
Ломбок достигает своих целей с помощью манипуляций с байт-кодом. Поэтому нам требуется дополнительный плагин в нашей IDE.
@SneakyThrows – это аннотация Ломбока для преобразования функции с проверяемым исключением в функцию, которая этого не делает. Этот подход не зависит от использования лямбд, поэтому вы можете использовать его для всех случаев. Это наименее подробная библиотека.
Пожалуйста, имейте в виду, что Lombok манипулирует байт-кодом, что может вызвать проблемы с другими инструментами, которые вы можете использовать.
How to ignore Exceptions in Java
I would like to execute: test.setSomething2(0); even if test.setSomething(0) (the line above it) throws an exception. Is there a way to do this OTHER than:
I have a lot of test.setSomething’s in a row and all of them could throw Exceptions. If they do, I just want to skip that line and move to the next one.
For clarification, I don’t care if it throws an Exception, and I can’t edit the source code of the code which throws this exception.
THIS IS A CASE WHERE I DON’T CARE ABOUT THE EXCEPTIONS (please don’t use universally quantified statements like «you should never ignore Exceptions»). I am setting the values of some Object. When I present the values to a user, I do null checks anyway, so it doesn’t actually matter if any of the lines of code execute.
9 лучших практик для обработки исключений в Java
Независимо от того, новичок вы или профессионал, всегда полезно освежить в памяти методы обработки исключений, чтобы убедиться, что вы и ваша команда можете справиться с проблемами.
Обработка исключений в Java — непростая тема. Новичкам сложно понять, и даже опытные разработчики могут часами обсуждать, как и какие исключения следует создавать или обрабатывать.
Вот почему у большинства команд разработчиков есть собственный набор правил их использования. И если вы новичок в команде, вас может удивить, насколько эти правила могут отличаться от тех, которые вы использовали раньше.
Тем не менее, есть несколько передовых практик, которые используются большинством команд. Вот 9 самых важных из них, которые помогут вам начать работу или улучшить обработку исключений.
1. Освободите ресурсы в блоке finally или используйте инструкцию «Try-With-Resource»
Довольно часто вы используете ресурс в своем блоке try, например InputStream, который вам нужно закрыть позже. Распространенной ошибкой в таких ситуациях является закрытие ресурса в конце блока try.
Проблема в том, что этот подход работает отлично до тех пор, пока не генерируется исключение. Все операторы в блоке try будут выполнены, и ресурс будет закрыт.
Но вы не зря добавили блок try. Вы вызываете один или несколько методов, которые могут вызвать исключение, или, может быть, вы сами вызываете исключение. Это означает, что вы можете не дойти до конца блока try. И как следствие, вы не закроете ресурсы.
Поэтому вам следует поместить весь код очистки в блок finally или использовать оператор try-with-resource.
Используйте блок Finally
В отличие от последних нескольких строк вашего блока try, блок finally всегда выполняется. Это происходит либо после успешного выполнения блока try, либо после обработки исключения в блоке catch. Благодаря этому вы можете быть уверены, что освободите все захваченные ресурсы.
Оператор Java 7 «Try-With-Resource»
Другой вариант — это оператор try-with-resource, который я объяснил более подробно во введении в обработку исключений Java.
Вы можете использовать его, если ваш ресурс реализует интерфейс AutoCloseable. Это то, что делает большинство стандартных ресурсов Java. Когда вы открываете ресурс в предложении try, он автоматически закрывается после выполнения блока try или обработки исключения.
2. Конкретные исключения предпочтительнее
Чем конкретнее исключение, которое вы генерируете, тем лучше. Всегда помните, что коллеге, который не знает вашего кода, а может быть, и вам через несколько месяцев, необходимо вызвать ваш метод и обработать исключение.
Поэтому постарайтесь предоставить им как можно больше информации. Это упрощает понимание вашего API. В результате вызывающий ваш метод сможет лучше обработать исключение или избежать его с помощью дополнительной проверки.
Поэтому всегда старайтесь найти класс, который лучше всего подходит для вашего исключительного события, например, генерируйте NumberFormatException вместо IllegalArgumentException. И избегайте создания неспецифического исключения.
3. Документируйте определенные вами исключения
Каждый раз, когда вы определяете исключение в сигнатуре вашего метода, вы также должны задокументировать его в своем Javadoc. Это преследует ту же цель, что и предыдущая передовая практика: предоставить вызывающему как можно больше информации, чтобы он мог избежать или обработать исключение.
Итак, не забудьте добавить объявление @throws в свой Javadoc и описать ситуации, которые могут вызвать исключение.
4. Генерирование исключений с описательными сообщениями
Идея, лежащая в основе этой передовой практики, аналогична двум предыдущим. Но на этот раз вы не предоставляете информацию вызывающей стороне вашего метода. Сообщение об исключении читают все, кто должен понимать, что произошло, когда исключение было зарегистрировано в файле журнала или в вашем инструменте мониторинга.
Следовательно, он должен как можно точнее описать проблему и предоставить наиболее актуальную информацию для понимания исключительного события.
Не поймите меня неправильно; вы не должны писать абзац текста. Но вам следует объяснить причину исключения в 1-2 коротких предложениях. Это помогает вашей группе эксплуатации понять серьезность проблемы, а также упрощает анализ любых инцидентов, связанных с обслуживанием.
Если вы выберете конкретное исключение, его имя класса, скорее всего, уже будет описывать тип ошибки. Таким образом, вам не нужно предоставлять много дополнительной информации. Хорошим примером этого является NumberFormatException. Оно вызывается конструктором класса java.lang.Long, когда вы предоставляете String в неправильном формате.
Название класса NumberFormatException уже говорит вам о типе проблемы. Его сообщение должно содержать только строку ввода, которая вызвала проблему. Если имя класса исключения не так выразительно, вам необходимо предоставить необходимую информацию в сообщении.
5. Сначала перехватите наиболее конкретное исключение
Большинство IDE помогут вам в этой лучшей практике. Они сообщают о недостижимом блоке кода, когда вы сначала пытаетесь перехватить менее конкретное исключение.
Проблема в том, что выполняется только первый блок catch, соответствующий исключению. Итак, если вы сначала поймаете IllegalArgumentException, вы никогда не достигнете блока catch, который должен обрабатывать более конкретное NumberFormatException, потому что это подкласс IllegalArgumentException.
Всегда сначала перехватывайте наиболее конкретный класс исключения и добавляйте менее конкретные блоки перехвата в конец вашего списка.
Пример такого оператора try-catch представлен в следующем фрагменте кода. Первый блок catch обрабатывает все NumberFormatException, а второй — все IllegalArgumentException, которые не являются NumberFormatException.
6. Не перехватывайте Throwable
Throwable — это суперкласс всех исключений и ошибок. Вы можете использовать его в предложении catch, но никогда не должны этого делать!
Если вы используете Throwable в предложении catch, он не только перехватит все исключения; он также перехватит все ошибки. JVM выдает ошибки, чтобы указать на серьезные проблемы, которые не предназначены для обработки приложением. Типичными примерами этого являются OutOfMemoryError или StackOverflowError. И то, и другое вызвано ситуациями, которые находятся вне контроля приложения и не могут быть обработаны.
Итак, лучше не перехватывайте Throwable, если вы не абсолютно уверены, что находитесь в исключительной ситуации, в которой вы можете или обязаны обрабатывать ошибку.
7. Не игнорируйте исключения
Вы когда-нибудь анализировали отчет об ошибке, в котором выполнялась только первая часть вашего сценария использования?
Часто это вызвано игнорируемым исключением. Разработчик, вероятно, был уверен, что оно никогда не будет вызвано, и добавил блок catch, который не обрабатывает и не регистрирует его. И когда вы найдете этот блок, вы, скорее всего, даже найдете один из известных комментариев «Этого никогда не будет».
Что ж, возможно, вы анализируете проблему, в которой произошло невозможное.
Поэтому, пожалуйста, никогда не игнорируйте исключения. Вы не знаете, как код изменится в будущем. Кто-то может удалить проверку, которая предотвратила исключительное событие, не осознавая, что это создает проблему. Или код, который генерирует исключение, изменяется и теперь генерирует несколько исключений одного и того же класса, а вызывающий код не предотвращает их все.
Вы должны хотя бы написать сообщение в журнале, сообщающее всем, что произошло немыслимое и что кто-то должен это проверить.
8. Не пишите в лог сгенерированные исключения
Это, вероятно, наиболее часто игнорируемая передовая практика в списке. Вы можете найти множество фрагментов кода и даже библиотек, в которых исключение перехватывается, регистрируется и повторно генерируется.
Может показаться интуитивно понятным регистрировать исключение, когда оно произошло, а затем повторно генерировать его, чтобы вызывающий мог обработать его соответствующим образом. Но, в таком случае, приложение будет писать в лог несколько сообщений об ошибках для одного и того же исключения.
Повторные сообщения также не добавляют никакой информации. Как объясняется в лучшей практике №4, сообщение об исключении должно описывать исключительное событие. А трассировка стека сообщает вам, в каком классе, методе и строке было сгенерировано исключение.
Если вам нужно добавить дополнительную информацию, вы должны перехватить исключение и обернуть его в пользовательское. Но обязательно следуйте передовой практике номер 9.
Итак, перехватывайте исключение, только если вы хотите его обработать. В противном случае укажите это в сигнатуре метода и позвольте вызывающей стороне позаботиться об этом.
9. Оберните исключение, не обрабатывая его
Иногда лучше поймать стандартное исключение и превратить его в настраиваемое. Типичным примером такого исключения является бизнес-исключение для конкретного приложения или платформы. Это позволяет вам добавлять дополнительную информацию, а также вы можете реализовать специальную обработку для вашего класса исключения.
Когда вы это сделаете, обязательно установите исходное исключение в качестве причины. Класс Exception предоставляет определенные методы конструктора, которые принимают Throwable в качестве параметра. В противном случае вы потеряете трассировку стека и сообщение об исходном исключении, что затруднит анализ исключительного события, вызвавшего ваше исключение.
Резюме
Как вы видели, есть много разных вещей, которые вы должны учитывать, когда генерируете или перехватываете исключение. Большинство из них имеют цель улучшить читаемость вашего кода или удобство использования вашего API.
Чаще всего исключения являются одновременно механизмом обработки ошибок и средством связи. Поэтому вам следует обязательно обсудить передовые практики и правила, которые вы хотите применять, со своими коллегами, чтобы все понимали общие концепции и использовали их одинаково.