Como devo fornecer informações adicionais sobre uma exceção?

20

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 Abbreviationpropriedade. 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 Personou seu Id(ou qualquer outra coisa)?


Conheço as três técnicas a seguir:


1 - Use a Datapropriedade

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 à Datapropriedade, 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?
t3chb0t
fonte
Não estou familiarizado com exceções em C #, mas normalmente esperaria que a instância Person ainda seja válida quando a exceção for lançada. Você já tentou isso?
John Kouraklis
1
@JohnKouraklis, não é disso que se trata ;-) É apenas um exemplo extremamente simples para demonstrar o que quero dizer com informações adicionais. Se eu publiquei aqui uma estrutura inteira em que vários métodos podem gerar exceções e vários níveis de informações de contexto devem ser fornecidos, provavelmente ninguém leria isso e eu tive muita dificuldade em explicá-lo.
t3chb0t
@JohnKouraklis Acabei de inventar para fins de demonstração.
t3chb0t
@ t3chb0t Acho que você respondeu sua própria pergunta aqui. Considere mover 1, 2 e 3 para uma resposta e ajustar sua pergunta para que não seja necessário escolher um estilo com base na minha opinião.
Candied_orange 20/11
O que há de errado com exceções personalizadas? Feito corretamente, eles fazem parte do idioma do seu domínio e ajudam a obter abstração longe dos detalhes da implementação.
precisa

Respostas:

6

Data FTW .

O seu "contra":

  • "não pode ser facilmente integrado à mensagem"

-> Para seus tipos de exceção, deve ser fácil substituí- Messagelo para que ele seja incorporado Data.. embora eu só considere isso se essa Datafor a mensagem .

  • "os registradores ignoram esse campo e não o despejam"

Pesquisando para NLog como exemplo rendimento :

Renderizador de layout de exceção

(...)

formato - Formato da saída. Deve ser uma lista separada por vírgulas de propriedades de excepção: Message, Type, ShortType, ToString, Method, StackTracee Data. Este valor do parâmetro não diferencia maiúsculas de minúsculas. Padrão:message

Parece que é facilmente configurável.

  • requer chaves e conversão porque os valores são objetos

Hã? Apenas despeje os objetos lá e verifique se eles têm um ToString()método utilizável .

Além disso, não vejo nenhum problema com as chaves. Basta usar alguma singularidade leve e você é bom.


Isenção de responsabilidade: foi o que pude ver imediatamente na pergunta e o que pesquisei Dataem 15 minutos. Eu pensei que era um pouco útil, então eu a coloco como resposta, mas nunca Datame usei , então pode ser que o interlocutor aqui saiba muito mais sobre isso do que eu.

Martin Ba
fonte
Cheguei à conclusão de que há apenas duas coisas úteis sobre uma exceção: seu nome e mensagem. Todo o resto é apenas um ruído inútil que pode e deve ser ignorado, porque é simplesmente muito frágil confiar nele.
t3chb0t 15/02
2

Por que você lança exceções? Tê-los capturados e manuseados.

Como o código de captura funciona como lidar com a exceção? Usando as propriedades que você define no objeto Exception.

Nunca use a propriedade Message para identificar a exceção, nem para fornecer "informações" nas quais qualquer manipulador em potencial deve confiar. É simplesmente muito volátil e não confiável.

Eu nunca usei a propriedade "Data" antes, mas parece excessivamente genérica para mim.

A menos que você criar muitas classes de exceção, cada um dos quais identifica um determinado caso excepcional, como você sabe quando você capturar a exceção que "Data" representa? (Veja o comentário anterior sobre "Mensagem").

Phill W.
fonte
1
Eu diria que Dataé inútil para o manuseio, mas valioso para o log para evitar a Messageformatação do inferno.
Martin Ba
-1

Eu gosto do seu terceiro exemplo, no entanto, existe outra maneira de codificá-lo para eliminar a maioria dos seus "contras".

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
fonte