Как правильно обработать IOException от close () - PullRequest
18 голосов
/ 13 января 2011

Классы ввода-вывода Java java.io.Reader, java.io.Writer, java.io.InputStream, java.io.OutpuStream и их различные подклассы имеют метод close(), который может выдавать IOException.

есть какой-либо консенсус относительно правильного способа обработки таких исключений?

Я часто видел рекомендации просто молча игнорировать их, но это кажется неправильным, и, по крайней мере, в случае ресурсов, открытых для записи, проблема при закрытииfile может означать, что непереработанные данные не могут быть записаны / отправлены.

С другой стороны, при чтении ресурсов мне совершенно неясно, почему close() может выдать и что с этим делать.

Так есть ли какая-либо стандартная рекомендация?

Смежный вопрос: Вызывает ли close когда-либо IOException? , но это больше о том, какие реализации действительно do throw, а не о том, как обрабатывать исключения.

Ответы [ 9 ]

7 голосов
/ 13 января 2011

Записать его.

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

Редактировать:
После дальнейшего изучения и прочтения других комментариев я бы сказал, что если вы действительно хотите с этим справиться, то вам нужно будет узнать подробности реализации.И наоборот, вам, вероятно, нужно знать детали реализации, чтобы решить, нужно ли вам ее обрабатывать.

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

4 голосов
/ 16 мая 2012

"Игнорирование или просто регистрация обычно плохая идея." Это не ответ на вопрос. Что делать с IOException от close ()? Просто выбросив его, вы еще больше продвигаете проблему, где с ней еще труднее справиться.

Theory

Чтобы ответить на ваш вопрос напрямую: если это действие ввода-вывода не выполнено, то, в зависимости от обстоятельств, вы можете

  • изменения отката (не оставляйте частично записанный файл на диске, удалите его)
  • повторить попытку (возможно, после отката)
  • игнорировать (и продолжить)
  • прервать текущее задание (отменить)

Вы часто можете видеть последние 3 в диалоговых окнах, показанных пользователю. Действительно, делегирование выбора пользователю возможно.

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

Практика

Немного обременительно работать с проверенными исключениями. Варианты возникают:

  • Преобразуйте их в RuntimeException, бросьте их и оставьте для обработки на достаточно высоком уровне

  • Продолжайте использовать проверенные исключения и страдайте синтаксической болью

  • Используйте более составные конструкции обработки ошибок, например, монаду ввода / вывода (на самом деле не доступно в Java, по крайней мере, не без смущающих скобок (см. http://apocalisp.wordpress.com/2008/05/16/thrower-functor/) и не с полной мощностью)

Подробнее о монаде IO

Когда вы возвращаете результат в контексте монады ввода-вывода, вы фактически не выполняете действие ввода-вывода, а скорее возвращаете описание этого действия. Зачем? У этих действий есть API, с помощью которого они хорошо сочетаются (по сравнению с бойней throws / try / catch).

Вы можете решить, хотите ли вы проверять обработку стиля исключений (с помощью вызова Option или Validation˙ in the return type), or dynamic style handling, with bracketing only on the final unsafePerformIO` (который фактически выполняет составленное действие ввода-вывода).

Чтобы получить представление, см. Мою суть Scala http://gist.github.com/2709535,, в которой размещен метод executeAndClose. Он возвращает как результат обработки ресурса (если имеется), так и незакрытый ресурс (если закрытие не удалось). Тогда пользователь решает, как обращаться с таким нераскрываемым ресурсом.

Может быть увеличено с Validation / ValidationNEL вместо Option, если нужно тоже одно / несколько актуальных исключений.

1 голос
/ 08 сентября 2018

Ваш код, который вызывает InputStream.close() или OutputStream.close(), является кодом высокого уровня, который делегирует классу InputStream или OutputStream более низкого уровня для выполнения вычислений высокого уровня.

Вызывать ли исключение

У вас есть только 3 варианта действий.

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

Итак, вам нужно спросить себя, не исключает ли исключение close() метод вашего , делающий то, что должен делать.

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

Я не знаю обстоятельств, при которых InputStream.close(), вызывающее исключение, может помешать успешному вычислению. Этот вызов close() естественно происходит после того, как вы закончили читать любые интересующие вас данные.

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

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

Регистрировать ли исключение

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

Сообщение о том, что close() не удалось, редко полезно. Как это может помочь пользователю принять решение? Если исключение не помешало вашему методу выполнить свою работу, проблем нет, и никаких действий со стороны пользователя не требуется. Если вашей программе не удалось close() поток, и что является проблемой, чем может помочь пользователь?

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

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

0 голосов
/ 13 января 2011

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

Я бы по крайней мере предложил обработку, как показано ниже:

//store the first exception caught
IOException ioe = null;
Closeable resource = null;
try{
  resource = initializeResource();
  //perform I/O on resource here
}catch(IOException e){
  ioe = e;
}finally{
  if (resource != null){
    try{
      resource.close();
    }catch(IOException e){
      if(null == ioe){
        //this is the first exception
        ioe = e;
      }else{
        //There was already another exception, just log this one.
        log("There was another IOException during close. Details: ", e);
      }
    }
  }
}

if(null != ioe){
  //re-throw the exception to the caller
  throw ioe;
}

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

Возможно, есть более хорошие способы сделать это, но вы поняли идею.

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

0 голосов
/ 13 января 2011

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

final Resource resource = context.acquire();
try {
    use(resource);
} finally {
    resource.release();
}

В (маловероятном) случае release, вызывающего исключение, любое исключение, выброшенное use, будет отброшено. У меня нет проблем с исключением, брошенным ближе всего к победе ловца. Я считаю, что блоки ARM в JDK7 (?) В этом случае будут делать что-то сумасшедшее, например, выбрасывать исключение use с добавленным исключением release.

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

0 голосов
/ 13 января 2011

Это зависит от того, что вы закрываете. Например, закрытие StringWriter ничего не делает. Javadocs ясно об этом. В этом случае вы можете игнорировать IOException, поскольку знаете, что он никогда не будет сгенерирован. Технически вам даже не нужно звонить close.

/**
 * Closing a <tt>StringWriter</tt> has no effect. The methods in this
 * class can be called after the stream has been closed without generating
 * an <tt>IOException</tt>.
 */
public void close() throws IOException {
}

Для других потоков зарегистрируйте и обработайте исключение соответствующим образом.

0 голосов
/ 13 января 2011

Ну, в большинстве случаев close () на самом деле не генерирует IOException.Вот код для InputStream.java :

  public void close() throws IOException
  {
    // Do nothing
  }

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

Вы можете увидеть некоторые примеры различных реализаций Reader / Writer и Streams с помощью Google Code Search.Ни BufferedReader, ни PipedReader на самом деле не генерируют IOException, поэтому я думаю, что вы в основном в сейфе, не беспокоясь об этом.Если вы действительно обеспокоены, вы можете проверить реализацию библиотек, которые вы используете, чтобы узнать, нужно ли вам когда-либо беспокоиться об исключении.

Как и другие упомянутые, вы ничего не можете сделать с IOExceptionкроме лога.

В конце концов, try/catch blocks в предложении finally довольно некрасиво.

Редактировать:

Дальнейшая проверка показываетподклассы IOException, такие как InterruptedIOException, SyncFailedException и ObjectStreamException, наряду с классами, которые наследуются от него.Так что просто поймать IOException было бы слишком общим - вы не знали бы, что делать с информацией, кроме записи в журнал, потому что это может быть из целого ряда ошибок.

Редактировать 2:

Urk, BufferedReader был плохим примером, поскольку он принимает Reader в качестве ввода.Я изменил его на InputStream.java

Однако есть иерархия с InputStream <= <code>FilterInputStream <= <code>BufferedInputStream <= <code>InputStreamReader (через наследование и частные экземпляры), которые все накапливаютсяк методу close() в InputStream.

0 голосов
/ 13 января 2011

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

0 голосов
/ 13 января 2011

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

...