Статические анализаторы кода, такие как Fortify, «жалуются», когда в finally
блоке может возникнуть исключение , говоря это Using a throw statement inside a finally block breaks the logical progression through the try-catch-finally
. Обычно я согласен с этим. Но недавно я наткнулся на этот код:
SomeFileWriter writer = null;
try {
//init the writer
//write into the file
} catch (...) {
//exception handling
} finally {
if (writer!= null) writer.close();
}
Теперь, если writer
невозможно закрыть должным образом, writer.close()
метод выдаст исключение. Необходимо создать исключение, потому что (скорее всего) файл не был сохранен после записи.
Я мог бы объявить дополнительную переменную, установить ее, если произошла ошибка при закрытии, writer
и выдать исключение после блока finally. Но этот код работает нормально, и я не уверен, стоит ли менять его или нет.
Каковы недостатки создания исключения внутри finally
блока?
Ответы:
По сути, существуют
finally
пункты, обеспечивающие правильное освобождение ресурса. Однако, если исключение выдается внутри блока finally, эта гарантия исчезает. Хуже того, если ваш основной блок кода вызывает исключение, исключение, созданное в этомfinally
блоке, скрывает его. Похоже, что ошибка была вызвана вызовомclose
, а не реальной причиной.Некоторые люди следуют неприятному шаблону вложенных обработчиков исключений, проглатывая любые исключения, сгенерированные в
finally
блоке.В более старых версиях Java вы можете «упростить» этот код, поместив ресурсы в классы, которые делают эту «безопасную» очистку за вас. Мой хороший друг создает список анонимных типов, каждый из которых обеспечивает логику для очистки своих ресурсов. Затем его код просто перебирает список и вызывает метод dispose внутри
finally
блока.источник
То, что сказал Трэвис Паркс, верно, что исключения в
finally
блоке будут использовать любые возвращаемые значения или исключения изtry...catch
блоков.Если вы используете Java 7, проблема может быть решена с помощью блока try-with-resources . Согласно документам, пока ваш ресурс реализует
java.lang.AutoCloseable
(большинство авторов / читателей библиотек делают это сейчас), блок try-with-resources будет закрывать его для вас. Дополнительным преимуществом здесь является то, что любое исключение, возникающее при его закрытии, будет подавлено, позволяя пройти исходному возвращаемому значению или исключению.От
к
http://docs.oracle.com/javase/tutorial/essential/exceptions/tryResourceClose.html
источник
close()
. «Любое исключение, возникающее при закрытии, будет подавлено, что позволит передать исходное возвращаемое значение или исключение» - это просто неправда . Исключение изclose()
метода будет подавлено, только когда из блока try / catch будет сгенерировано другое исключение. Таким образом, еслиtry
блок не выдает никакого исключения, будет сгенерировано исключение изclose()
метода (и возвращаемое значение не будет возвращено). Его даже можно поймать из текущегоcatch
блока.Я думаю, что это то, что вам нужно будет решать в каждом конкретном случае. В некоторых случаях то, что говорит анализатор, верно в том смысле, что ваш код не так уж велик и нуждается в переосмыслении. Но могут быть и другие случаи, когда бросание или даже повторное бросание может быть лучшей вещью. Это не то, что вы можете поручить.
источник
Это будет скорее концептуальный ответ на вопрос, почему эти предупреждения могут существовать даже при использовании подхода «попробуй с ресурсами». К сожалению, это не то простое решение, на которое вы, возможно, надеетесь достичь.
Ошибка восстановления не может завершиться ошибкой
finally
моделирует поток управления после транзакции, который выполняется независимо от того, успешна ли транзакция или нет.В случае терпят неудачу,
finally
захватывает логик, которая выполняется в середине восстановления от ошибки, до того как он был полностью восстановлен (прежде чем мы достигнем нашегоcatch
назначения).Представьте себе концептуальные проблемы , которые он представляет , чтобы столкнуться с ошибкой в середине восстановления после ошибки.
Представьте себе сервер базы данных, на котором мы пытаемся совершить транзакцию, и она не удается в середине (скажем, серверу не хватило памяти в середине). Теперь сервер хочет откатить транзакцию до точки, как будто ничего не произошло. Однако представьте, что в процессе отката возникает еще одна ошибка. Теперь мы получаем половинную транзакцию в базе данных - атомарность и неделимый характер транзакции теперь нарушены, и целостность базы данных будет нарушена.
Эта концептуальная проблема существует в любом языке, который имеет дело с ошибками, будь то C с ручным распространением кода ошибки, C ++ с исключениями и деструкторами или Java с исключениями и
finally
.finally
не может потерпеть неудачу в языках, которые предоставляют его таким же образом, деструкторы не могут потерпеть неудачу в C ++ в процессе обнаружения исключений.Единственный способ избежать этой концептуальной и сложной проблемы - убедиться, что процесс отката транзакций и освобождения ресурсов в середине не может встретить рекурсивное исключение / ошибку.
Таким образом, единственный безопасный дизайн здесь - это проект, который
writer.close()
не может потерпеть неудачу. В дизайне обычно есть способы избежать сценариев, когда такие вещи могут потерпеть неудачу во время восстановления, делая это невозможным.К сожалению, это единственный способ - восстановление после ошибки не может быть неудачным Самый простой способ обеспечить это - сделать так, чтобы функции «освобождения ресурсов» и «обратных побочных эффектов» не могли работать. Это нелегко - правильное восстановление после ошибок сложно, а также, к сожалению, сложно проверить. Но способ добиться этого - убедиться, что любые функции, которые «уничтожают», «закрывают», «выключают», «откатывают» и т. Д., Не могут столкнуться с внешней ошибкой в процессе, поскольку такие функции часто должны быть вызванным во время восстановления после существующей ошибки.
Пример: ведение журнала
Допустим, вы хотите регистрировать вещи внутри
finally
блока. Это часто будет огромной проблемой, если регистрация не может завершиться ошибкой . Ведение журнала почти наверняка может потерпеть неудачу, так как может потребоваться добавить больше данных в файл, и это может легко найти множество причин сбоя.Таким образом, решение здесь состоит в том, чтобы сделать так, чтобы любая функция регистрации, используемая в
finally
блоках, не могла бросить вызывающей стороне (она могла бы потерпеть неудачу, но она не бросит). Как мы можем это сделать? Если ваш язык позволяет выбрасывать в контексте finally, при условии, что есть вложенный блок try / catch, это был бы один из способов избежать выброса вызывающей стороне, проглатывая исключения и превращая их в коды ошибок, например, возможно, запись в журнал может быть выполнена в отдельном процесс или поток, который может выйти из строя отдельно и вне существующего стека восстановления после восстановления, раскручивается. Пока вы можете общаться с этим процессом без возможности столкнуться с ошибкой, это также будет безопасным для исключений, поскольку проблема безопасности существует только в этом сценарии, если мы рекурсивно выбрасываем из одного потока,В этом случае мы можем избежать неудачного ведения журнала при условии, что он не генерирует ошибку, так как невозможность войти в систему и ничего не делать - это не конец света (это не утечка каких-либо ресурсов или неспособность откатить побочные эффекты, например).
В любом случае, я уверен, что вы уже можете представить, как невероятно сложно действительно сделать программное обеспечение безопасным от исключений. Возможно, нет необходимости искать это в полной мере во всем, кроме самого критически важного программного обеспечения. Но стоит обратить внимание на то, как по-настоящему добиться безопасности исключений, поскольку даже авторы библиотек очень общего назначения часто возятся здесь и разрушают всю безопасность ваших приложений с помощью библиотеки.
SomeFileWriter
Если
SomeFileWriter
можно бросить внутрьclose
, то я бы сказал, что это вообще несовместимо с обработкой исключений, если только вы никогда не пытаетесь закрыть его в контексте, который включает восстановление из существующего исключения. Если код для него находится вне вашего контроля, мы могли бы быть SOL, но стоило бы уведомить авторов этой вопиющей проблемы безопасности исключений. Если это находится под вашим контролем, моя главная рекомендация - убедиться, что закрытие его не может завершиться неудачей любыми необходимыми средствами.Представьте себе, если операционная система не может закрыть файл. Теперь любая программа, которая пытается закрыть файл при завершении работы, не сможет завершить работу . Что мы должны делать сейчас, просто держать приложение открытым и в подвешенном состоянии (возможно, нет), просто утечь файловый ресурс и проигнорировать проблему (возможно, хорошо, если это не так критично)? Самый безопасный дизайн: сделайте так, чтобы невозможно было закрыть файл.
источник