Является ли блокfinally без блока catch анти-шаблоном Java?
-
03-07-2019 - |
Вопрос
У меня только что был довольно болезненный опыт устранения неполадок в коде, который выглядел так:
try {
doSomeStuff()
doMore()
} finally {
doSomeOtherStuff()
}
Эту проблему было трудно устранить, поскольку doSomeStuff() выдал исключение, что, в свою очередь, привело к тому, что doSomeOtherStuff() также выдало исключение.Второе исключение (выброшенное блокомfinally) было выброшено в мой код, но у него не было дескриптора первого исключения (выброшенного из doSomeStuff()), которое и было настоящей основной причиной проблемы.
Если бы в коде было сказано это, проблема была бы очевидна:
try {
doSomeStuff()
doMore()
} catch (Exception e) {
log.error(e);
} finally {
doSomeOtherStuff()
}
Итак, мой вопрос заключается в следующем:
Является ли блокfinally, используемый без какого-либо блока catch, известным антишаблоном Java?(Конечно, это не совсем очевидный подкласс очевидно известного антишаблона «Не пожирайте исключения!»)
Решение
В общем нет, это не антипаттерн.Цель блоков, наконец, состоит в том, чтобы убедиться, что все очищено независимо от того, выдано исключение или нет.Весь смысл обработки исключений заключается в том, что, если вы не можете с ними справиться, вы позволяете им перейти к тому, кто может, посредством относительно чистой обработки исключений внеполосной сигнализации.Если вам нужно убедиться, что все будет очищено в случае возникновения исключения, но вы не можете правильно обработать исключение в текущей области, то это именно то, что нужно сделать.Возможно, вам просто стоит быть немного осторожнее и следить за тем, чтобы ваш блокfinally не выдал ошибку.
Другие советы
Я думаю, что настоящий «анти-паттерн» здесь — это делать что-то в finally
блок, который можно бросить, не имея возможности поймать.
Нисколько.
Что не так, так это код внутри файлаfinally.
Помните, что, наконец, всегда будет выполняться, и просто рискованно (как вы только что стали свидетелями) помещать туда что-то, что может вызвать исключение.
Нет абсолютно ничего плохого в попытке с окончанием и без улова.Учтите следующее:
InputStream in = null;
try {
in = new FileInputStream("file.txt");
// Do something that causes an IOException to be thrown
} finally {
if (in != null) {
try {
in.close();
} catch (IOException e) {
// Nothing we can do.
}
}
}
Если генерируется исключение, и этот код не знает, как с ним справиться, тогда исключение должно подняться по стеку вызовов вызывающей стороне.В этом случае мы все еще хотим очистить поток, поэтому я думаю, что имеет смысл иметь блок try без catch.
Я думаю, что это далеко не антишаблон, и я делаю это очень часто, когда важно освободить ресурсы, полученные во время выполнения метода.
Когда я имею дело с дескрипторами файлов (для записи), я очищаю поток перед его закрытием с помощью метода IOUtils.closeQuietly, который не генерирует исключений:
OutputStream os = null;
OutputStreamWriter wos = null;
try {
os = new FileOutputStream(...);
wos = new OutputStreamWriter(os);
// Lots of code
wos.flush();
os.flush();
finally {
IOUtils.closeQuietly(wos);
IOUtils.closeQuietly(os);
}
Мне нравится это делать по следующим причинам:
- Игнорировать исключение при закрытии файла не совсем безопасно — если есть байты, которые еще не были записаны в файл, то файл может находиться не в том состоянии, которого ожидал вызывающий объект;
- Таким образом, если во время методаlush() возникает исключение, оно будет передано вызывающему объекту, но я все равно проверю, что все файлы закрыты.Метод IOUtils.closeQuietly(...) менее многословен, чем соответствующий метод try...ловить ...игнорировать меня блокировать;
- При использовании нескольких потоков вывода важен порядок использования методаlush().Потоки, созданные путем передачи других потоков в качестве конструкторов, должны быть очищены в первую очередь.То же самое справедливо и для метода close(), но, на мой взгляд, методlush() более понятен.
Я бы сказал, что блок try без блока catch — это антишаблон.Высказывание «Не делай наконец-то без подвоха» — это разновидность фразы «Не пытайся без подвоха».
Я использую try/finally в следующей форме:
try{
Connection connection = ConnectionManager.openConnection();
try{
//work with the connection;
}finally{
if(connection != null){
connection.close();
}
}
}catch(ConnectionException connectionException){
//handle connection exception;
}
Я предпочитаю это try/catch/finally (+ вложенный try/catch вfinally).Я думаю, что это более лаконично, и я не дублирую улов (исключение).
try {
doSomeStuff()
doMore()
} catch (Exception e) {
log.error(e);
} finally {
doSomeOtherStuff()
}
Не делай этого и ты...вы просто скрыли еще больше ошибок (ну не совсем скрыли их...но усложняло борьбу с ними.Когда вы перехватываете Exception, вы также перехватываете любые исключения RuntimeException (например, NullPointer и ArrayIndexOutOfBounds).
В общем, перехватывайте те исключения, которые вам нужно перехватить (проверенные исключения), и обрабатывайте остальные во время тестирования.Исключения RuntimeExceptions предназначены для использования при ошибках программиста, а ошибки программиста — это вещи, которые не должны происходить в правильно отлаженной программе.
На мой взгляд, дело скорее в том, что finally
с catch
указать на какую-то проблему.Идиома ресурса очень проста:
acquire
try {
use
} finally {
release
}
В Java вы можете получить исключение практически где угодно.Часто при получении выдается проверенное исключение, разумный способ справиться с этим — установить ловушку вокруг количества.Не пытайтесь использовать отвратительную проверку нуля.
Если вы собираетесь быть по-настоящему анальным, вам следует отметить, что среди исключений существуют подразумеваемые приоритеты.Например, ThreadDeath должен уничтожить все, независимо от того, происходит ли это в результате приобретения/использования/выпуска.Правильно распоряжаться этими приоритетами некрасиво.
Поэтому абстрагируйте обработку ресурсов с помощью идиомы «Выполнение вокруг».
Try/Finally — это способ правильно освободить ресурсы.Код в блоке Final НИКОГДА не должен выдавать исключение, поскольку он должен воздействовать только на ресурсы или состояние, которые были получены ДО входа в блок try.
Кроме того, я думаю, что log4J почти антипаттерн.
ЕСЛИ ВЫ ХОТИТЕ ПРОВЕРИТЬ РАБОТАЮЩУЮ ПРОГРАММУ, ИСПОЛЬЗУЙТЕ ПОДХОДЯЩИЙ ИНСТРУМЕНТ ПРОВЕРКИ (т. е.отладчик, IDE или, в крайнем смысле, программа для создания байт-кода, но НЕ РАЗМЕЩАЙТЕ ЗАЯВЛЕНИЯ РЕГИСТРАЦИИ В КАЖДЫХ НЕСКОЛЬКИХ СТРОКАХ!).
В двух приведенных вами примерах первый выглядит правильно.Второй включает код регистратора и содержит ошибку.Во втором примере вы подавляете исключение, если оно выдается первыми двумя операторами (т. е.вы ловите его и регистрируете, но не выбрасываете повторно.Это то, что я считаю очень распространенным при использовании log4j, и это настоящая проблема при разработке приложений.По сути, с вашим изменением вы приводите к сбою программы таким образом, что системе будет очень трудно справиться, поскольку вы в основном продвигаетесь вперед, как если бы у вас никогда не было исключения (что-то вроде VB Basic при ошибке возобновляет следующую конструкцию).
try-finally
может помочь вам сократить код копирования и вставки в случае, если метод имеет несколько return
заявления.Рассмотрим следующий пример (Android Java):
boolean doSomethingIfTableNotEmpty(SQLiteDatabase db) {
Cursor cursor = db.rawQuery("SELECT * FROM table", null);
if (cursor != null) {
try {
if (cursor.getCount() == 0) {
return false;
}
} finally {
// this will get executed even if return was executed above
cursor.close();
}
}
// database had rows, so do something...
return true;
}
Если бы не было finally
пункт, вам, возможно, придется написать cursor.close()
дважды:незадолго до этого return false
а также после окружающих if
пункт.
Я думаю, что попытка без улова — это антишаблон.Использование try/catch для обработки исключительный условия (ошибки ввода-вывода файлов, тайм-аут сокета и т. д.) не являются анти-шаблоном.
Если вы используете try/finally для очистки, рассмотрите вместо этого блок using.