5

这不是什么大问题,而是更多的反馈和想法。我一直在考虑通过我们的内部团队彻底测试过的方法的实现。我想编写一个通用的异常捕获方法和报告服务。

我认为这不像“try-catch”块那么容易,但允许使用统一的方法来捕获异常。理想情况下,我想执行一个方法,提供一个失败回调并记录调用方法的所有参数。

通用尝试执行。

public class ExceptionHelper
{
     public static T TryExecute<T, TArgs>(Func<TArgs, T> Method, Func<TArgs, T> FailureCallBack, TArgs Args)
     {
            try
            {
                return Method(Args);
            }
            catch (Exception ex)
            {
                StackTrace stackTrace = new StackTrace();
                string method = "Unknown Method";
                if (stackTrace != null && stackTrace.FrameCount > 0)
                {
                    var methodInfo = stackTrace.GetFrame(1).GetMethod();
                    if (methodInfo != null)
                        method = string.Join(".", methodInfo.ReflectedType.Namespace, methodInfo.ReflectedType.Name, methodInfo.Name);
                }
                List<string> aStr = new List<string>();
                foreach (var prop in typeof(TArgs).GetProperties().Where(x => x.CanRead && x.CanWrite))
                {
                    object propVal = null;
                    try
                    {
                        propVal = prop.GetValue(Args, null);
                    }
                    catch
                    {
                        propVal = string.Empty;
                    }
                    aStr.Add(string.Format("{0}:{1}", prop.Name, propVal.ToString()));
                }
                string failureString = string.Format("The method '{0}' failed. {1}", method, string.Join(", ", aStr));
                //TODO: Log To Internal error system
                try
                {
                    return FailureCallBack(Args);
                }
                catch
                {
                    return default(T);
                }
            }
      }
}

我所知道的退路。

  • 使用反射的性能损失
  • MethodBase(methodInfo)可能无法通过优化获得
  • 错误处理程序周围的 try-catch。基本上,我可以使用 TryExecute 包装器来围绕错误回调进行 try-catch,但这可能会导致堆栈溢出情况。

这是一个示例实现

var model = new { ModelA = "A", ModelB = "B" };
return ExceptionHelper.TryExecute((Model) =>
{
     throw new Exception("Testing exception handler");
},
(Model) =>
{
    return false;
}, 
model);

想法和评论表示赞赏。

4

2 回答 2

12

放入 a 中有很多代码,包括catch两个/ trycatch。如果您问我,这似乎有点矫枉过正,有很大的风险是进一步的异常可能会掩盖实际的异常并且错误信息会丢失。

还有,为什么return default(T)?返回默认值或空值作为问题的指示通常很草率。如果没有别的,它需要在每次调用该方法时都包含相同的条件,以检查返回并响应......一些现在已经到其他地方的错误。

老实说,这个用法示例看起来也很混乱。看起来您最终会用错误捕获代码掩盖实际的业务逻辑。整个代码库看起来像一系列错误陷阱,实际的业务逻辑隐藏在其中的某个地方。这将宝贵的注意力从应用程序的实际意图上移开,并将后台基础设施重要性(日志记录)放在首位。

简化。

如果方法中发生异常,您通常有两个明智的选择:

  1. 在方法中捕获(有意义地处理)异常。
  2. 让异常在堆栈中冒泡,以便在其他地方捕获。

逃避它发生的方法范围的异常绝对没有错。事实上,异常就是为了做到这一点而设计的,它携带有用的堆栈信息,说明发生了什么以及发生在哪里。(而且,如果您向异常添加有意义的运行时上下文,它还可以携带有关原因的信息。)

事实上,编译器甚至巧妙地暗示了这一点。以这两种方法为例:

public int Sum(int first, int second)
{
    // TODO: Implement this method
}

public int Product(int first, int second)
{
    throw new NotImplementedException();
}

其中一种方法将编译,其中一种不会。编译器错误将指出并非所有代码路径都返回前一种方法的值。但为什么不是后者?因为抛出异常对于方法来说是完全可以接受的退出策略。这就是方法如何放弃它正在做的事情(它应该尝试做的一件事,仅此而已),让调用代码处理这个问题。

代码应该以清楚地表达被建模的业务概念的方式阅读。错误处理是一个重要的基础架构概念,但它只是……基础架构。代码实际上应该清晰而简洁地表明正在建模的业务概念基础设施问题不应该妨碍这一点。

于 2012-09-04T00:18:53.390 回答
6

这很少有用。

它仅涵盖以下情况:

  1. 该方法具有明确定义的方法,可以在失败时获得适当的返回值。
  2. 你真的很想记录它发生了。

现在,2 很常见,除了各种例外,但 1 也不是真的。

1 当然很少见,因为在大多数情况下,如果您可以通过手段 X 为给定参数生成合理的返回值,您就不会首先尝试手段 Y。

default(T)如果回退不起作用,它还具有返回的默认行为- 因此为 null 或全为零。

这仅适用于您上面的案例 1 有“结果只是返回 null 的东西,因为我们不太关心这个东西做了什么”,或者被调用的方法从不返回 null,在这种情况下,您然后测试 null ,这意味着你真正的错误处理代码发生在那里。

总而言之,您在这里所得到的是一种方式,在这种方式中,可以通过实际代码捕获的异常必须通过测试(有时是测试+猜测)来捕获,而那些会使程序在一个清晰的地方崩溃的方式具有良好的调试信息的情况下,它会将其置于您不知道任何地方发生了什么的状态,但至少在某些东西设法将其完全关闭之前记录的几十个错误中,其中一个可能是实际问题

当您因特定原因捕获某些异常时,请务必记录该异常。请注意,这并不能帮助找到错误(如果引发的异常存在错误,您不应该在那里捕获它),而是要取消存在捕获可能隐藏错误的事实 - 即取消通过在所有地方放置捕获物来刻意鼓励您的效果。(例如,您希望定期命中的 Web 服务有时会无法连接,并且您可以使用缓存的数据持续几个小时 - 所以您捕获故障并从缓存中继续 - 在这里您记录,因为如果有错误意味着您从来没有试图正确地点击网络服务,你只是隐藏了它)。

让一些非交互式(服务或服务器)应用程序记录所有到达堆栈顶部的异常也是合理的,因为没有人注意到异常。

但例外不是敌人,它们是使者。不要射击信使。

于 2012-09-04T00:29:35.730 回答