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:
Messagepuede 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?