¿Cómo debo proporcionar información adicional sobre una excepción?

20

Cada vez que necesito proporcionar información adicional sobre una excepción, me pregunto cuál es la forma correcta de hacerlo.


Por el bien de esta pregunta escribí un ejemplo. Supongamos que hay una clase en la que queremos actualizar la Abbreviationpropiedad. Desde el punto de vista SÓLIDO, podría no ser perfecto, pero incluso si pasáramos el método de trabajo a través de DI con algún servicio, se produciría la misma situación: se produce una excepción y no hay contexto. De vuelta al ejemplo ...

class Person
{
    public int Id { get; set; }
    public string Name { get; set; }
    public string Abbreviation { get; set; }
}

Luego hay algunas instancias de la clase y un bucle donde se llama al método trabajador. Se puede tirar el StringTooShortException.

var persons =
{
    new Person { Id = 1, Name = "Fo" },
    new Person { Id = 2, Name = "Barbaz" },
}

public IEnumerable<Person> GenerateAbbreviation(IEnumerable<Person> persons)
{
    foreach (var person in persons)
    {
        try
        {
            person.Abbreviation = GenerateAbbreviation(person.Name);
        }
        catch(Exception ex)
        {
            // ?
        }
    }
    // throw AggregateException...
}

public IEnumerable<string> GenerateAbbreviation(string value)
{
    if (value.Length < 5)
    {
        throw new StringTooShortException(value);
    }

    // generate abbreviation
}

La pregunta es: ¿cómo agregar el Persono su Id(o cualquier otra cosa)?


Conozco las siguientes tres técnicas:


1 - Usa la Datapropiedad

Pros:

  • información adicional fácil de configurar
  • no requiere crear aún más excepciones
  • no requiere adicional try/catch

Contras:

  • no puede integrarse fácilmente en el Message
  • los registradores ignoran este campo y no lo vuelcan
  • requiere claves y valores de fundición porque object
  • no inmutable

Ejemplo:

public IEnumerable<Person> GenerateAbbreviation(IEnumerable<Person> persons)
{
    foreach (var person in persons)
    {
        try
        {
            person.Abbreviation = GenerateAbbreviation(person.Name);
        }
        catch(Exception ex)
        {
            ex.Data["PersonId"] = person.Id;
            // collect ex
        }
    }
    // throw AggregateException...
}

2 - Usar propiedades personalizadas

Pros:

  • similar a la Datapropiedad pero fuertemente tipado
  • más fácil de integrar en el Message

Contras:

  • requiere excepciones personalizadas
  • el registrador los ignorará
  • no inmutable

Ejemplo:

public IEnumerable<Person> GenerateAbbreviation(IEnumerable<Person> persons)
{
    foreach (var person in persons)
    {
        try
        {
            person.Abbreviation = GenerateAbbreviation(person.Name);
        }
        catch(Exception ex)
        {
            // not suitable for this exception because 
            // it doesn't have anything in common with the Person
        }
    }
    // throw AggregateException...
}

3 - Ajustar la excepción con otra excepción

Pros:

  • Message puede formatearse de forma predecible
  • los registradores volcarán las excepciones internas
  • inmutable

Contras:

  • requiere adicional try/catch
  • aumenta el anidamiento
  • aumenta la profundidad de las excepciones

Ejemplo:

public IEnumerable<Person> GenerateAbbreviation(IEnumerable<Person> persons)
{
    foreach (var person in persons)
    {
        try
        {
            try
            {
                person.Abbreviation = GenerateAbbreviation(person.Name);
            }
            catch(Exception ex)
            {
                throw new InvalidPersonDataException(person.Id, ex);
            }
        }
        catch(Exception ex)
        {
            // collect ex
        }
    }
    // throw AggregateException...
}

  • ¿Hay otros patrones?
  • ¿Hay mejores patrones?
  • ¿Puede sugerir las mejores prácticas para alguno / todos ellos?
t3chb0t
fuente
No estoy familiarizado con las excepciones en C #, pero normalmente esperaría que la instancia de Person todavía sea válida cuando se lanza la excepción. ¿Has intentado eso?
John Kouraklis
1
@JohnKouraklis esto no es de lo que se trata la pregunta ;-) Es solo un ejemplo extremadamente simple para demostrar lo que quiero decir con información adicional. Si publicara aquí un marco completo donde los métodos múltiples pueden arrojar excepciones y se deben proporcionar niveles múltiples de información de contexto, probablemente nadie leería esto y me costó mucho explicarlo.
t3chb0t
@JohnKouraklis Lo acabo de inventar con fines de demostración.
t3chb0t
@ t3chb0t Creo que has respondido tu propia pregunta aquí. Considere mover 1, 2 y 3 a una respuesta y ajustar su pregunta para que no me pida que elija un estilo basado en mi opinión.
naranja confitada
¿Qué hay de malo con las excepciones personalizadas? Hecho correctamente, son parte de su lenguaje de dominio y ayudan a lograr la abstracción lejos de los detalles de implementación.
RubberDuck

Respuestas:

6

Data FTW .

Tu "contra":

  • "no se puede integrar fácilmente en el mensaje"

-> Para sus tipos de excepción, debería ser lo suficientemente fácil de anularMessage de manera que hace incorporan Data.. aunque yo sólo consideraría esto si el Dataes el mensaje .

  • "los registradores ignoran este campo y no lo vuelcan"

Googlear para Nlog como ejemplo arroja :

Renderizador de diseño de excepción

(...)

format - Formato de la salida. Debe ser una lista separada por comas de las propiedades de excepción: Message, Type, ShortType, ToString, Method, StackTracey Data. Este valor de parámetro no distingue entre mayúsculas y minúsculas. Defecto:message

Entonces parece que es fácilmente configurable.

  • requiere claves y conversión porque los valores son objeto

¿Eh? Simplemente arroje los objetos allí y asegúrese de que tengan un ToString()método utilizable .

Además, no veo ningún problema con las teclas. Solo usa un poco de singularidad y estarás bien.


Descargo de responsabilidad: Esto es lo que pude ver inmediatamente de la pregunta y lo que busqué en Google Dataen 15 minutos. Pensé que era un poco útil, así que lo expuse como respuesta, pero nunca me he utilizado Data, por lo que bien podría ser que el interlocutor aquí sepa mucho más sobre esto que yo.

Martin Ba
fuente
Llegué a la conclusión de que solo hay dos cosas sobre una excepción que son útiles, su nombre y mensaje. Todo lo demás es un ruido inútil que puede y debe ignorarse porque es simplemente demasiado frágil.
t3chb0t
2

¿Por qué lanzas excepciones? Tenerlos atrapados y manejados.

¿Cómo funciona el código de captura cómo manejar la excepción? Usar las propiedades que defina en el objeto Excepción.

Nunca use la propiedad Mensaje para identificar la excepción, ni para proporcionar "información" en la que pueda confiar cualquier posible controlador. Es simplemente demasiado volátil y poco confiable.

Nunca he usado la propiedad "Datos" antes, pero me parece demasiado genérica.

A menos que cree muchas clases de Excepción, cada una de las cuales identifica un caso Excepcional específico , ¿cómo sabe cuando capta la Excepción qué representan los "Datos"? (Ver comentario anterior sobre "Mensaje").

Phill W.
fuente
1
Yo diría que Dataes inútil para el manejo, pero valioso para iniciar sesión para evitar Messageformatear el infierno.
Martin Ba
-1

Me gusta su tercer ejemplo, sin embargo, hay otra forma de codificarlo para eliminar la mayoría de sus "estafas".

public IEnumerable<Person> GenerateAbbreviation(IEnumerable<Person> persons)
{
    var exceptions = new List<InvalidPersonDataException>();

    foreach (var person in persons)
    {
        try
        {
            person.Abbreviation = GenerateAbbreviation(person.Name);
        }
        catch(Exception ex)
        {
            exceptions.Add(new InvalidPersonDataException(person.Id, ex));
        }
    }

    if (exceptions.Any())
    {
        throw new AggregateException(exceptions);
    }
}
krillgar
fuente