【问题标题】:Pattern for adding entry and exit trace to a method将进入和退出跟踪添加到方法的模式
【发布时间】:2013-08-15 20:28:54
【问题描述】:

当方法具有多个退出点时,我试图找出跟踪方法返回值的最佳方法。我有几十种方法要添加跟踪。我将完成我尝试过的。

首先,我尝试将每个方法重构为具有这样的单个退出点:

StatusCode CreateResource(string name, string type)
{
    Trace.LogEvent("BEGIN CreateResource name=" + name + " type=" + type);
    StatusCode status = StatusCode.Ok;
    if (!IsValidResourceName(name))
        status = StatusCode.InvalidName;
    else
    {
        if (!IsValidResourceType(type))
            status = StatusCode.InvalidType;
        else
        {
            if (!SystemOnline())
                status = StatusCode.SystemOffline;
            //continues to nest with more conditions
        }
    }
    Trace.LogEvent("END CreateResource result=" + status);
    return status;
}

但是嵌套的 if 语句使它变得丑陋且可读性差。我大量使用了guard statements 的早期退出点,并且重构它只会造成一团糟,感觉就像解重构。

我尝试的另一件事是将每个方法包装在另一个跟踪返回值的方法中:

StatusCode CreateResource(string name, string type)
{
    Trace.LogEvent("BEGIN CreateResource name=" + name + " type=" + type);
    StatusCode status = CreateResource_DONT_CALL_THIS_METHOD(name, type);
    Trace.LogEvent("END CreateResource result=" + status);
    return status;
}

StatusCode CreateResource_DONT_CALL_THIS_METHOD(string name, string type)
{
    if (!IsValidResourceName(name))
        return StatusCode.InvalidName;
    if (!IsValidResourceType(type))
        return StatusCode.InvalidType;
    if (!SystemOnline())
        return StatusCode.SystemOffline;
    return StatusCode.Ok;
}

问题是如何防止将来其他开发人员(或我)调用包装方法并绕过跟踪,因此包装方法的名称荒谬(且自相矛盾)。我可以为内部方法定义和调用匿名方法,但在整个项目中使用这种模式相当混乱。

我发现的最可靠、最易读的方法是这样的,这在 IMO 中还可以,但我怀疑这种 for 语句的使用是否会在代码审查中发挥作用:

StatusCode CreateResource_Internal(string name, string type)
{
    Trace.LogEvent("BEGIN CreateResource name=" + name + " type=" + type);
    StatusCode status = StatusCode.Ok;
    for (int i = 0; i < 1; i++)
    {
        if (!IsValidResourceName(name))
        {
            status = StatusCode.InvalidName;
            break;
        }
        if (!IsValidResourceType(type))
        {
            status = StatusCode.InvalidType;
            break;
        }
        if (!SystemOnline())
        {
            status = StatusCode.SystemOffline;
            break;
        }
    }
    Trace.LogEvent("END CreateResource result=" + status);
    return status;
}

我使用 try finally 块尝试了一个变体:

StatusCode CreateResource_Internal(string name, string type)
{
    Trace.LogEvent("BEGIN CreateResource name=" + name + " type=" + type);
    StatusCode status = StatusCode.Ok;
    try
    {
        if (!IsValidResourceName(name))
        {
            status = StatusCode.InvalidName;
            return status;
        }
        if (!IsValidResourceType(type))
        {
            status = StatusCode.InvalidType;
            return status;
        }
        if (!SystemOnline())
        {
            status = StatusCode.SystemOffline;
            return status;
        }
    }
    finally
    {
        Trace.LogEvent("END CreateResource result=" + status);
    }
    return StatusCode.Ok;
}

这里的缺点是在返回之前可能忘记设置“状态”变量,在这种情况下将不会执行跟踪。

我的问题是,是否有这样做的最佳做法?我是否应该重构为单个退出点?是否有某种方法可以防止从其他地方调用包装的方法?允许绕过跟踪的危险是否比重构为单个出口点的不整洁更糟糕?

p.s 我不想引入诸如面向方面编程之类的繁重内容。

【问题讨论】:

  • 这些建议对您有用吗?

标签: c#


【解决方案1】:

作为一个侧面答案,您可以查看实现Log4PostSharp,您可以在链接here 中查看教程。它可能不会直接回答您的问题,但会对您的方案有所帮助。

【讨论】:

    【解决方案2】:

    编写跟踪文件(或任何形式的日志记录)是Cross Cutting Concern 的典型示例,您应该尽量避免在您的方法中使用这种类型的代码,因为它会使它们 A) 可读性降低和 B)重复了很多代码。

    我知道您在问题中提到您不想将任何 AOP 风格的编程添加到您的应用程序中,但我真的建议为此实施 Microsoft Unity。它支持Interception,这正是您在此处尝试解决的场景。只要您遵循 coding to an interface 的良好编程实践,您就会惊讶于它是多么容易实现(并做一些非常酷的事情!)。

    只是一些思考的食物......

    【讨论】:

      【解决方案3】:

      我一直使用try-finally 方案,但将整个方法封装在try-block 中。

      类似这样的:

      StatusCode CreateResource_Internal(string name, string type)
      {
          try
          {
              Trace.LogEvent("BEGIN CreateResource name=" + name + " type=" + type);
              StatusCode status = StatusCode.Ok;
      
              if (!IsValidResourceName(name))
              {
                  status = StatusCode.InvalidName;
                  return status;
              }
              if (!IsValidResourceType(type))
              {
                  status = StatusCode.InvalidType;
                  return status;
              }
              if (!SystemOnline())
              {
                  status = StatusCode.SystemOffline;
                  return status;
              }
              status = StatusCode.Ok; // A bit silly, but that avoids the problem of status not being set.
              return status;
          }
          finally
          {
              Trace.LogEvent("END CreateResource result=" + status);
          }
      }
      

      【讨论】:

      • 我在简单性与“正确性”之间取得平衡,因此接受了这个答案,因为它很简单,即使其他答案更“正确”(尤其是“使用 AOP”)。
      • 我同意,这种方法确实有其缺点。我主要是坚持下去,因为我继承了一个庞大的代码库,而且很多日志条目都是自定义的,所以重构日志并不是一件容易的事。
      【解决方案4】:

      我会使用依赖注入 - 如果每个类都实现一个接口,那么装饰器模式是最好的解决方案(只是一个代码草图):

      interface A
      {
        int method1(float x);
      }
      
      class AImpl : A
      {
          public int method1(float x) { }
      }
      
      class LoggedAImpl : A
      {
      private AImpl innerA;
      
      public int method1(float x) 
      {
        //log method and parameters
        int result;
        try
        {
          result = innerA.method1(x);
        }
        finally
        {
          //log method exit
        }
      }
      }
      

      然后,在应用程序设置中:

      new LoggedAImpl(new AImpl()); //pass it everywhere A is needed
      

      将它与unity 一起使用看起来很优雅,而且相对来说没有什么痛苦。

      如果你不想使用统一,那么至少工厂模式以及上面的就足够了。

      【讨论】:

        猜你喜欢
        • 1970-01-01
        • 1970-01-01
        • 2021-11-23
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2018-06-24
        • 1970-01-01
        • 1970-01-01
        相关资源
        最近更新 更多