2010-02-05 19 views
15

Me pregunto cuál es la forma correcta de pasar una excepción de un método a otro.¿Cuál es la forma correcta de pasar una excepción? (C#)

Estoy trabajando en un proyecto dividido en capas de presentación (web), empresarial y lógica, y los errores (por ejemplo, SqlExceptions) deben transmitirse a la cadena para notificar a la capa web cuando algo va mal.

que he visto 3 enfoques básicos:

try 
{ 
    //error code 
} 
catch (Exception ex) 
{ 
    throw ex; 
} 

(simplemente volver a lanzar)

try 
{ 
    //error code 
} 
catch (Exception ex) 
{ 
    throw new MyCustomException(); 
} 

(lanzar una excepción personalizada, de manera que una dependencia en el proveedor de datos no se transmite)
y luego simplemente

//error code 

(no hacer nada en absoluto, dejando que el error salte por sí solo)

Naturalmente, también se registra algo en el bloque catch.

Prefiero el número 3, mientras que mi colega usa el método 1, pero ninguno de nosotros puede motivar por qué.

¿Cuáles son las ventajas/desventajas de usar cada método? ¿Hay algún método mejor que yo no sepa? ¿Hay una mejor manera aceptada?

+17

Su primer ejemplo es un antipatrón para C#, use 'throw;' en su lugar o bien está restableciendo la traza de pila al punto de proyección actual. Solo usar 'throw;' mantendrá la traza original de la pila. –

+1

Doble duplicado: http://stackoverflow.com/questions/178456/what-is-the-proper-way-to-re-throw-an-exception-in-c y http://stackoverflow.com/questions/ 22623/net-throwing-exceptions-best-practices. –

+1

Tenga en cuenta que puede mantener el seguimiento de la pila incluso si cambia el tipo de excepción mediante catch (Exception ex) {throw new MyCustomException (ex); } si utiliza el constructor proporcionado de la clase Exception en su clase MyCustomException (como lo hacen todos los tipos de excepciones .NET). – dbemerlin

Respuesta

13

Si no hace nada, simplemente déjelo ir arriba donde alguien lo manejará.

Siempre puede manejar una parte (como el registro) y volver a lanzarlo. Puede volver a lanzar simplemente enviando throw; sin tener que explicitar el nombre ex.

try 
{ 

} 
catch (Exception e) 
{ 
    throw; 
} 

La ventaja de manejar esto es que se puede asegurar que existe algún mecanismo es para notificarle que usted tiene un error en el que no sospecha de tener uno.

Pero, en algún caso, digamos un tercero, quiere dejar que el usuario lo maneje y, en ese caso, debe dejarlo continuar.

+1

Tenga en cuenta que en el ejemplo anterior, detectará errores como NullReferenceException, y como resultado, se ejecutarán todos los bloques finalmente entre el punto de lanzamiento y el try/catch, incluso aunque se haya detectado claramente un error fatal. ¿Qué harán los bloques finalmente, con el programa en este estado desconocido? ¿Quién sabe? –

+2

@Earwicker, su punto está bien, pero los bloques finalmente se ejecutan independientemente. Los bloques finalmente ya tienen que escribirse para ser robustos frente a la maldad. Lo que más me preocupa aquí son las implicaciones de seguridad, no las implicaciones de robustez. La robustez ya está fuera de la ventana; el servidor está fallando No ayudaremos a quien causó la falla del servidor. –

+0

@Eric - hablando por mí mismo No estoy seguro de saber cómo escribir un bloque final, ¡así que es robusto contra cualquier error! :) De ahí la necesidad de filtrado de excepción y FailFast. Pero considero su punto acerca de la necesidad de ocultar a veces la información. No le gustaría devolver los detalles al cliente en un sitio de producción. Pero probablemente aún desee iniciar sesión tanto como sea posible de forma privada en el servidor. –

6

La forma correcta para volver a lanzar una excepción en C# es como abajo:

try 
{ 
    .... 
} 
catch (Exception e) 
{ 
    throw; 
} 

ver este thread para obtener información específica.

+0

Bien, de lo contrario perderá su pila de llamadas, lo que puede hacer que la depuración sea dolorosa. –

6

Utilice solo bloques try/catch alrededor de las excepciones que espera y puede manejar. Si atrapa algo que no puede manejar, se frustra el propósito de try/catch que es manejar los errores esperados.

Capturar grandes excepciones rara vez es una buena idea. La primera vez que vea una OutOfMemoryException, ¿podrá manejarla con elegancia? La mayoría de las API documentan las excepciones que cada método puede arrojar, y esas deberían ser las únicas que se manejan y solo si puede manejarlas con elegancia.

Si desea manejar el error más arriba en la cadena, déjelo brillar por sí mismo sin atraparlo ni volver a lanzarlo. La única excepción a esto sería para fines de registro, pero el registro en cada paso va a hacer una cantidad excesiva de trabajo.Es mejor documentar las excepciones que se espera que sus métodos públicos generen y permitir que el consumidor de su API tome la decisión sobre cómo manejarlo.

9

creo que usted debe comenzar con una pregunta ligeramente diferente

¿Cómo esperan que otros componentes interactúen con las excepciones lanzadas desde mi módulo?

Si los consumidores son bastante capaces de manejar las excepciones arrojadas por las capas inferiores/de datos, simplemente no hacen nada. La capa superior es capaz de manejar las excepciones y solo debe hacer la cantidad mínima necesaria para mantener su estado y luego volver a lanzar.

Si los consumidores no pueden manejar las excepciones de bajo nivel pero en cambio necesitan excepciones de nivel un poco más alto, entonces crea una nueva clase de excepción que puedan manejar. Pero asegúrese de pasar la excepción original a la excepción interna.

throw new MyCustomException(msg, ex); 
0

Normalmente, solo captaría las excepciones que espera, podrá manejar y permitirá que la aplicación funcione de forma normal. En caso de que desee tener algún registro de error adicional, detectará una excepción, inicie sesión y vuelva a lanzarla usando "throw"; por lo que el seguimiento de la pila no se modifica. Las excepciones personalizadas generalmente se crean con el propósito de informar los errores específicos de su aplicación.

3

He visto (y mantenido) varias opiniones fuertes sobre esto. La respuesta es que no creo que actualmente haya un enfoque ideal en C#.

En un momento sentí que (en una forma de Java) la excepción es parte de la interfaz binaria de un método, tanto como el tipo de retorno y los tipos de parámetros. Pero en C#, simplemente no lo es. Esto está claro por el hecho de que no hay un sistema de especificación de lanzamientos.

En otras palabras, puede, si lo desea, adoptar la actitud de que solo sus tipos de excepción salgan volando de los métodos de su biblioteca, para que sus clientes no dependan de los detalles internos de su biblioteca. Pero pocas bibliotecas se molestan en hacer esto.

El consejo oficial del equipo de C# es captar cada tipo específico que pueda arrojarse con un método, si cree que puede manejarlos. No atrape nada que no pueda manejar realmente. Esto implica que no hay encapsulamiento de excepciones internas en los límites de la biblioteca.

Pero a su vez, eso significa que necesita una documentación perfecta de lo que podría arrojar un método determinado. Las aplicaciones modernas se basan en montones de bibliotecas de terceros, que evolucionan rápidamente. Se burla de tener un sistema de tipado estático si todos intentan atrapar tipos de excepción específicos que podrían no ser correctos en futuras combinaciones de versiones de bibliotecas, sin verificación en tiempo de compilación.

Así que la gente haga lo siguiente:

try 
{ 
} 
catch (Exception x) 
{ 
    // log the message, the stack trace, whatever 
} 

El problema es que este atrapa todo tipo de excepción, incluidas las que indican fundamentalmente un problema grave, como una excepción de referencia nula. Esto significa que el programa está en un estado desconocido. En el momento que se detecta, debe cerrarse antes de que haga algún daño a los datos persistentes del usuario (comienza a destruir archivos, registros de bases de datos, etc.).

El problema oculto aquí es intentar/finalmente. Es una característica de gran lenguaje, de hecho es esencial, pero si una excepción suficientemente seria está volando en la pila, ¿realmente debería estar causando que finalmente se ejecuten los bloques? ¿Realmente quieres que la evidencia se destruya cuando hay un error en el progreso?Y si el programa está en un estado desconocido, cualquier cosa importante podría ser destruida por esos bloques finally.

Así que lo que realmente quiere es (actualizado para C# 6!):

try 
{ 
    // attempt some operation 
} 
catch (Exception x) when (x.IsTolerable()) 
{ 
    // log and skip this operation, keep running 
} 

En este ejemplo, podría escribir IsTolerable como un método de extensión en Exception que devuelve false si la excepción interna es NullReferenceException, IndexOutOfRangeException , InvalidCastException o cualquier otro tipo de excepción que usted ha decidido debe indicar un error de bajo nivel que debe detener la ejecución y requerir una investigación. Estas son situaciones "intolerables".

Esto podría denominarse manejo de excepciones "optimista": suponiendo que todas las excepciones son tolerables excepto por un conjunto de tipos de lista negra conocidos. La alternativa (respaldada por C# 5 y anterior) es el enfoque "pesimista", donde solo las excepciones conocidas de la lista blanca se consideran tolerables y cualquier otra cosa no se controla.

Hace años, el enfoque pesimista era la postura oficial recomendada. Pero en estos días, el CLR detecta todas las excepciones en Task.Run, por lo que puede mover errores entre hilos. Esto hace que finalmente se ejecuten bloques. Por lo tanto, la CRL es muy optimista de forma predeterminada.

También puede contar con el evento AppDomain.UnhandledException, ahorrar tanta información como sea posible para fines de apoyo (al menos el seguimiento de pila) y luego llamar a Environment.FailFast para apagar el proceso antes de cualquier finally bloques pueden ejecutar (que podría destruir la valiosa la información necesaria para investigar el error, o arrojar otras excepciones que oculten el original).

+1

@Luaan ha actualizado la respuesta! –

2

no estoy seguro de que en realidad es una las mejores prácticas aceptadas, pero la OMI

try // form 1: useful only for the logging, and only in debug builds. 
{ 
    //error code 
} 
catch (Exception ex) 
{ 
    throw;// ex; 
} 

no tiene ningún sentido real, excepto por el aspecto de registro, por lo que podría hacer esto sólo en una versión de depuración. Atrapar un nuevo lanzamiento es costoso, por lo que debe tener una razón para pagar ese costo, además de simplemente mirar el código.

try // form 2: not useful at all 
{ 
    //error code 
} 
catch (Exception ex) 
{ 
    throw new MyCustomException(); 
} 

Esto no tiene ningún sentido. Es descartando la excepción real y reemplazándola por una que contenga menos información sobre el problema real real. Puedo ver posiblemente haciendo esto si quiero aumentar la excepción con algo de información sobre lo que estaba sucediendo.

try // form 3: about as useful as form 1 
{ 
    //error code 
} 
catch (Exception ex) 
{ 
    throw new MyCustomException(ex, MyContextInformation); 
} 

Pero yo diría que en casi todos los casos en los que no está manejando la excepción la mejor forma es simplemente dejar un acuerdo manejador de nivel superior de la misma.

// form 4: the best form unless you need to log the exceptions. 
// error code. no try - let it percolate up to a handler that does something productive. 
4

Nadie ha señalado la primera cosa que usted debe pensar en: ¿cuáles son las amenazas?

Cuando un nivel de back-end arroja una excepción, algo terrible e inesperado ha sucedido. La situación terrible inesperada podría haber sucedido porque ese nivel está siendo atacado por un usuario hostil.La última cosa que desea hacer en ese caso es servir al atacante una lista detallada de todo lo que salió mal y por qué. Cuando algo falla en el nivel lógico de negocios, lo correcto es registrar cuidadosamente toda la información sobre la excepción y reemplazar la excepción con un mensaje genérico "Lo sentimos, algo salió mal, la administración ha sido alertada, intente nuevamente". .

Una de las cosas para realizar un seguimiento es toda la información que tiene sobre el usuario y lo que estaban haciendo cuando ocurrió la excepción. De esta forma, si detecta que el mismo usuario siempre parece estar causando problemas, puede evaluar si es probable que lo indaguen en busca de debilidades o si simplemente utilizan un rincón poco común de la aplicación que no fue suficientemente probado.

Obtenga el diseño de seguridad correcto primero, y solo entonces preocúpese por el diagnóstico y la depuración.

Cuestiones relacionadas