2012-01-19 25 views
6

He buscado, pero no he podido encontrar una respuesta definitiva para algunas de mis preguntas de excepción, especialmente con respecto a las mejores prácticas de C#.Errores de mezcla y excepciones en C#

Tal vez siempre esté confundido acerca de la mejor manera de usar excepciones, me encontré con este artículo que básicamente dice 'siempre use excepciones, nunca utilice códigos de error o propiedades' http://www.eggheadcafe.com/articles/20060612.asp. Definitivamente voy a comprar eso, pero aquí está mi dilema:

Tengo una función 'llamante' que llama 'callee'. 'callee' realiza algunas cosas diferentes, cada una de las cuales arroja el mismo tipo de excepción. ¿Cómo devuelvo información significativa a la 'persona que llama' sobre lo que estaba 'haciendo' en el momento de la excepción?

que podría lanzar una nueva excepción como la de abajo, pero me preocupa voy a estropear el StackTrace que es malo:

//try to log in and get SomeException 
catch(SomeException ex) 
{ 
    throw new SomeException("Login failed", ex); 
} 

... 

//try to send a file and get SomeException 
catch(SomeException ex) 
{ 
    throw new SomeException("Sending file failed", ex): 
} 

Gracias, Mark

+2

excepciones propagarse por la pila de llamadas de forma automática. No tienes que volver a lanzarlos. –

+0

@CodyGray La razón para reiniciar es agregar información para que la persona que llama pueda diferenciar entre ellos. Si remito el try/catch del destinatario, la persona que llama no sabrá qué operación de múltiples operaciones causó la excepción. – MStodd

+1

No entiendo por qué el destinatario no arrojaría una excepción con información sobre lo que estaba haciendo. ¡La persona que llama no debe saber lo que el destinatario está haciendo de todos modos! –

Respuesta

3

En escenarios como éste por lo general inyectar un registrador y registrar el error específico y volver a lanzar la excepción original:

//try to log in and get SomeException 
catch(SomeException ex) 
{ 
    logger.LogError("Login failed"); 
    throw; 
} 

... 

//try to send a file and get SomeException 
catch(SomeException ex) 
{ 
    logger.LogError("Sending file failed"); 
    throw; 
} 

El throw; mantendrá la pila de originales traza viva (en contraposición a throw ex; que creará un nuevo stacktrace).

Editar

Dependiendo de lo que su código actual está haciendo podría tener sentido para realmente romper callee en varias llamadas (Login(), SendFile(), etc.), por lo caller está realizando los pasos individuales en lugar de llamar un gran método que está haciendo todo. A continuación, la persona que llama sabrá donde falló y puede actuar sobre ella (tal vez pedir al usuario para diferentes credenciales de inicio de sesión)

+0

+1 esta es una buena idea si tiene que lanzar una excepción y he hecho mucho de esto en el pasado cuando se trata de excepciones de archivos io. – JonH

+0

Esto es realmente lo que estaba haciendo.Podría ir por el camino de hacer que el destinatario no maneje NINGUNA excepción, y hacer que la persona que llama intente/atrape, y obtener información adicional del registro si es necesario. – MStodd

+0

@MStodd: podría tener sentido interrumpir el llamado si es importante que la persona que llama sepa en qué paso ocurrió el problema. – ChrisWue

5

La persona que llama no necesita saber qué estaba haciendo el destinatario en el momento de la excepción. No debe saber nada sobre cómo se implementa el llamado.

+5

Como con todas las declaraciones generales, esta también es engañosa (oh, la ironía). Si eso fuera cierto, no habría un millón de tipos de excepciones diferentes en .NET. – Jon

+0

No hay realmente un tipo de excepciones de bazillion. Además, esos tipos no indican "lo que estaba haciendo el destinatario"; indican el resultado de que el destinatario lo haga, lo cual es bastante diferente. –

+0

que se atreven a hacer clic [aquí] (http://msdn.microsoft.com/en-us/library/system.exception.aspx#inheritanceContinued) (y tenemos en cuenta que estos son sólo los hijos inmediatos de 'System.Exception' - la diversión continúa, por ejemplo [aquí] (http://msdn.microsoft.com/en-us/library/system.systemexception.aspx#inheritanceContinued)). En cuanto a la distinción entre acción y resultado, en mi humilde opinión es una pista falsa. nadie arroja excepciones a menos que * haya * un resultado que informar (uno no deseado). – Jon

7

No existe una mejor práctica única para cubrir todos los escenarios en .Net. Cualquiera que diga "usar siempre excepciones" o "usar siempre códigos de error" es simplemente incorrecto. Los programas escritos en .Net son amplios y complejos para generalizar en una regla dura y rápida como esa.

Hay muchos casos en los que las excepciones simplemente no son apropiadas. Por ejemplo, si tengo un bucle muy ajustado buscando valores en un Dictionary<TKey, TValue>, preferiría TryGetValue sobre un indexador arrojadizo. Sin embargo, en otros casos, preferiría el lanzamiento si los datos que se me han proporcionado ya están en el mapa.

La mejor manera de abordar esta decisión es caso por caso. En general, solo uso Excepciones si la situación es realmente excepcional. Uno esencialmente que no podría ser predicho por la persona que llama o los resultados de la persona que llama violando explícitamente un contrato. Ejemplos

  • no puede predecirse: Archivo no encontrado
  • Violación del contrato: El paso de un índice negativo en un indexador
2

'destinatario de la llamada' lleva a cabo un par de cosas diferentes, cada uno de los cuales podría lanzar el mismo tipo de excepción.

Creo que este es el verdadero culpable: callee debería ser tirar ya sea un tipo diferente de excepción basada en tipo de fallo ha ocurrido, o las excepciones del mismo tipo debe llevar suficiente información para permitir que la cifra que llama a cabo cómo manejar esta excepción en particular Por supuesto, si la persona que llama no tiene la intención de manejar la excepción en absoluto, no debería estar en el negocio de atraparla en primer lugar.

0

Si genuinamente tiene un escenario en el que una persona que llama tiene que manejar las excepciones de manera diferente, entonces la estrategia típica es para "atrapar y ajustar" la excepción subyacente en el destinatario.

Puede

  • envoltura todas atrapado excepciones que subyace en un solo tipo de excepción personalizada, con un código de estado que indica qué excepción subyacente fue lanzado. Esto es común cuando se utiliza el patrón de diseño del modelo de proveedor. Por ejemplo, Membership.CreateUser puede arrojar un MembershipCreateUserException que tiene un código de estado para distinguir los motivos comunes de la falla. Se espera que los implementadores de proveedores sigan este patrón para que el manejo de excepciones del consumidor sea independiente de la implementación del proveedor.

  • o envuelva cada tipo de excepción subyacente en una excepción personalizada separada. Esto podría ser apropiado si espera que algunas personas que llaman deseen manejar una de las excepciones (por ejemplo, enviar archivo fallido) pero no otras (por ejemplo, iniciar sesión fallido). Al usar diferentes tipos de excepciones, la persona que llama solo necesita captar los que le interesan.

2

¿Por qué el cuidado caller método callee lo estaba haciendo? ¿Tomará una acción diferente si el problema ocurrió al enviar el archivo?

try 
{ 
    callee(..); 
} 
catch (SomeException e) 
{ 
    if (e.Message == "Sending file failed") 
    { 
     // what would you do here? 
    } 
} 

Como regla general, las excepciones deben ser excepcional. Su flujo lógico típico no debería requerir que sean lanzados y atrapados. Cuando se lanzan, es generalmente se supone que es debido a un error en su código *. Así que el propósito principal de una excepción es:

  1. para detener el flujo de la lógica antes de que el error hace más daño, y
  2. para proporcionar información sobre el estado del sistema cuando se produjo el error, por lo que puede Identifica la fuente del error.

Con esto en mente, usted debe por lo general sólo capturar las excepciones si:

  1. usted está planeando para envolver la excepción con datos adicionales (como en el ejemplo) y lanzar una nueva excepción, o
  2. usted sabe que no habrá más efectos negativos al continuar con su flujo lógico: reconoce que lo que intentó hacer falló, y sin importar cómo falló, está en paz con eso. En estos casos, siempre debe aprovechar la oportunidad para registrar el error.

Si hay un caso bastante común de error (por ejemplo, credenciales de inicio de sesión incorrectas proporcionadas por el usuario), esto no debe tratarse como una excepción. En su lugar, debe estructurar su firma de método callee para que su resultado devuelto brinde todos los detalles que el caller necesita saber sobre lo que salió mal.

* La excepción notable es cuando ocurre algo verdaderamente imprevisible, como si alguien desenchufó el cable de red de la computadora en el medio de la transacción.

1

La clase Exception proporciona un diccionario para añadir datos adicionales a una excepción, que es accesible a través de la propiedad Exception.Data.

Si todo lo que quiero hacer es la transmisión de bits adicionales de información a la persona que llama, entonces no hay razón para envolver la excepción original y lanzar una nueva. En su lugar sólo se puede hacer:

//try to log in and get SomeException 
catch(SomeException ex) 
{ 
    ex.Data.Add("action", "Login"); 
    throw; 
} 

... 

//try to send a file and get SomeException 
catch(SomeException ex) 
{ 
    ex.Data.Add("action", "Sending of file"); 
    throw; 
} 

... 

// somewhere further up the call stack 
catch(SomeException ex) 
{ 
    var action = ex.Data["action"] ?? "Unknown action"; 
    Console.WriteLine("{0} failed.", action); // ... or whatever. 
} 

De esa manera usted será capaz de preservar la StackTrace original, mientras que todavía se puede presentar información adicional para el usuario.