Подтвердить что ты не робот

Как правильно обрабатывать IOException из close()

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

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

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

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

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

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

4b9b3361

Ответ 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.