Бросить исключение внутри наконец

27

Статические анализаторы кода, такие как 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блока?

superM
источник
4
Если это Java, и вы можете использовать Java 7, проверьте, могут ли блоки ARM решить вашу проблему.
Landei
@Landei, это решает проблему, но, к сожалению, мы не используем Java 7.
superM
Я бы сказал, что код, который вы показали, не является «использованием оператора throw внутри блока finally», и поэтому логическая последовательность вполне приемлема.
Майк
@Mike, я использовал стандартную сводку, которую показывает Fortify, но прямо или косвенно внутри наконец-то возникло исключение.
СуперМ
К сожалению, блок try-with-resources также обнаруживается Fortify как исключение, созданное внутри, наконец ... оно слишком умное, черт побери ... все еще не уверен, как его преодолеть, казалось, что причина для попытки с ресурсами заключалась в том, чтобы обеспечить закрытие ресурсы, наконец, и теперь каждое такое заявление сообщается Fortify как угроза безопасности ..
mmona

Ответы:

18

По сути, существуют finallyпункты, обеспечивающие правильное освобождение ресурса. Однако, если исключение выдается внутри блока finally, эта гарантия исчезает. Хуже того, если ваш основной блок кода вызывает исключение, исключение, созданное в этом finallyблоке, скрывает его. Похоже, что ошибка была вызвана вызовом close, а не реальной причиной.

Некоторые люди следуют неприятному шаблону вложенных обработчиков исключений, проглатывая любые исключения, сгенерированные в finallyблоке.

SomeFileWriter writer = null; 
try { 
     //init the writer
     //write into the file
} finally {
    if (writer!= null) {
        try {
            writer.close();
        } catch (...) {
        }
    }
}

В более старых версиях Java вы можете «упростить» этот код, поместив ресурсы в классы, которые делают эту «безопасную» очистку за вас. Мой хороший друг создает список анонимных типов, каждый из которых обеспечивает логику для очистки своих ресурсов. Затем его код просто перебирает список и вызывает метод dispose внутри finallyблока.

Трэвис Паркс
источник
1
+1 Хотя я из тех, кто использует этот противный код. Но, к моему оправданию, я скажу, что я делаю это не всегда, только когда исключение не критично.
СуперМ
2
Я тоже этим занимаюсь. Иногда создание какого-либо целого менеджера ресурсов (анонимного или нет) умаляет цель кода.
Трэвис Паркс
+1 от меня тоже; Вы в основном сказали именно то, что я собирался, но лучше. Стоит отметить, что некоторые реализации Stream в Java на самом деле не могут генерировать исключения на close (), но интерфейс объявляет это, потому что некоторые из них делают. Таким образом, в некоторых случаях вы можете добавить блок catch, который на самом деле никогда не понадобится.
vaughandroid
1
Что такого неприятного в использовании блока try / catch внутри блока finally? Я бы предпочел сделать это, а не раздувать весь мой код, поскольку нужно обернуть все мои соединения, потоки и т. Д., Чтобы они могли быть тихо закрыты, что само по себе создает новую проблему - не знать, закрывать ли соединение или поток ( или что-то еще) вызывает исключение - что-то, о чем вы действительно хотели бы знать или что-то делать ...
ban-geoengineering
10

То, что сказал Трэвис Паркс, верно, что исключения в finallyблоке будут использовать любые возвращаемые значения или исключения из try...catchблоков.

Если вы используете Java 7, проблема может быть решена с помощью блока try-with-resources . Согласно документам, пока ваш ресурс реализует java.lang.AutoCloseable(большинство авторов / читателей библиотек делают это сейчас), блок try-with-resources будет закрывать его для вас. Дополнительным преимуществом здесь является то, что любое исключение, возникающее при его закрытии, будет подавлено, позволяя пройти исходному возвращаемому значению или исключению.

От

FileWriter writer = null;
try {
  writer = new FileWriter("myFile.txt");
  writer.write("hello");
} catch(...) {
  // return/throw new exception
} finally {
  writer.close(); // an exception would consume the catch block's return/exception
}

к

try (FileWriter writer = new FileWriter("myFile.txt")) {
  writer.write("hello");
} catch(...) {
  // return/throw new exception, always gets returned even if writer fails to close
}

http://docs.oracle.com/javase/tutorial/essential/exceptions/tryResourceClose.html

clintonmonk
источник
1
Это иногда полезно. Но в случае, когда мне действительно нужно сгенерировать исключение, когда файл не может быть закрыт, этот подход скрывает проблемы.
СуперМ
1
@superM На самом деле, try-with-resources не скрывает исключение из close(). «Любое исключение, возникающее при закрытии, будет подавлено, что позволит передать исходное возвращаемое значение или исключение» - это просто неправда . Исключение из close()метода будет подавлено, только когда из блока try / catch будет сгенерировано другое исключение. Таким образом, если tryблок не выдает никакого исключения, будет сгенерировано исключение из close()метода (и возвращаемое значение не будет возвращено). Его даже можно поймать из текущего catchблока.
Руслан Стельмаченко
0

Я думаю, что это то, что вам нужно будет решать в каждом конкретном случае. В некоторых случаях то, что говорит анализатор, верно в том смысле, что ваш код не так уж велик и нуждается в переосмыслении. Но могут быть и другие случаи, когда бросание или даже повторное бросание может быть лучшей вещью. Это не то, что вы можете поручить.

drekka
источник
0

Это будет скорее концептуальный ответ на вопрос, почему эти предупреждения могут существовать даже при использовании подхода «попробуй с ресурсами». К сожалению, это не то простое решение, на которое вы, возможно, надеетесь достичь.

Ошибка восстановления не может завершиться ошибкой

finally моделирует поток управления после транзакции, который выполняется независимо от того, успешна ли транзакция или нет.

В случае терпят неудачу, finallyзахватывает логик, которая выполняется в середине восстановления от ошибки, до того как он был полностью восстановлен (прежде чем мы достигнем нашего catchназначения).

Представьте себе концептуальные проблемы , которые он представляет , чтобы столкнуться с ошибкой в середине восстановления после ошибки.

Представьте себе сервер базы данных, на котором мы пытаемся совершить транзакцию, и она не удается в середине (скажем, серверу не хватило памяти в середине). Теперь сервер хочет откатить транзакцию до точки, как будто ничего не произошло. Однако представьте, что в процессе отката возникает еще одна ошибка. Теперь мы получаем половинную транзакцию в базе данных - атомарность и неделимый характер транзакции теперь нарушены, и целостность базы данных будет нарушена.

Эта концептуальная проблема существует в любом языке, который имеет дело с ошибками, будь то C с ручным распространением кода ошибки, C ++ с исключениями и деструкторами или Java с исключениями и finally.

finally не может потерпеть неудачу в языках, которые предоставляют его таким же образом, деструкторы не могут потерпеть неудачу в C ++ в процессе обнаружения исключений.

Единственный способ избежать этой концептуальной и сложной проблемы - убедиться, что процесс отката транзакций и освобождения ресурсов в середине не может встретить рекурсивное исключение / ошибку.

Таким образом, единственный безопасный дизайн здесь - это проект, который writer.close()не может потерпеть неудачу. В дизайне обычно есть способы избежать сценариев, когда такие вещи могут потерпеть неудачу во время восстановления, делая это невозможным.

К сожалению, это единственный способ - восстановление после ошибки не может быть неудачным Самый простой способ обеспечить это - сделать так, чтобы функции «освобождения ресурсов» и «обратных побочных эффектов» не могли работать. Это нелегко - правильное восстановление после ошибок сложно, а также, к сожалению, сложно проверить. Но способ добиться этого - убедиться, что любые функции, которые «уничтожают», «закрывают», «выключают», «откатывают» и т. Д., Не могут столкнуться с внешней ошибкой в ​​процессе, поскольку такие функции часто должны быть вызванным во время восстановления после существующей ошибки.

Пример: ведение журнала

Допустим, вы хотите регистрировать вещи внутри finallyблока. Это часто будет огромной проблемой, если регистрация не может завершиться ошибкой . Ведение журнала почти наверняка может потерпеть неудачу, так как может потребоваться добавить больше данных в файл, и это может легко найти множество причин сбоя.

Таким образом, решение здесь состоит в том, чтобы сделать так, чтобы любая функция регистрации, используемая в finallyблоках, не могла бросить вызывающей стороне (она могла бы потерпеть неудачу, но она не бросит). Как мы можем это сделать? Если ваш язык позволяет выбрасывать в контексте finally, при условии, что есть вложенный блок try / catch, это был бы один из способов избежать выброса вызывающей стороне, проглатывая исключения и превращая их в коды ошибок, например, возможно, запись в журнал может быть выполнена в отдельном процесс или поток, который может выйти из строя отдельно и вне существующего стека восстановления после восстановления, раскручивается. Пока вы можете общаться с этим процессом без возможности столкнуться с ошибкой, это также будет безопасным для исключений, поскольку проблема безопасности существует только в этом сценарии, если мы рекурсивно выбрасываем из одного потока,

В этом случае мы можем избежать неудачного ведения журнала при условии, что он не генерирует ошибку, так как невозможность войти в систему и ничего не делать - это не конец света (это не утечка каких-либо ресурсов или неспособность откатить побочные эффекты, например).

В любом случае, я уверен, что вы уже можете представить, как невероятно сложно действительно сделать программное обеспечение безопасным от исключений. Возможно, нет необходимости искать это в полной мере во всем, кроме самого критически важного программного обеспечения. Но стоит обратить внимание на то, как по-настоящему добиться безопасности исключений, поскольку даже авторы библиотек очень общего назначения часто возятся здесь и разрушают всю безопасность ваших приложений с помощью библиотеки.

SomeFileWriter

Если SomeFileWriterможно бросить внутрь close, то я бы сказал, что это вообще несовместимо с обработкой исключений, если только вы никогда не пытаетесь закрыть его в контексте, который включает восстановление из существующего исключения. Если код для него находится вне вашего контроля, мы могли бы быть SOL, но стоило бы уведомить авторов этой вопиющей проблемы безопасности исключений. Если это находится под вашим контролем, моя главная рекомендация - убедиться, что закрытие его не может завершиться неудачей любыми необходимыми средствами.

Представьте себе, если операционная система не может закрыть файл. Теперь любая программа, которая пытается закрыть файл при завершении работы, не сможет завершить работу . Что мы должны делать сейчас, просто держать приложение открытым и в подвешенном состоянии (возможно, нет), просто утечь файловый ресурс и проигнорировать проблему (возможно, хорошо, если это не так критично)? Самый безопасный дизайн: сделайте так, чтобы невозможно было закрыть файл.


источник