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

本文关键字:出口 跟踪 模式 入口 方法 添加 用于 | 更新日期: 2023-09-27 18:27:51

当一个方法有多个出口点时,我正试图找出跟踪该方法返回值的最佳方法。我有几十种方法要添加跟踪。我将运行我尝试过的方法。

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

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语句使其丑陋且可读性较差。我大量使用早期的退出点作为保护语句,而重构这一切只会造成混乱,感觉就像是去重构。

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

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-finaly块的变体:

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我不想引入像面向方面编程这样的繁重内容。

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

作为一个附带的答案,您可以看看实现Log4PostSharp,您可以在这里的链接中看到教程。它可能不会直接回答你的问题,但会对你的情况有所帮助。

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

我知道你在你的问题中提到,你不想在你的应用程序中添加任何AOP风格的编程,但我真的建议为此实现Microsoft Unity。它支持拦截,这正是您在这里试图解决的场景。只要你遵循良好的编程实践,对接口进行编码,你就会惊讶于它的实现是多么容易(并做一些非常酷的事情!)。

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

我一直使用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);
    }
}

我会使用依赖注入-如果每个类都实现一个接口,那么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

统一地使用它似乎很优雅,而且相对来说很无痛。

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