¿Volver a lanzar una excepción filtra una abstracción?


12

Tengo un método de interfaz que indica en la documentación que arrojará un tipo específico de excepción. Una implementación de ese método usa algo que arroja una excepción. Se captura la excepción interna y se lanza la excepción declarada por el contrato de interfaz. Aquí hay un pequeño ejemplo de código para explicar mejor. Está escrito en PHP pero es bastante simple de seguir.

// in the interface

/**
 * @return This method returns a doohickey to use when you need to foo
 * @throws DoohickeyDisasterException
 */
public function getThatDoohickey();

// in the implementation

public function getThatDoohickey() {

    try {
        $SomethingInTheClass->doSomethingThatThrowsAnException();
    } catch (Exception $Exc) {
        throw new DoohickeyDisasterException('Message about doohickey failure');
    }

    // other code may return the doohickey

}

Estoy usando este método en un intento de evitar que la abstracción se filtre.

Mis preguntas son: ¿Pasar la excepción interna lanzada como la excepción anterior estaría filtrando la abstracción? Si no es así, ¿sería adecuado simplemente reutilizar el mensaje de la excepción anterior? Si se filtrara la abstracción, ¿podría proporcionar alguna guía sobre por qué cree que lo hace?

Solo para aclarar, mi pregunta implicaría cambiar a la siguiente línea de código

throw new DoohickeyDisasterException($Exc->getMessage(), null, $Exc);

Las excepciones deben ser sobre causas, no sobre quién las arroja . Si su código puede tener un file_not_found, entonces debería arrojar un file_not_found_exception. Las excepciones no deben ser específicas de la biblioteca.
Mooing Duck

Respuestas:


11

Mis preguntas son: ¿Pasar la excepción interna lanzada como la excepción anterior estaría filtrando la abstracción? Si no es así, ¿sería adecuado simplemente reutilizar el mensaje de la excepción anterior?

La respuesta es, depende".

Específicamente, depende de la excepción y de lo que significa. En esencia, volver a lanzarlo significa que está agregando eso a su interfaz. ¿Harías eso si escribieras el código que estás llamando a ti mismo, o es solo para evitar cambiar la marca de la excepción?

Si su excepción incluye un seguimiento de la causa raíz, en el código llamado, eso podría filtrar datos fuera de la abstracción, pero, en general, creo que eso es aceptable porque la persona que llama maneja la excepción (y a nadie le importa dónde está) vino de) o se muestra al usuario (y desea conocer la causa raíz, para la depuración).

Sin embargo, con frecuencia, solo permitir la excepción es una opción de implementación muy razonable, y no un problema de abstracción. Simplemente pregunte "¿lanzaría esto si el código al que estoy llamando no lo hizo?"


8

Hablando estrictamente, si la excepción 'interna' revela algo sobre el funcionamiento interno de una implementación, está goteando. Entonces, técnicamente, siempre debes atrapar todo y lanzar tu propio tipo de excepción.

PERO.

Se pueden hacer bastantes argumentos importantes en contra de esto. Más destacado:

  • Las excepciones son cosas excepcionales ; ya violan las reglas de "operación normal" de muchas maneras, por lo que también podrían violar la encapsulación y la abstracción a nivel de interfaz.
  • El beneficio de tener una traza de pila significativa e información de error al punto a menudo supera la corrección de OOP, siempre y cuando no se filtre la información interna del programa más allá de la barrera de la interfaz de usuario (que sería la divulgación de información).
  • Ajustar cada excepción interna en un tipo de excepción externa adecuada puede conducir a un montón de código repetitivo inútil , que anula el propósito completo de las excepciones (es decir, implementar el manejo de errores sin contaminar el flujo de código regular con repeticiones de manejo de errores).

Dicho esto, la captura y envolviendo excepciones a menudo lo hace tiene sentido, aunque sólo sea para añadir información adicional a sus registros, o para evitar la filtración de información interna para el usuario final. El manejo de errores sigue siendo una forma de arte, y es difícil reducirlo a unas pocas reglas generales. Algunas excepciones deberían burbujear, otras no, y la decisión también depende del contexto. Si está codificando una interfaz de usuario, no desea dejar pasar nada al usuario sin filtrar; pero si está codificando una biblioteca que implementa algún protocolo de red, es probable que hacer ciertas excepciones sea burbujeante (después de todo, si la red está inactiva, es poco lo que puede hacer para mejorar la información o recuperar y continuar) ; traducir NetworkDownExceptiona FoobarNetworkDownExceptiones simplemente sin sentido).


6

En primer lugar, lo que estás haciendo aquí no es volver a lanzar una excepción. Relanzar sería lanzar literalmente la misma excepción, así:

try {
    $SomethingInTheClass->doSomethingThatThrowsAnException();
} catch (Exception $Exc) {
    throw $Exc;
}

Obviamente, esto es un poco inútil. Relanzar es para circunstancias en las que desea liberar algunos recursos o registrar la excepción o cualquier otra cosa que desee hacer, sin evitar que la excepción aumente la pila.

Lo que ya está haciendo es reemplazar todas las excepciones, sin importar la causa, con una DoohickeyDisasterException. Lo que puede o no ser lo que pretende hacer. Pero sugeriría que cada vez que haga esto, también debe hacer lo que sugiere y pasar la excepción original como una excepción interna, en caso de que sea útil en el seguimiento de la pila.

Pero esto no se trata de abstracciones con fugas, ese es otro problema.

Para responder a la pregunta de si una excepción relanzada (o de hecho una excepción no detectada) puede ser una abstracción permeable, diría que depende de su código de llamada. Si ese código requiere conocimiento del marco abstracto, entonces es una abstracción permeable, porque si reemplaza ese marco con otro, entonces tiene que cambiar el código fuera de su abstracción.

Por ejemplo, si DoohickeyDisasterException es una clase del marco y su código de llamada se ve así, entonces tiene una abstracción permeable:

try {
    $wrapper->getThatDoohickey();
} catch (DoohickeyDisasterException $Exc) {
    echo "The Doohickey is all wrong!";
    echo $Exc->getMessage();
    gotoAHappyPlaceAndSingHappySongs();
}

Si reemplaza su marco de terceros con otro, que usa un nombre de excepción diferente, y tiene que encontrar todas estas referencias y cambiarlas. Es mejor crear su propia clase de excepción y envolver la excepción de marco en ella.

Si, por otro lado, DoohickeyDisasterException es una de tus creaciones, entonces no estás filtrando abstracciones, deberías preocuparte más por mi primer punto.


2

La regla que trato de seguir es: envolverlo si lo causaste. Del mismo modo, si se creara un error debido a una excepción de tiempo de ejecución, debería ser algún tipo de DoHickeyException o MyApplicationException.

Lo que eso implica es que si la causa del error es algo externo a su código, y no espera que falle, entonces falle con gracia y vuelva a lanzarlo. Del mismo modo, si se trata de un problema con su código, envuélvalo en una excepción (potencialmente) personalizada.

Por ejemplo, si se espera que un archivo esté en el disco, o se espera que un servicio esté disponible, trátelo con gracia y vuelva a lanzar el error para que el consumidor de su código pueda descubrir por qué ese recurso no está disponible. Si creó demasiados datos para algún tipo, entonces es su error envolver y lanzar.

Al usar nuestro sitio, usted reconoce que ha leído y comprende nuestra Política de Cookies y Política de Privacidad.
Licensed under cc by-sa 3.0 with attribution required.