Sempre que preciso fornecer informações adicionais sobre uma exceção, pergunto-me qual é a maneira correta de fazer isso.
Para o bem desta pergunta, escrevi um exemplo. Vamos supor que há uma classe em que queremos atualizar a Abbreviation
propriedade. Do ponto de vista do SOLID, pode não ser perfeito, mas mesmo que passássemos o método de trabalho via DI com algum serviço, a mesma situação ocorreria - ocorre uma exceção e não há contexto para ele. Voltar ao exemplo ...
class Person
{
public int Id { get; set; }
public string Name { get; set; }
public string Abbreviation { get; set; }
}
Depois, existem algumas instâncias da classe e um loop em que o método worker é chamado. Pode jogar o 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
}
A questão é: como adicionar o Person
ou seu Id
(ou qualquer outra coisa)?
Conheço as três técnicas a seguir:
1 - Use a Data
propriedade
Prós:
- fácil de configurar informações adicionais
- não requer a criação de ainda mais exceções
- não requer adicional
try/catch
Contras:
- não pode ser facilmente integrado ao
Message
- os madeireiros ignoram esse campo e não o despejam
- requer chaves e elenco porque os valores são
object
- não imutável
Exemplo:
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 - Use propriedades personalizadas
Prós:
- semelhante à
Data
propriedade, mas fortemente tipado - mais fácil de integrar no
Message
Contras:
- requer exceções personalizadas
- logger irá ignorá-los
- não imutável
Exemplo:
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 - Quebra a exceção com outra exceção
Prós:
Message
pode ser formatado de forma previsível- madeireiros irão despejar exceções internas
- imutável
Contras:
- requer adicional
try/catch
- increses aninhamento
- aumenta a profundidade das exceções
Exemplo:
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...
}
- Existem outros padrões?
- Existem padrões melhores?
- Você pode sugerir práticas recomendadas para algumas delas?