Как правильно обрабатывать IOException из close()
Классы ввода/вывода Java java.io.Reader
, java.io.Writer
, java.io.InputStream
, java.io.OutpuStream
и их различные подклассы имеют метод close()
, который может вызывать IOException
.
Есть ли какой-либо консенсус относительно правильного способа обработки таких исключений?
Я часто видел рекомендации, чтобы просто молча игнорировать их, но это кажется неправильным, и, по крайней мере, в случае ресурсов, открытых для записи, проблема при закрытии файла может означать, что нефиксированные данные не могут быть записаны/отправлены.
С другой стороны, при чтении ресурсов я совершенно не понимаю, почему close()
может бросить и что с этим делать.
Так есть ли какая-нибудь стандартная рекомендация?
Связанный с этим вопрос Когда-нибудь закрывает IOException?, но это больше о том, какие реализаций действительно делают, а не о том, как обрабатывать исключения.
Ответы
Ответ 1
Запишите его.
Вы не можете ничего с этим поделать (например, напишите какой-то код, который восстановится из-за ошибки), но его обычно стоит сообщить кому-то об этом.
Edit:
После дальнейшего изучения и чтения других комментариев я бы сказал, что если вы захотите справиться с этим, вам придется знать подробности реализации. И наоборот, вам, вероятно, нужно знать подробности реализации, чтобы решить, нужно ли вам это обрабатывать.
Реально, однако, я не могу думать о каких-либо примерах потоков, где чтение или запись будут работать правильно, не вызывая исключения, но закрытие будет.
Ответ 2
"Игнорирование или просто ведение журнала - это, как правило, плохая идея". Это не отвечает на вопрос. Что нужно делать с IOException из close()? Простое восстановление его только подталкивает проблему дальше, где ее еще труднее обрабатывать.
Теория
Чтобы ответить на ваш вопрос напрямую: когда это действие ввода-вывода завершилось неудачно, в зависимости от обстоятельств вы можете захотеть
- изменения отката (не оставляйте частично записанный файл на диске, удалите его)
- повторить попытку (возможно, после откат)
- игнорировать (и продолжать)
- прервать текущую задачу (отменить)
Вы можете часто видеть последние 3 в диалоговых окнах, отображаемых пользователю. Действительно, делегирование выбора пользователю возможно.
Я считаю, что главное - не оставлять систему в противоречивом состоянии. Просто проглатывание закрытого исключения может оставить вас с искаженным файлом, вызывая неприятные ошибки позже.
Практика
Немного громоздко работать с проверенными исключениями. Возможны варианты:
-
Преобразуйте их в RuntimeException
, выбросьте их и оставьте их обработанными на достаточно высоком уровне
-
Продолжайте использовать проверенные исключения и страдайте синтаксической болью
-
Используйте более сложные конструкторы обработки ошибок, такие как IO-монада (на самом деле недоступная на Java, по крайней мере, не без смущающего множества фигурных скобок (см. http://apocalisp.wordpress.com/2008/05/16/thrower-functor/), а не с полной мощностью)
Подробнее о монаде IO
Когда вы возвращаете результат в контексте монады IO, вы фактически не выполняете действие IO, а скорее возвращаете описание этого действия. Зачем? Эти действия имеют API, с которыми они прекрасно компонуются (по сравнению с броском /try/catch massacre).
Вы можете решить, хотите ли вы выполнить проверку стиля исключения (с вызовом Option
или Validation˙ in the return type), or dynamic style handling, with bracketing only on the final
unsafePerformIO` (который фактически выполняет скомпилированное действие ввода-вывода).
Чтобы получить некоторую идею, см. мой Scala gist http://gist.github.com/2709535, в котором размещен метод executeAndClose
. Он возвращает как результат обработки ресурсов (если есть), так и незакрытый ресурс (если закрытие завершилось неудачно). Именно тогда пользователи решают, как бороться с таким незакрытым ресурсом.
Он может быть увеличен с помощью Validation
/ValidationNEL
вместо Option
, если требуется также одно и более фактических исключений.
Ответ 3
Это зависит от того, что вы закрываете. Например, закрытие 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 {
}
Для других потоков регистрируйте и обрабатывайте исключение по мере необходимости.
Ответ 4
Игнорирование или просто ведение журнала обычно плохое. Несмотря на то, что вы не можете его восстановить, исключения I/O всегда должны обрабатываться унифицированным образом, чтобы убедиться, что ваше программное обеспечение ведет себя унифицированным образом.
Я бы по крайней мере предложил обработать, как показано ниже:
//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
, вы можете сохранить их оба.
Ответ 5
Попробуйте использовать флеш перед закрытием записи. Основная причина исключения в закрытии заключается в том, что некоторые другие ресурсы, возможно, использовали данные, или автор/читатель может вообще не открываться. попытайтесь найти, когда ресурсы открыты до закрытия.
Ответ 6
Прежде всего, не забудьте поместить close() в раздел "finally" вашего блока catch try.
Во-вторых, вы можете окружить метод close другим методом исключения catch catch и зарегистрировать исключение.
Ответ 7
Обычно обработка ресурсов должна выглядеть так:
final Resource resource = context.acquire();
try {
use(resource);
} finally {
resource.release();
}
В (маловероятном) случае release
, бросающем исключение, любое исключение, созданное use
, будет удалено. У меня нет проблемы с тем исключением, которое было самым близким к выигрышу ловушки. Я полагаю, что блоки ARM в JDK7 (?) Будут делать что-то сумасшедшее в этом случае, например, реконструировать исключение use
с прикрепленным исключением release
.
Если вы используете идиому Execute Around, вы можете поместить эти решения и потенциально беспорядочный код в одном (или нескольких) местах.
Ответ 8
Ну, в большинстве случаев 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
довольно уродливое.
Edit:
Дальнейшая проверка показывает подклассы IOException
, такие как InterruptedIOException
, SyncFailedException
и ObjectStreamException
, а также классы, которые наследуются от него. Так что просто ловли IOException
были бы слишком общими - вы не знали бы, что делать с информацией, отличной от регистрации, потому что это может быть из целого ряда ошибок.
Изменить 2:
Urk, BufferedReader
был плохим примером, так как он принимает вход Reader
. Я изменил его на InputStream.java
Однако существует иерархия с InputStream
<= FilterInputStream
<= BufferedInputStream
<= InputStreamReader
(через наследование и частные экземпляры), которые все просачиваются до метода close()
в InputStream
.