用于向方法添加进入和退出跟踪的模式

时间:2013-08-15 10:20:56

标签: c#

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

首先,我尝试重构每个方法,以便有一个像这样的退出点:

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;
}

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

我的问题是,有这样做的最佳做法吗?我是否希望重构到一个退出点?有没有办法阻止从其他地方调用包装的方法?允许跟踪的危险是否比重构到单一退出点的不整洁更糟?

p.s我不想引入像面向方面编程那样沉重的东西。

4 个答案:

答案 0 :(得分:3)

作为旁边的答案,您可以查看实施Log4PostSharp,您可以在链接here中看到教程。它可能无法直接回答您的问题,但它会对您的方案有所帮助。

答案 1 :(得分:3)

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

我知道你在问题中提到你不想在你的应用程序中添加任何AOP样式编程,但我真的建议为此实现Microsoft Unity。它支持Interception,这正是您在此尝试解决的问题。只要你遵循coding to an interface的良好编程习惯,你会惊讶地发现它是多么容易(并做一些非常酷的东西!)。

只是一些值得思考的东西......

答案 2 :(得分:2)

我一直使用try-finally方案,但整个方法都包含在try - 块中。

这样的事情:

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);
    }
}

答案 3 :(得分:1)

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

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一起使用似乎优雅且相对无痛。

如果您不想使用unity,那么至少Factory模式以及上面的内容就足够了。