对象捕获自己的异常并将消息存储在属性中是不好的做法?

时间:2009-02-17 19:03:03

标签: .net oop exception exception-handling

假设我有一个相同类型的对象列表。我想迭代该对象列表并对每个对象执行“危险”方法。如果在该方法中发生异常,那么让方法本身捕获异常并在对象上设置error属性是不好的做法吗?

这是一个快速示例,其中Car的Start()方法捕获自己的异常并将其存储在“Problem”属性中:

Sub Main()

    // Build cars.
    Dim ford As New Car("ford", "1988")
    Dim chevy As New Car("chevy", "1992")
    Dim honda As New Car("honda", "2002")

    // Create a list of cars.
    Dim carList As New List(Of Car)
    carList.Add(ford)
    carList.Add(chevy)
    carList.Add(honda)

    // Start all of the cars.
    For Each c As Car In carList
        // I don't want to stop processing if an exception occurs.
        // And I don't care to report any errors now.
        c.Start()
    Next

    // other stuff...

    // Now report errors.
    Dim cr As New CarReport
    cr.ReportProblems(carList)

End Sub

报告任何问题的班级:

Public Class CarReport

    Public Sub ReportProblems(ByVal c As List(Of Car))
        // Report any problems.
        For Each c In carList
            If Not String.IsNullOrEmpty(c.Problem) Then
                Console.WriteLine(c.Problem)
            End If
        Next
    End Sub

End Class

简单的Car类:

Public Class Car

    Private _make As String
    Private _year As String
    Private _problem As String = String.Empty

    Public Sub New(ByVal make As String, ByVal year As String)
        _make = make
        _year = year
    End Sub

    Public Sub Start()
        Try
            // Do something that could throw an exception.
        Catch ex As Exception
            _problem = "Car would not start."
        End Try
    End Sub

    Public ReadOnly Property Problem() As String
        Get
            Return _problem
        End Get
    End Property

End Class

编辑:如果调用者(即上面的Sub Main())捕获到异常,然后在Car对象上设置Problem属性,该怎么办?这似乎更清洁。我真正需要做的是将错误文本与特定的Car对象保持在一起,以便它可以传递给另一个系统进行报告。

注意:汽车场景是一个简单的例子来说明一个场景,我没有在课堂设计中考虑太多。

7 个答案:

答案 0 :(得分:4)

我绝对会说,因为这只是一次通话的规定。如果您对该对象进行其他调用会发生什么,它也会抛出异常?覆盖属性值,然后您不确定对象的状态是什么。

最好在try / catch块中捕获异常信息,然后单独处理它。一般来说,除非你设计了一个对象的方法不修改对象的状态,否则我会考虑在调用对象的方法/属性时抛出异常,使对象无法使用,因为对象的状态那时候是不确定的。

答案 1 :(得分:4)

这不是一个坏习惯,它是ADO.Net中使用的设计模式。

数据集中的每一行都有一个名为HasErrors的属性。这在各种验证方案中很有用。

以下是Beth Massi的一个例子:“Displaying Data Validation Messages in WPF

编辑:@casperOne:感谢您的评论。是的,HasError行为违反了封装的理想 - 一旦对象出错,一些外部动作必须收集/使用该信息,然后销毁/重置对象。

但它简单易用。涉及包装器的“正确”实现将是复杂且昂贵的,因此是不切实际的。

这不仅是对权威的诉求,也是对“该死的东西有效”的呼吁! ;)

答案 2 :(得分:3)

我认为有很多原因:

(1)通过推迟报告问题违反了Fail Fast原则。

(2)它不是一个非常模块化的设计。汽车和汽车报告类太紧密了。

(3)它要求调用者具有main()实现的特殊知识,以了解如何处理错误,使得客户端代码更容易忽略错误来处理错误条件处理机制或懒惰。

(4)如果你有这样的情况会发生多个错误,它会有一定的气味,可能会将操作细分为较小的工作单元。

(5)如另一个答案所述。 car.problem和car.start方法耦合得太紧。例如,如果你两次调用car.start会发生什么,它只会失败一次?你保留第一个问题吗?在这种情况下,调用者如何知道如何解释car.problem属性。总的来说,这似乎是不好的封装。

答案 3 :(得分:1)

我认为需要在“数据验证”和“程序错误”之间进行区分 在数据验证方案中:

如果完全驱动验证或通过异常驱动验证,捕获异常并将其应用于属性,无论是通过ADO.NET HasErrors,还是IDataErrorInfo,或者您需要的任何方式都是一件好事。关于使用例外验证是否是良好的编程实践以及我在屏障的两侧看到了良好代码的问题,有很多争论。

但是,如果出现与您的验证(NullReferenceException或MemoryException)无关的异常,使得您的对象因其状态未定义而无法再正常运行,则由于上述原因需要引发异常在JohnFx的回答中。

答案 4 :(得分:1)

var errors = new string[carList.Length];
for(var i = 0; i < carList.Length; i += 1) {
    try {
        carList[i].Start();
    } catch(Exception ex) {
        errors[i] = ex.Message;
    }
}

答案 5 :(得分:0)

以这种特殊方式使用它我认为这是不好的做法。 我看到的主要问题是使用该对象的类没有被告知发生的问题,它必须自行检查,尽管它负责错误处理。 如果汽车本身进行错误处理,则没有任何东西可以抵抗它,因为它可以确保对象状态保持一致。

虽然这个patern在ADO.Net中用作'Tom A'说,但我认为它在另一个上下文中使用。显然,这是一个验证上下文。那里没有真正的错误/异常,但它可能!在那里,您可以更正可能崩溃的数据,在此处使用的上下文中已经出现错误。

答案 6 :(得分:0)

有些情况下,人们可能想要尝试一些操作并查看哪些操作成功。在某些情况下,即使先前的操作失败,也可能希望继续尝试以后的操作。不过,我不喜欢你的特殊模式。我建议让Start包含一个接受类似Func<Exception,someEnumeratedType>(可能是Action<Exception,somethingElse>)的重载可能会更好。如果出现问题,代码将创建Exception并将其传递给提供的Action;默认函数只会抛出传入的异常,但函数也可以记录失败并返回一个值,表明事情应继续进行,或者他们应该优雅地退出。

我建议对象应该保持&#34;操作失败&#34;标志当且仅当操作失败表明对象处于不太有用的状态时。对状态已损坏的对象的操作除了设置标志外还应抛出异常,除非已知调用者准备处理失败的特殊情况。