Как мне предоставить дополнительную информацию об исключении?

20

Каждый раз, когда мне нужно предоставить дополнительную информацию об исключении, я задаюсь вопросом, какой способ действительно является правильным .


Ради этого вопроса я написал пример. Давайте предположим, что есть класс, где мы хотим обновить 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...
}

  • Есть ли другие модели?
  • Есть ли лучшие образцы?
  • Можете ли вы предложить лучшие практики для любого из них?
t3chb0t
источник
Не знаком с исключениями в C #, но обычно я ожидаю, что экземпляр Person все еще будет действителен при выдаче исключения. Вы пробовали это?
Джон Кураклис
1
@JohnKouraklis Это не тот вопрос, о котором идет речь ;-) Это просто очень простой пример, чтобы продемонстрировать, что я имею в виду под дополнительной информацией. Если бы я разместил здесь целую платформу, в которой методы Mutliple могут генерировать исключения, и должны быть предусмотрены Multliple уровни контекстной информации, никто, вероятно, не прочитал бы это, и мне было очень трудно объяснить это.
t3chb0t
@JohnKouraklis Я только что сделал это для демонстрационных целей.
t3chb0t
@ t3chb0t Я думаю, что вы ответили здесь на свой вопрос. Попробуйте перевести 1, 2 и 3 в ответ и скорректировать свой вопрос, чтобы он не просил меня выбрать стиль, основанный на моем мнении.
candied_orange
Что не так с пользовательскими исключениями? Если все сделано правильно, они являются частью языка вашего домена и помогают абстрагироваться от деталей реализации.
RubberDuck

Ответы:

6

Data FTW .

Ваш "контра":

  • «не может быть легко интегрировано в Послание»

-> Для ваших типов исключений, она должна быть достаточно легко переопределить Messageтак , что она делает инкорпорировать Data.. хотя я только хотел бы рассмотреть это , если Dataэто сообщение .

  • «регистраторы игнорируют это поле и не будут его сбрасывать»

Поиск в Google для Nlog в качестве примера дает :

Рендерер макетов исключений

(...)

format - формат вывода. Должно быть запятой список свойств исключения: Message, Type, ShortType, ToString, Method, StackTraceи Data. Значение этого параметра не зависит от регистра. По умолчанию:message

Кажется, это легко настраивается.

  • требует ключей и приведения, потому что значения являются объектом

А? Просто добавьте туда объекты и убедитесь, что у них есть подходящий ToString()метод.

Кроме того, я не вижу никаких проблем с ключами. Просто используйте небольшую уникальность, и все хорошо.


Отказ от ответственности: это то, что я сразу понял из вопроса и на что я погуглил за Data15 минут. Я подумал, что это немного полезно, поэтому я изложил это как ответ, но я никогда не использовал Dataсебя, так что вполне возможно, что спрашивающий здесь знает об этом гораздо больше, чем я.

Мартин Ба
источник
Я пришел к выводу, что в исключении есть только две полезные вещи: его имя и сообщение. Все остальное - просто бесполезный шум, который можно и нужно игнорировать, потому что он слишком хрупкий, чтобы полагаться на него.
t3chb0t
2

Почему вы бросаете исключения? Чтобы их поймали и обработали.

Как работает ловящий код, как обрабатывать исключение? Используя свойства, которые вы определяете для объекта Exception.

Никогда не используйте свойство Message, чтобы идентифицировать исключение или предоставить «информацию», на которую должен опираться любой потенциальный обработчик. Это просто слишком изменчиво и ненадежно.

Я никогда раньше не использовал свойство «Данные», но оно звучит слишком обобщенно для меня.

Если вы не создадите много классов исключений, каждый из которых идентифицирует конкретный исключительный случай, как вы узнаете, когда поймаете исключение, которое представляет «Данные»? (См. Предыдущий комментарий о «Сообщение»).

Фил В.
источник
1
Я бы сказал, что Dataэто бесполезно для обработки, но полезно для регистрации, чтобы избежать Messageформатирования ада.
Мартин Ба,
-1

Мне нравится ваш третий пример, однако есть и другой способ, которым он может быть закодирован для устранения большинства ваших "минусов".

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
источник