Ogni volta che ho bisogno di fornire informazioni aggiuntive su un'eccezione mi chiedo quale sia in realtà il modo giusto per farlo.
Per amore di questa domanda ho scritto un esempio. Supponiamo che esista una classe in cui vogliamo aggiornare la proprietà Abbreviation
. Dal punto di vista SOLIDO, potrebbe non essere perfetto, ma anche se abbiamo passato il metodo-lavoratore via DI con qualche servizio, si verificherebbe la stessa situazione - si verifica un'eccezione e non c'è un contesto. Torna all'esempio ...
class Person
{
public int Id { get; set; }
public string Name { get; set; }
public string Abbreviation { get; set; }
}
Poi ci sono alcune istanze della classe e un ciclo in cui viene chiamato il metodo worker. Può lanciare 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 domanda è: come aggiungere il Person
o il suo Id
(o qualsiasi altra cosa)?
Conosco le seguenti tre tecniche:
1: utilizza la proprietà Data
Pro:
- facile da impostare ulteriori informazioni
- non richiede la creazione di ulteriori eccezioni
- non richiede ulteriore
try/catch
Contro:
- non può essere facilmente integrato in
Message
- i logger ignorano questo campo e non lo scaricheranno
- richiede chiavi e casting perché i valori sono
object
- non immutabile
Esempio:
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: utilizza le proprietà personalizzate
Pro:
- simile alla proprietà
Data
ma strongmente tipizzata - più facile da integrare in
Message
Contro:
- richiede eccezioni personalizzate
- logger li ignorerà
- non immutabile
Esempio:
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: avvolge l'eccezione con un'altra eccezione
Pro:
-
Message
può essere formattato in modo prevedibile
I logger - eseguiranno il dump delle eccezioni interne
- immutabile
Contro:
- richiede un ulteriore
try/catch
- aumenta la nidificazione
- aumenta la profondità delle espressioni
Esempio:
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...
}
- Ci sono altri schemi?
- Esistono modelli migliori?
- Puoi suggerire le best practice per tutti / tutti?