我应该如何提供有关异常的其他信息?


20

每当我需要提供有关异常的其他信息时,我都会想知道哪种方法实际上是正确的方法。


为了这个问题,我写了一个例子。假设有一个我们要更新Abbreviation属性的类。从SOLID的角度来看,这可能并不完美,但是即使我们通过具有某些服务的DI 传递了工作方法,也会发生相同的情况-发生异常,并且没有上下文。回到示例...

class Person
{
    public int Id { get; set; }
    public string Name { get; set; }
    public string Abbreviation { get; set; }
}

然后是该类的一些实例和一个调用worker方法的循环。它可以抛出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...
}

  • 还有其他模式吗?
  • 有更好的模式吗?
  • 您能为所有/所有建议最佳做法吗?

不熟悉C#中的异常,但是通常我希望在抛出异常时,Person实例仍然有效。你有尝试过吗?
John Kouraklis '16

1
@JohnKouraklis这不是问题所在;-)这只是一个非常简单的示例,它通过附加信息来说明我的意思。如果我在这里发布了一个整个框架,其中多个方法可能引发异常,并且应该提供上下文信息的多个级别,那么没人会读到这个,我真的很难解释。
t3chb0t

@JohnKouraklis我只是为了演示目的而做了。
t3chb0t

@ t3chb0t我认为您已经在这里回答了自己的问题。考虑将1、2和3移到答案中并调整您的问题,这样就不会要求我根据我的意见选择样式。
candied_orange

自定义异常怎么了?正确完成后,它们是您域语言的一部分,可帮助您从实现细节中实现抽象。
RubberDuck

Answers:


6

Data FTW

您的“反对”:

  • “无法轻松集成到消息中”

->对于您的异常类型,它应该很容易覆盖,Message以便它确实包含Data..尽管我仅Data在消息为时才考虑这一点。

  • “记录器会忽略此字段,不会将其转储”

以Nlog为例,搜索会产生以下结果

异常布局渲染器

(...)

format-输出格式。必须是一个逗号分隔的异常属性列表:MessageTypeShortTypeToStringMethodStackTraceData。此参数值不区分大小写。默认:message

因此,这似乎很容易配置。

  • 需要键和强制类型转换,因为值是对象

??只需将对象转储到那里,并确保它们具有可用的ToString()方法。

另外,我不太觉得按键有什么问题。只需使用一些温和的唯一性,您就可以了。


免责声明:这是我可以立即从问题中看到的内容,也是我Data在15分钟内用谷歌搜索的内容。我认为这是有帮助的,所以我把它作为回答,但我从来没有用过Data,所以很可能这里的提问者比我更了解这一点。


我得出的结论是,关于异常只有两件事有用,即其名称和消息。其他所有东西都是无用的噪声,可以并且应该将其忽略,因为依靠它太脆弱了。
t3chb0t

2

你为什么抛出异常?抓住并处理它们。

捕获代码如何计算如何处理异常?使用您在Exception对象上定义的属性。

永远不要使用Message属性来标识异常,也不要提供任何潜在处理程序都应该依赖的“信息”。它只是太不稳定和不可靠。

我以前从未使用过“ Data”属性,但对我来说听起来过于笼统。

除非您创建许多 Exception类,每个类都标识一个特定的 Exception实例,否则如何知道捕获到Exception时 “数据”代表什么?(请参阅前面有关“消息”的评论)。


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);
    }
}
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.