Как наиболее элегантно окружить код командой try / catch

У меня часто возникают проблемы при использовании try-and-catch:

1) Некоторые переменные должны быть объявлены внутри скобок try, иначе они не будут в области видимости
2) В конечном итоге даже мой оператор return должен быть в скобках try, но тогда метод ничего не возвращает.

Как правильно решить эту проблему?

Пример метода, вызывающего эту проблему, приведен ниже. Необходимо обработать исключение FileNotFoundException и исключение IOException. Как сделать это наиболее элегантно?

public static String getContents (File file) {
      BufferedReader reader = new BufferedReader(new FileReader(file));
      String contents = new String();
      while (reader.ready())
        contents += reader.readLine();
      return contents;
    }

person Ankur    schedule 15.06.2009    source источник


Ответы (8)


Есть еще один вариант, если обработка исключений не обязательно должна происходить в методе getContents - добавить к методу предложение throws, чтобы метод генерировал исключение:

public static String getContents (File file)
    throws IOException, FileNotFoundException {

Таким образом, код, вызывающий метод, будет обрабатывать Exceptions, а не сам метод. В этом методе не будет необходимости в блоках _5 _ / _ 6_, если Exception будут переданы методам, которые его вызвали.

Это может быть или не быть желаемым способом справиться с ситуацией, в зависимости от ожидаемого поведения метода.

Изменить

Если подумать, то хорошей идеей может быть создание исключения из метода. Я думаю, что комментарий Д. Шоули, на мой взгляд, хорошо подытоживает: «обработка исключений должна означать обработку исключения только там, где это имеет смысл».

В этом случае кажется, что метод getContents получает содержимое указанного File и возвращает String вызывающей стороне.

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

Однако, если сам метод возвращает исключение вызывающей стороне, вызывающая сторона может выбрать соответствующую реакцию:

try {
    String contents = getContents(new File("input.file"));
} catch (IOException ioe) {
    // Perform exception handling for IOException.
} catch (FileNotFoundException fnfe) {
    // Inform user that file was not found.
    // Perhaps prompt the user for an alternate file name and try again?
}

Вместо того, чтобы метод setContents предлагал свой собственный протокол для уведомления о возникновении ошибки, вероятно, было бы лучше передать IOException и FileNotFoundException обратно вызывающей стороне метода, чтобы обработка исключений могла выполняться в месте, где соответствующий альтернативный действия могут иметь место.

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

person coobird    schedule 15.06.2009
comment
+1: обработка исключения должна означать обработку исключения только там, где это имеет смысл. Пусть распространение исключения должно быть предположением по умолчанию ИМХО. - person D.Shawley; 15.06.2009
comment
@ D.Shawley Хорошо, но в какой-то момент необходимо обработать исключение (если вы не защищаете, чтобы конечный пользователь видел красивый красный текст в stderr). На этом этапе вы должны использовать блок try / catch, который был исходной точкой вопрос, а не приведенный пример кода. - person Brandon Bodnar; 15.06.2009
comment
Это все еще помогает. Я всегда думал, что использование Throws - это плохо, поскольку оно просто передаёт ответственность. Приятно знать, что использование бросков - неплохая практика. - person Ankur; 15.06.2009

Вы можете справиться с этим следующим образом:

StringBuilder contents = new StringBuilder();
BufferedReader reader;

try {
   reader = new BufferedReader(new FileReader(file));

   while (reader.ready()) {
      contents.append(reader.readLine());
   }

} catch (FileNotFoundException fne) {
   log.warn("File Not Found", fne);
} catch (IOException ioe) {
   log.warn("IOException", ioe);
} 

return contents.toString();

Вам, вероятно, следует использовать StringBuilder в приведенном выше случае вместо String, что намного лучше с точки зрения производительности.

person Jon    schedule 15.06.2009
comment
Джон, вы молча вылавливаете исключения? Возможно, это не лучшая идея, если только это не намерение Анкура. Не будет индикации состояния ошибки. - person Robert Harvey; 15.06.2009
comment
К сожалению, целью было проиллюстрировать обработку переменных, а не исключений. Я исправил, несмотря ни на что ... - person Jon; 15.06.2009
comment
Если бы вы написали это до 1.5, вы бы использовали класс StringBuffer? Тогда какой код был бы лучшим исполнителем после версии 1.5? Я понятия не имею, потому что я не тестировал производительность ни одного из них. :) Кроме того, если вы действительно беспокоитесь о производительности, почему бы не читать страницы char [] непосредственно из FileReader, а не построчно через BufferedReader? - person Jordan Stewart; 15.06.2009
comment
Обратите внимание, что на производстве вам нужно будет повторно генерировать эти исключения после их регистрации, а не лгать вызывающему методу, сообщая ему, что файл пуст. Это, вероятно, вызовет каскад вторичных ошибок, которые могут привести к катастрофической системной ошибке (например, транзакция с медицинской базой данных будет зафиксирована, а не прервана). - person Jim Ferrans; 15.06.2009
comment
@Jordan: StringBuilder - лучший исполнитель. StringBuffer является потокобезопасным, поэтому его использование несколько дороже. StringBuilder не является потокобезопасным, но в 99% случаев это не имеет значения. - person Jim Ferrans; 15.06.2009
comment
@Jim: Я знаю, что StringBuilder не синхронизируется - я хотел сказать, что до тех пор, пока вы не проведете тест, любые ваши заявления о производительности не должны делаться без четких доказательств. Например. Взгляните на эти результаты, показывающие, что (в 1.5.0_02, на любом ПК, который он использовал, с написанным им кодом) начальная длина буфера имеет большее значение, чем используемый вами класс: javaspecialists.co.za/archive/Issue105.html - person Jordan Stewart; 16.06.2009
comment
@Jim: также сильно синхронизирован! = Потокобезопасный. Например. StringBuffer # append (StringBuffer) вызывает метод StringBuffer # getChars (..) своего аргумента StringBuffer. Т.е. исполняющий поток пытается получить две блокировки в определенном порядке. . . (т.е. возможность тупика) - person Jordan Stewart; 16.06.2009

public static String getContents (File file) {
    String contents = new String();
    BufferedReader reader = null;
    try {
        reader = new BufferedReader(new FileReader(file));
        while (reader.ready())
            contents += reader.readLine();
    }
    catch (FileNotFoundException ex) {
        // handle FileNotFoundException
    }
    catch (IOException ex) {
        // handle IOException
    }
    finally {
        if (reader != null) {
            try {
                reader.close();
            }
            catch (IOException ex) {
                // handle IOException
            }
        }
    }
    return contents;
}

Я добавил блок finally, чтобы закрыть ваш BufferedReader, хотя вы не сделали этого в своем коде. Я также предлагаю вам использовать StringBuilder вместо String конкатенации, но кто-то уже указал на это. Объявление и инициализация reader находится за пределами блока try только из-за добавленного мной блока finally; в противном случае ссылка reader могла быть объявлена ​​внутри блока try.

и я не обрабатывал исключения, я не думал, что это связано с вашим вопросом.

person cd1    schedule 15.06.2009
comment
+1 за то, что первым упомянул, что читатель должен быть закрыт - person Andreas Dolk; 15.06.2009

Вы можете попробовать переместить оператор return в блок finally.

person Suvesh Pratapa    schedule 15.06.2009
comment
Пожалуйста, не делайте этого - блоки finally проглатывают любые брошенные и неперехваченные исключения. - person Robert Munteanu; 16.06.2009

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

Что касается оператора return, то, если вы воспользуетесь моим предложением выше, вы можете просто вернуться после блока try / catch.

Поэтому, если бы я использовал нулевое возвращаемое значение, чтобы указать на ошибку, я бы сделал

public static String getContents (File file) {
    String contents = null;
    try {        
        BufferedReader reader = new BufferedReader(new FileReader(file));
        contents = new String();
        while (reader.ready())
            contents += reader.readLine();
    } catch (Exception e) {
        // Error Handling
    }
    return contents;
}
person Brandon Bodnar    schedule 15.06.2009

Здесь вам не хватает простой утилиты, которая значительно упрощает очистку:

public static void closeAndLog(Closable c) {
    if ( c == null )
        return;

    try { 
        c.close() 
    } catch ( IOException e) {
        LOGGER.warn("Failed closing " + c +, e);
    }
}

Таким образом, ваш код может стать:

public static String getContents (File file) throws IOException {

    BufferedReader r = null;

    try { 
        r = new BufferedReader(...);
        // do stuff
    } finally {
        closeAndLog(r);
    }
}
person Robert Munteanu    schedule 15.06.2009

IMHO у вас есть два способа правильно справиться с исключениями (здесь IOException и FileNotFoundException):

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

Чтобы получить полезный совет по поводу исключений, см. Также: Почему люди, использующие Java, часто молча потребляют исключения ?

person Michael Zilbermann    schedule 15.06.2009

Начиная с Java 7, вы можете использовать try-with-resources для убедитесь, что ваши ресурсы закрываются правильно и «автоматически». Все, что вам нужно, это объект, реализующий java.lang.AutoCloseable, который делает BufferedReader. На самом деле в документации есть следующий пример:

try (BufferedReader br = new BufferedReader(new FileReader(path))) {
    return br.readLine();
}
person Rodrigue    schedule 30.09.2015