我正在尝试在不使用异常的情况下在我的 C# 代码中进行错误通信和恢复。举个例子,假设有一个 Func A,它可以被 Func B 或 Func C 或其他函数调用。Func A 的设计必须牢记重用。(此应用程序有一个不断发展的库,其中新功能将在一段时间内不断添加)
如果 Func A 不能做它应该做的事情,它会返回一个 int,其中任何非零值都表示失败。我也想交流失败的原因。调用者函数可以通过多种方式使用此信息:
- 它可以向用户显示错误消息,
- 它可能会显示与其上下文更相关的自己的错误消息
- 它本身可能会返回一个 int 值,指示进一步的祖先调用函数失败。
- 它可能会尝试使用一些智能算法从错误中恢复。
假设地,其他函数所依赖的任何函数都可能需要将多个信息传递给其调用函数以采取适当的行动,包括状态代码、错误消息和其他指示数据状态的变量。将所有内容作为分隔字符串返回可能不允许调用函数在不解析字符串的情况下检索信息(这会导致其自身的问题,不推荐)。
唯一的另一种方法是返回一个包含所有必需信息的成员变量的对象。这可能会导致过多的“状态”对象,因为每个函数都需要有它的状态对象。
我想了解如何以最优雅的方式设计此要求。请注意,在编码时,Func A 可能不知道调用者函数是否具有从错误中恢复的智能,因此我不想抛出异常。另外,我想看看在不使用异常的情况下,这样的设计是否可行(同时又很优雅)。
如果唯一的方法是使用每个函数的数据对象进行通信,那么它就是编写专业库的方式。可以有一个通用的数据对象吗?请注意,将来可能会添加新功能,这些功能可能具有不同的状态变量,或有关其错误的支持信息。
另请注意,由于函数的返回值是一个“状态”对象,它应该返回的实际数据可能需要作为 ref 或 out 参数传递。
这有设计模式吗?
在发布此问题之前,我已阅读以下文章:
http://blogs.msdn.com/b/ricom/archive/2003/12/19/44697.aspx
我还阅读了许多其他文章,这些文章建议不要将异常用于代码流控制和可恢复的错误。此外,抛出异常有其自身的成本。此外,如果调用者函数想要从每个被调用函数抛出的异常中恢复,则必须用 try catch 块包围每个函数调用,因为通用 try catch 块不允许从下一行“继续”的错误线。
编辑:
几个具体问题:我需要编写一个应用程序,它将同步 2 个不同的数据库:一个是专有数据库,另一个是 SQL Server 数据库。我想将可重用的函数封装在一个单独的层中。
功能是这样的:专有应用程序可以有许多数据库。需要将来自这些数据库中的每一个的一些信息推送到单个通用 SQL Server 数据库。专有应用程序的数据库只有在应用程序的 GUI 打开时才能读取,并且只能通过 XML 读取。
算法是这样的:
- 读取专有应用程序中的打开数据库列表
- 对于每个数据库,启动同步进程。
- 检查当前登录的用户是否在此数据库中具有同步权限。(注意:每个数据库都可以使用不同的用户 ID 打开)。
- 从此数据库中读取数据。
- 将数据传输到 SQL Server
- 进入下一个数据库。
在开发这个应用程序时,我将编写几个可重用的函数,如 ReadUserPermission、ReadListOfDatabases 等。
在这种情况下,如果 ReadUserPermission 发现该权限不存在,调用者应该记录这个并继续下一个打开的数据库。如果 ReadListOfDatabases 无法与专有应用程序建立连接,调用者应自动启动应用程序等。
那么哪些错误条件应该传达异常,哪些使用返回码呢?
请注意,可重用函数可能会在其他项目中使用,其中调用者可能具有不同的错误恢复要求或功能,因此必须牢记这一点。
编辑:
对于所有提倡异常的人,我问他们:如果 Func A 调用 Func B,C,D,E,F,G 并且 Func B 在某些错误条件下抛出异常,但 Func A 可以从这个错误中恢复并愿意继续执行的其余部分,即调用 Func B、C、D、...,异常处理如何允许“优雅地”执行此操作?唯一的解决方案是将对 B、C、D、... 中的每一个的调用包装在一个 try catch 块中,以便执行剩余的语句。
另请阅读以下两条评论:
https://stackoverflow.com/a/1279137/1113579
https://stackoverflow.com/a/1272547/1113579
注意我不反对使用异常,如果可以优雅地实现错误恢复和剩余代码执行而不影响性能。此外,轻微的性能影响不是问题,但我更喜欢设计应该是可扩展的和优雅的。
编辑:
好的,基于“Zdeslav Vojkovic”评论,我现在正在考虑使用异常。
如果我要使用异常,你能给出一些不使用异常但使用返回码的用例吗?注意:我说的是返回码,而不是函数应该返回的数据。有没有使用返回码来指示成功/失败的用例,或者没有用例?这将帮助我更好地理解。
我从“Zdeslav Vojkovic”中了解到的一个异常用例是当被调用函数想要强制通知调用函数某些条件并中断调用者执行时。在没有异常的情况下,调用者可能会或可能不会选择检查返回码。但是在异常的情况下,调用者函数必须处理异常,如果它想继续执行。
编辑:
我有另一个有趣的想法。任何想要支持调用者函数从错误中恢复的思想的被调用者函数都可以引发事件,并在事件处理后检查事件数据,然后决定是否抛出异常。根本不会使用错误代码。异常将用于未恢复的错误。基本上,当被调用函数无法执行其合同规定的操作时,它会以任何可用事件处理程序的形式请求“帮助”。如果它不能履行合同,它仍然会抛出一个异常。优点是减少了引发异常的额外开销,并且仅当被调用函数或其任何调用函数无法从错误中恢复时才引发异常。
假设如果调用者函数不想处理错误,而是调用者的调用者函数想要处理错误,自定义事件分派器将确保事件处理程序以事件注册的相反顺序被调用,即最近注册的事件handler 应该在其他注册的事件处理程序之前被调用,如果这个事件处理程序能够解决错误,那么后续的事件处理程序根本不会被调用。另一方面,如果最近的事件处理程序不能解决错误,事件链将传播到下一个处理程序。
请对此方法提供反馈。