.NET中的错误通信和恢复方法

时间:2013-03-08 15:03:02

标签: c# .net oop design-patterns

我试图在不使用Exceptions的情况下在C#代码中进行错误通信和恢复。 举个例子,假设有一个Func A,它可以被Func B或Func C或其他函数调用。必须设计Func A以保持重用。 (此应用程序有一个不断发展的库,新功能将在一段时间内不断添加)

如果Func A无法做它应该做的事情,它会返回一个int,其中任何非零值表示失败。我也想传达失败的原因。调用者函数可以多种方式使用此信息:

  1. 它可以向用户显示错误消息
  2. 它可能会显示与其上下文更相关的错误消息
  3. 它本身可能返回一个int值,表示无法进一步激活调用者函数。
  4. 可能会尝试使用某种智能算法从错误中恢复。
  5. 假设其他函数所依赖的任何函数可能需要将多个事物传递给其调用函数以采取适当的操作,包括状态代码,错误消息和指示数据状态的其他变量。将所有内容作为分隔字符串返回可能不允许调用者函数在不解析字符串的情况下检索信息(这将导致其自身的问题,并且不建议使用)。

    唯一的另一种方法是返回包含所有必需信息的成员变量的对象。这可能会导致太多的“状态”状态。对象,因为每个函数都需要有自己的状态对象。

    我想了解如何以最优雅的方式设计此要求。请注意,在编码时,Func A可能不知道调用函数是否具有从错误中恢复的智能,因此我不想抛出异常。此外,我想看看这样的设计是否可行(并且同时优雅),而不使用例外。

    如果唯一的方法是使用每个函数的数据对象进行通信,那么它是专业库的编写方式。可以有通用数据对象吗?注意将来可能会添加新函数,这些函数可能具有不同的状态变量,或者支持有关其错误的信息。

    另请注意,由于函数的返回值是“状态”,因此'对象,它应该返回的实际数据可能需要作为ref或out参数传递。

    这有设计模式吗?

    在发布此问题之前,我已阅读以下文章:

    http://blogs.msdn.com/b/ricom/archive/2003/12/19/44697.aspx

    Do try/catch blocks hurt performance when exceptions are not thrown?

    Error Handling without Exceptions

    我还阅读了许多其他文章,建议不要使用代码流控制的异常,以及可恢复的错误。此外,抛出异常有其自身的成本。此外,如果调用函数想要从每个被调用函数抛出的异常中恢复,它将必须用try catch块包围每个函数调用,因为通用try catch块将不允许继续' ;从错误行的下一行开始。

    修改

    一些具体问题: 我需要编写一个同步2个不同数据库的应用程序:一个是专有数据库,另一个是SQL Server数据库。我想在一个单独的层中封装可重用的函数。

    功能如下:专有应用程序可以有许多数据库。需要将来自每个数据库的一些信息推送到单个公共SQL Server数据库。只有当应用程序的GUI处于打开状态并且只能通过XML读取时,才能读取专有应用程序的数据库。

    算法是这样的:

    1. 阅读专有应用程序中的开放数据库列表
    2. 对于每个数据库,启动同步过程。
    3. 检查此数据库中当前登录的用户是否具有“同步权限”。 (注意:可以使用不同的用户ID打开每个数据库。)
    4. 从此数据库中读取数据。
    5. 将数据传输到SQL Server
    6. 继续下一个数据库。
    7. 在开发此应用程序时,我将编写几个可重用的函数,如ReadUserPermission,ReadListOfDatabases等。

      在这种情况下,如果ReadUserPermission发现该权限不存在,则调用者应记录此权限并继续下一个打开的数据库。如果ReadListOfDatabases无法与Proprietory Application建立连接,则调用者应自动启动应用程序等。

      那么应该通过异常和使用返回码来传达哪些错误条件?

      请注意,可重复使用的函数可能在其他项目中使用,其中调用者可能具有不同的错误恢复要求或功能,因此必须牢记这一点。

      修改

      对于所有提倡例外的人,我问他们: 如果Func A调用Func B,C,D,E,F,G和Func B在某些错误条件下抛出异常,但Func A可以从此错误中恢复并希望继续执行其余部分,即调用Func B,C, D,......,异常处理如何做到这一点'优雅'?唯一的解决方案是在try catch块中包含对B,C,D,...中的每一个的调用,以便执行剩余的语句。

      请同时阅读以下2条评论:

      https://stackoverflow.com/a/1279137/1113579

      https://stackoverflow.com/a/1272547/1113579

      注意我不反对使用异常,如果可以优雅实现错误恢复和剩余代码执行,并且不会影响性能。此外,轻微的性能影响不是一个问题,但我更喜欢设计应该可扩展和优雅。

      修改

      好的,基于" Zdeslav Vojkovic"评论',我现在正考虑使用例外。

      如果我要使用异常,是否可以在不使用异常时给出一些用例,但是使用返回码? 注意:我说的是返回代码,而不是应该返回的函数。是否有使用返回代码表示成功/失败或没有用例的用例?这将有助于我更好地理解。

      我所理解的一个例外用例" Zdeslav Vojkovic"当被调用函数想要强制通知调用函数某些条件并中断调用者执行时。在没有例外的情况下,呼叫者可能会也可能不会选择检查返回码。但是在异常的情况下,调用函数必须处理异常,如果它想继续执行

      修改

      我有另一个有趣的想法。 任何想要支持调用函数从其错误中恢复的想法的被调用者函数都可以引发事件,并在事件处理完毕后检查事件数据,然后决定是否抛出异常。根本不会使用错误代码。例外情况将用于未恢复的错误。基本上当被调用函数无法完成其合同所说的内容时,它会要求" help"以任何可用的事件处理程序的形式。如果它无法执行合同,它会引发异常。优点是减少了抛出异常的额外开销,并且只有当被调用函数或其任何调用函数无法从错误中恢复时才会抛出异常。

      假设调用者函数不想处理错误,而调用者的调用函数想要处理错误,则自定义事件调度程序将确保以事件注册的相反顺序调用事件处理程序,即最近注册的事件处理程序应该在其他已注册的事件处理程序之前调用,如果此事件处理程序能够解决该错误,则根本不会调用后续事件处理程序。另一方面,如果最新的事件处理程序无法解决错误,则事件链将传播到下一个处理程序。

      请对此方法提供反馈。

3 个答案:

答案 0 :(得分:1)

您在所有不想抛出异常的方法中用作FunctionResult参数的常见out对象怎么样?

public class FuncResultInfo
    {
        public bool ExecutionSuccess { get; set; }
        public string ErrorCode { get; set; }
        public ErrorEnum Error { get; set; }
        public string CustomErrorMessage { get; set; }

        public FuncResultInfo()
        {
            this.ExecutionSuccess = true;
        }

        public enum ErrorEnum
        {
            ErrorFoo,
            ErrorBar,
        }
    }

    public static class Factory
    {
        public static int GetNewestItemId(out FuncResultInfo funcResInfo)
        {
            var i = 0;
            funcResInfo = new FuncResultInfo();

            if (true) // whatever you are doing to decide if the function fails
            {
                funcResInfo.Error = FuncResultInfo.ErrorEnum.ErrorFoo;
                funcResInfo.ErrorCode = "234";
                funcResInfo.CustomErrorMessage = "Er mah gawds, it done blewed up!";
            }
            else
            {
                i = 5; // whatever.
            }

            return i;
        }
    }

确保所有可以失败且没有例外的函数都具有out的{​​{1}}参数

答案 1 :(得分:1)

“这是专业图书馆的写作方式吗?”

不,通过使用异常进行错误处理来编写专业库 - 我不确定是否存在使用建议方法的模式,但我认为它是反模式(在.NET中)。毕竟,.NET本身就是一个专业的框架,它uses exceptions。此外,.NET开发人员习惯于例外。您是否认为您的库非常特别,以迫使用户学习完全不同的错误处理方式?

你刚刚做的是重新发明COM错误处理。如果这是您想要做的,请查看thisISupportErrorInfo界面以获取一些想法。

你为什么要这样做?我敢打赌这是一次表演“优化”。

担心异常处理方面的性能问题几乎总是不成熟的优化。您将创建一个笨拙的API,其中每个返回值必须通过ref / out参数处理,这将损害lib的每个用户,只是为了解决可能根本不存在的问题。

  

“Func A可能不知道调用函数是否具有   智能从错误中恢复与否,所以我不想   抛出异常“

所以你想确保调用者默默地允许FuncA搞乱系统不变量,并且调用者只是快乐地继续下去?这将使调试看起来不可能的错误变得更加困难,因为这会在以后的另一个函数中发生。

有些情况下避免异常是有意义的,但应该有一个很好的理由。例外是好主意。

编辑:我看到你已经添加了“你还阅读了很多其他文章,建议不要使用代码流控制的例外”。这是正确的,.NET中的异常不是代码流,而是错误处理。

你问:

  

如果Func A调用Func B,C,D,E,F,则必须封装每个调用   尝试捕获因为它可以从错误中恢复或它仍然会喜欢   执行剩余的函数调用,然后尝试catch不是那么多   陈述尴尬

不过是另类。你犯了一个错误,你可以用同样的方式简单地处理从函数返回的所有错误,但你通常不能。

考虑是否需要单独处理每个函数 - 最坏的情况和代码通常不是这样编写的:

Result x, y;
try {
   x = Function1();
}
catch(SomeException e) {
   // handle error   
}
try {
   y = Function2();
}
catch(SomeOtherException e) {
   // handle error   
}

针对:

int error;
Result x, y;
error = Function1(out x);
if(error != SOME_KNOWN_ISSUE) {
   // handle error    
}
error = Function2(out y);
if(error != SOME_KNOWN_ISSUE) {
   // handle error
}

没有太大的区别。请不要告诉我你不会检查错误代码。 但是,如果您决定忽略所有错误(一个可怕的想法),那么异常就更简单了:

try {
    var x = Function1();
    var y = Function2();    
    var z = Function3();
}
catch Exception() { you still can see the message here and possibly rethrow }

VS

Result1 r1;
Function1(out r1);
Result2 r2;
Function2(out r2);
Result3 r3;
Function3(out r3);
// and here you still don't know whether there was an error
  

你能详细说明“我需要关于时间限制的可预测性”是什么意思吗?

在某些系统级软件或实时内容中,您无法负担与异常处理相关的堆栈展开,因为您无法保证持续时间,这可能会违反您的时序要求。但在.NET中从来就不是这样,因为垃圾收集在这方面要差得多。

  

另外,当你说“在.NET中我会永远使用例外   “错误处理”,你能解释一下你如何定义为错误   条件?可恢复情况是否是错误情况   错误情况? -

@shambulater已经在评论中给出了一个很好的例子。在FileStream中,丢失的文件不可恢复,它将抛出。在FileStream的客户端中,根据上下文,它可能是可恢复的。有些客户会忽略它,有些客户会退出应用程序,有些会将其包装在另一个例外中,让上游某人决定。

  

什么时候不使用例外?

在那些我也不会返回错误代码的情况下。

答案 2 :(得分:1)

我在ms-access中广泛使用FunctionResult方法,它运行得非常好。我认为它远比错误处理好。首先,每条错误消息都是特定于应用程序的,并不是通常关闭目标的默认错误消息。如果错误传播了函数的调用列表,则错误消息可以菊花链式连接在一起。此最终错误消息看起来像一个调用堆栈,但更清晰,例如[无法读取驱动器F中的照片:无法读取文件,驱动器未准备就绪]。 Wacko,我刚刚发现一些驱动器可以安装但没有准备好。我无法对该错误进行单元测试,因为我不知道会发生这样的错误(意味着SD卡读卡器为空)。即使没有先前对此错误的了解,我也可以编写一个优雅地处理它的应用程序。

我的方法是在类中调用一个方法,该类被编写为返回布尔值的函数。返回值在函数的最后一行设置为True,因此如果函数在最后一行之前退出,则默认情况下不成功。我编码,调用函数看起来像getphotos(folderID)然后...做某事..否则报告错误。类模块内部是模块级错误变量(Str mEM),它通过getter读取,因此该类具有.em属性,该属性保存错误消息。我还有一个注释变量,有时像错误信息一样使用,例如,如果文件夹为空,寻找照片的代码有效,但没有返回任何照片。这不会是一个错误,但我可能想要与调用程序进行通信。如果出现错误,用户将收到错误消息,并且调用过程将退出。相比之下,如果有一个cmt,例如“没有照片”,那么我可能会尝试阅读照片元数据。 Zdeslav Vojkovic如何处理那些有例外情况的摘要?

我正在转向C#,因此找到了这个帖子。我喜欢知道为什么函数调用失败的确定性(我一直与数据库和文件系统交互,所以我很难用单元测试来覆盖我的项目)。我同意Zdeslav Vojkovic关于使用其使用的标准的异常,但不会在我自己的代码中这样做。我正在寻找一个干净的设计模式,它允许我验证被调用函数内的参数,并通知调用者参数是否正确。