java - remoto - Cómo manejar adecuadamente una excepción IOException desde close()
java.io.ioexception se ha forzado la interrupción de una conexión existente por el host remoto (9)
Las clases de E / S de Java java.io.Reader
, java.io.Writer
, java.io.InputStream
, java.io.OutpuStream
y sus diversas subclases tienen un método close()
que puede IOException
una IOException
.
¿Existe algún consenso sobre la forma correcta de manejar tales excepciones?
A menudo he visto recomendaciones para ignorarlos solo en silencio, pero eso se siente mal, y al menos en el caso de recursos abiertos para escritura, un problema al cerrar el archivo puede significar que los datos no vaciados no se pueden escribir / enviar.
Por otro lado, cuando leo recursos, no estoy del todo claro sobre por qué podría tirar close()
y qué hacer al respecto.
Entonces, ¿hay alguna recomendación estándar?
Una pregunta relacionada es ¿El cierre nunca lanza una excepción IOException? , pero eso es más sobre qué implementaciones realmente se lanzan, no sobre cómo manejar las excepciones.
"Ignorar o simplemente iniciar sesión suele ser una mala idea". Eso no es responder a la pregunta. ¿Qué se debe hacer con respecto a una excepción IOException de close ()? Solo con volver a arrancarlo, el problema aumenta aún más, donde es aún más difícil de manejar.
Teoría
Para responder su pregunta directamente: cuando esta acción de E / S ha fallado, entonces dependiendo de las circunstancias, es posible que desee
- cambios de reversión (no deje un archivo parcialmente escrito en el disco, elimínelo)
- reintentar (tal vez después de una reversión)
- ignorar (y continuar)
- interrumpir la tarea actual (cancelar)
A menudo puede ver los últimos 3 en los cuadros de diálogo que se muestran al usuario. De hecho, delegar la elección al usuario es una posibilidad.
Creo que el punto principal es no dejar el sistema en un estado inconsistente. El solo hecho de tragar una excepción cerrada podría dejarlo con un archivo dañado, lo que podría ocasionar errores desagradables más adelante.
Práctica
Es un poco incómodo trabajar con excepciones comprobadas. Surgen opciones:
Conviértalos a
RuntimeException
,RuntimeException
yRuntimeException
para que se manejen a un nivel suficientemente altoSeguir usando excepciones comprobadas y sufrir el dolor sintáctico.
Utilice construcciones de manejo de errores más compensables, como la mónada IO (no está realmente disponible en Java, al menos no sin vergonzosas llaves) (consulte http://apocalisp.wordpress.com/2008/05/16/thrower-functor/ ) y no con plena potencia)
Más sobre la mónada IO
Cuando devuelve un resultado en el contexto de la Mónada de E / S, no está ejecutando realmente una acción de E / S, sino que devuelve la descripción de esa acción. ¿Por qué? Estas acciones tienen una API con la que se componen muy bien (en comparación con la masacre de lanzamientos / intentos / capturas).
Puede decidir si desea verificar el manejo del estilo de excepción (con Option
o Validation˙ in the return type), or dynamic style handling, with bracketing only on the final
llamada Validation˙ in the return type), or dynamic style handling, with bracketing only on the final
unsafePerformIO` (que en realidad ejecuta la acción compuesta de IO).
Para tener una idea, consulte mi lista de Scala http://gist.github.com/2709535 , que aloja el método executeAndClose
. Devuelve tanto el resultado del procesamiento del recurso (si está disponible) como un recurso no cerrado (si el cierre falló). Entonces es la decisión de los usuarios cómo tratar con un recurso que no se puede cerrar.
Podría ser reforzado con Validation
/ ValidationNEL
lugar de Option
si uno necesita la una / más excepciones reales también.
Bueno, en la mayoría de los casos, close () no lanza una excepción IOException. Aquí está el código para InputStream.java :
public void close() throws IOException
{
// Do nothing
}
Los errores que se producen al cerrar un recurso de red deberían ser de algún tipo de RuntimeException
, ya que puede desconectar un recurso de red después de que el programa se conecte a él.
Puede ver algunos ejemplos de varias implementaciones de Reader / Writer y Streams utilizando Google Code Search. Ni BufferedReader ni PipedReader realmente lanzan una excepción IOException, por lo que creo que la mayoría de las veces está en la caja fuerte al no preocuparse por eso. Si está realmente preocupado, puede verificar la implementación de las bibliotecas que está utilizando para ver si alguna vez necesita preocuparse por la excepción.
Como mencionaron otros, no puede hacer mucho sobre la excepción IOException que no sea registrarla.
Después de todo, try/catch blocks
en la cláusula finally
son bastante feos.
Editar:
Una inspección adicional revela subclases de IOException
como InterruptedIOException
, SyncFailedException
y ObjectStreamException
, junto con las clases que heredan de ella. Por lo tanto, capturar una IOException
sería demasiado general: no sabría qué hacer con la información que no sea registrarla, ya que podría deberse a una gran variedad de errores.
Edición 2:
Urk, BufferedReader
fue un mal ejemplo, ya que toma un Reader
como entrada. Lo he cambiado a InputStream.java
Sin embargo, hay una jerarquía con InputStream
<= FilterInputStream
<= BufferedInputStream
<= InputStreamReader
(a través de la herencia y las instancias privadas) que se filtran hasta el método close()
en InputStream
.
Depende de lo que estés cerrando. Por ejemplo, cerrar un StringWriter
no hace nada. Los javadocs lo tienen claro. En este caso, puede ignorar la excepción IOException porque sabe que nunca se generará. Técnicamente, ni siquiera necesitas llamar a 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 {
}
Para otras transmisiones, registre y maneje la excepción según corresponda.
En general, el manejo de recursos debería verse así:
final Resource resource = context.acquire();
try {
use(resource);
} finally {
resource.release();
}
En (el improbable) caso de release
lanzando una excepción, cualquier excepción lanzada por el use
será eliminada. No tengo un problema con la excepción lanzada más cercana al ganador del catcher. Creo que los bloques ARM en JDK7 (?) Harán algo alocado en este caso, como volver a enviar la excepción de use
con la excepción de release
adjunta.
Si usa el lenguaje Execute Around, puede colocar estas decisiones y el código potencialmente desordenado en uno (o pocos) lugares.
En primer lugar, no olvide colocar el cierre () dentro de la sección "finalmente" de su bloque catch catch. En segundo lugar, puede rodear el método close con otra excepción try catch y registrar la excepción.
Ignorar o simplemente el registro es una mala idea. Aunque es cierto que no puede recuperarse de él, las excepciones de E / S siempre deben manejarse de manera unificada para asegurarse de que su software se comporte de manera unificada.
Al menos sugeriría un manejo como el siguiente:
//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;
}
Lo anterior es bastante detallado pero funciona bien, ya que preferirá la IOException
durante la E / S en el recurso al cierre, ya que es más probable que contenga información de interés para el desarrollador. El código también emitirá una IOException
cuando algo salga mal, por lo que obtendrá un comportamiento unificado.
Puede haber mejores maneras de hacerlo, pero entiendes la idea.
Lo ideal sería crear un nuevo tipo de excepción que permitiera almacenar excepciones de hermanos, por lo que en caso de que obtenga dos IOException
s podría almacenarlas.
Iniciar sesión
Realmente no se puede hacer nada al respecto (por ejemplo, escribir un código que se recupere del error), pero generalmente vale la pena que alguien lo sepa.
Editar:
Después de investigar más y leer los otros comentarios, diría que si desea manejarlo, tendrá que conocer los detalles de la implementación. A la inversa, es probable que necesite conocer los detalles de la implementación para decidir si necesita manejarlo.
Sin embargo, siendo realistas, no puedo pensar en ningún ejemplo de flujos en los que la lectura o la escritura funcionen correctamente sin lanzar una excepción, pero el cierre sí lo haría.
Su código que llama a InputStream.close()
o OutputStream.close()
es un código de alto nivel que delega a la clase de nivel inferior de InputStream
o OutputStream
para realizar un cálculo de alto nivel.
Si lanzar una excepción
Sólo tienes 3 opciones sobre qué hacer.
- Atrapa y traga la excepción, descartándola.
- Deja que se propague hacia arriba.
- Captura la excepción y lanza un tipo diferente de excepción. Normalmente utilizarías una excepción encadenada en este caso.
Las dos últimas opciones son similares en que su código lanza una excepción. Entonces, esta pregunta es en realidad un caso especial de cuándo debería mi código lanzar una excepción . La respuesta correcta a esa pregunta en este contexto es: si, y solo si, la alternativa, es no cumplir una condición de publicación de su código o mantener una invariante de su código . Las condiciones de publicación especifican lo que su método debe hacer. Los invariantes especifican las características de la clase.
Por lo tanto, debe preguntarse si el close()
lanzar una excepción impide que su método haga lo que debe hacer.
- Si no impide que su método haga su trabajo, lo correcto es tragar la excepción. A pesar de lo que mucha gente te dirá.
- Si el lanzamiento
close()
impide que su método haga su trabajo, y el método puede lanzar unaIOException
, no puede hacer nada, simplemente permitiendo que la excepción se propague hacia arriba . - Si
close()
impide que su método haga su trabajo, pero es posible que el método no lance laIOException
, debe capturar laIOException
y repetirla como una clase diferente de excepción, registrando laIOException
como la causa de la excepción lanzada.
No conozco ninguna circunstancia en la que un InputStream.close()
lanzando una excepción pueda evitar que una computación tenga éxito. Esa llamada a close()
ocurre naturalmente después de que haya terminado de leer los datos que le interesan.
Sin embargo, los flujos de salida pueden almacenar datos en búfer para que se escriban en el destino de salida, ya sea internamente (dentro del código Java) o en un nivel inferior (dentro del sistema operativo). Por lo tanto, no puede estar seguro de que las operaciones de escritura en una secuencia de salida hayan resultado en una salida real hasta que OutputStream.close()
haya regresado exitosamente ( sin lanzar una excepción). Por lo tanto, debe tratar una excepción lanzada por OutputStream.close()
como un error de escritura.
Tu método es responsable de mantener sus invariantes. A veces, esto requerirá una operación de limpieza o retroceso si close()
lanza una excepción. Debe colocar el código para eso en una cláusula catch
o finally
para la IOException
, incluso si desea que la excepción se propague hacia arriba. Si utiliza una cláusula catch y desea propagarla, tendrá que volver a lanzarla.
Ya sea para registrar la excepción
Algunas personas le aconsejarán que registre la excepción . Este es casi siempre un mal consejo. Los mensajes de registro forman parte de la interfaz de usuario de su programa. Fundamentalmente, siempre debe preguntarse si debe registrar algo en absoluto, ya que las palabras inútiles pueden distraer y confundir a los usuarios que leen su archivo de registro ("usuarios" incluye a los administradores del sistema). Cada mensaje registrado debe ser para algún propósito útil. Cada uno debe proporcionar información que ayude al usuario a tomar decisiones .
Informar específicamente que el close()
rara vez es útil. ¿Cómo puede ayudar a un usuario a tomar una decisión? Si la excepción no impidió que su método hiciera su trabajo, no hay ningún problema y el usuario no necesita hacer nada. Si su programa no pudo close()
la transmisión, y eso es un problema, ¿qué puede hacer el usuario para ayudar?
El código de bajo nivel no suele ser responsable del registro en absoluto. En su lugar, realiza operaciones abstractas, informando fallas a partes de niveles más altos de su programa al lanzar excepciones. El código que cierra un flujo es generalmente un nivel bastante bajo, por lo que el código que detecta que close()
es un nivel demasiado bajo para realizar cualquier registro.
El hecho específico de que el close()
fallado rara vez es útil. Lo que puede ser útil es saber que la operación abstracta que su método debe realizar ha fallado. Esto se puede hacer haciendo que el código de nivel superior detecte todas las excepciones esperadas e informe que la operación falló, en lugar de que su método informe con precisión que "el cierre falló".
Trate de usar el color antes de cerrar el escritor. La razón principal para cerrar la excepción es que algunos otros recursos podrían haber estado usando los datos o el escritor / lector puede no estar abierto en absoluto. Trate de encontrar si los recursos están abiertos antes de cerrarlos.