Является ли блок finally без блока catch анти-паттерном Java? - PullRequest
44 голосов
/ 02 марта 2009

У меня был довольно болезненный опыт устранения неполадок при поиске и устранении неисправностей в некотором коде, который выглядел следующим образом:

try {
   doSomeStuff()
   doMore()
} finally {
   doSomeOtherStuff()
}

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

Если бы код сказал это вместо этого, проблема была бы очевидна:

try {
    doSomeStuff()
    doMore()
} catch (Exception e) {
    log.error(e);
} finally {
   doSomeOtherStuff()
}

Итак, мой вопрос таков:

Является ли блок finally без какого-либо блока catch известным анти-паттерном Java? (Это, безусловно, не совсем очевидный подкласс явно известного анти-паттерна «Не глотай исключения!»)

Ответы [ 12 ]

62 голосов
/ 02 марта 2009

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

26 голосов
/ 02 марта 2009

Я думаю, что настоящий "анти-паттерн" здесь делает что-то в finally блоке, который может бросить, не имея подвоха.

16 голосов
/ 02 марта 2009

Совсем нет.

Что не так, так это код внутри finally.

Помните, что, наконец, всегда выполняется, и просто рискованно (как вы только что засвидетельствовали) поместить что-то, что может вызвать исключение.

10 голосов
/ 02 марта 2009

Нет ничего плохого в том, чтобы попытаться наконец-то и без улова. Учтите следующее:

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 без перехвата.

5 голосов
/ 02 марта 2009

Я думаю, что это далеко не анти-паттерн, и это то, что я делаю очень часто, когда критично освобождать ресурсы, полученные во время выполнения метода.

Одна вещь, которую я делаю, когда имею дело с дескрипторами файлов (для записи), это очистка потока перед его закрытием с помощью метода 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);
}

Мне нравится так делать по следующим причинам:

  • Не совсем безопасно игнорировать исключение при закрытии файла - если есть байты, которые еще не были записаны в файл, то файл может быть не в том состоянии, которое ожидал бы вызывающий объект;
  • Итак, если исключение возникает во время метода flush (), оно будет передано вызывающей стороне, но я все равно позабочусь, чтобы все файлы были закрыты. Метод IOUtils.closeQuietly (...) менее многословен, чем соответствующий try ... catch ... ignore me block;
  • Если используется несколько выходных потоков, важен порядок для метода flush (). Потоки, созданные путем передачи других потоков в качестве конструкторов, должны быть сброшены первыми. То же самое относится и к методу close (), но, на мой взгляд, flush () более понятен.
1 голос
/ 02 марта 2009

На мой взгляд, более вероятно, что finally с catch указывает на какую-то проблему. Идиома ресурса очень проста:

acquire
try {
    use
} finally {
    release
}

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

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

Следовательно, абстрагируйте обработку ваших ресурсов от идиомы Execute Around.

1 голос
/ 02 марта 2009
try {
    doSomeStuff()
    doMore()
} catch (Exception e) {
    log.error(e);
} finally {
   doSomeOtherStuff()
}

Не делайте этого тоже ... вы просто спрятали больше ошибок (ну, не совсем, спрятали их ... но затруднили с ними работу. Когда вы ловите Exception, вы также ловите любой вид RuntimeException (например, NullPointer и ArrayIndexOutOfBounds).

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

1 голос
/ 02 марта 2009

Я использую try / finally в следующей форме:

try{
   Connection connection = ConnectionManager.openConnection();
   try{
       //work with the connection;
   }finally{
       if(connection != null){
          connection.close();           
       }
   }
}catch(ConnectionException connectionException){
   //handle connection exception;
}

Я предпочитаю это попробовать / поймать / наконец-то (+ вложенная попытка / поймать в наконец-то). Я думаю, что это более кратко, и я не дублирую выгоду (Исключение).

1 голос
/ 02 марта 2009

Я бы сказал, что блок try без блока catch является анти-паттерном. Выражение «У меня нет, наконец, без улова» является подмножеством «У меня нет попытки без улова».

0 голосов
/ 15 апреля 2014

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.

...