Каждый раз, когда мне нужно предоставить дополнительную информацию об исключении, я задаюсь вопросом, какой способ действительно является правильным .
Ради этого вопроса я написал пример. Давайте предположим, что есть класс, где мы хотим обновить Abbreviation
свойство. С точки зрения SOLID это может быть не идеально, но даже если мы передадим рабочий метод через DI с каким-либо сервисом, возникнет та же ситуация - возникает исключение, и для него нет контекста. Вернуться к примеру ...
class Person
{
public int Id { get; set; }
public string Name { get; set; }
public string Abbreviation { get; set; }
}
Затем есть несколько экземпляров класса и цикла, где вызывается рабочий метод. Это может бросить 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
}
Вопрос в следующем: как добавить Person
или его Id
(или что-нибудь еще)?
Я знаю следующие три техники:
1 - Используйте Data
собственность
Плюсы:
- легко установить дополнительную информацию
- не требует создания еще большего количества исключений
- не требует дополнительного
try/catch
Минусы:
- не может быть легко интегрирована в
Message
- регистраторы игнорируют это поле и не будут сбрасывать его
- требуются ключи и приведение, потому что значения
object
- не неизменный
Пример:
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 - Используйте пользовательские свойства
Плюсы:
- похож на
Data
собственность, но строго типизирован - легче интегрировать в
Message
Минусы:
- требует пользовательских исключений
- регистратор будет игнорировать их
- не неизменный
Пример:
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 - Обернуть исключение другим исключением
Плюсы:
Message
может быть отформатирован предсказуемым образом- регистраторы будут сбрасывать внутренние исключения
- неизменный
Минусы:
- требует дополнительного
try/catch
- увеличивает вложенность
- увеличивает глубину исключений
Пример:
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...
}
- Есть ли другие модели?
- Есть ли лучшие образцы?
- Можете ли вы предложить лучшие практики для любого из них?