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

C# 用于向方法添加入口和出口跟踪的模式,c#,C#,当一个方法有多个退出点时,我试图找出跟踪该方法返回值的最佳方法。我有几十种方法要添加跟踪。我会把我试过的东西再看一遍 首先,我尝试重构每个方法,使其有一个单一的退出点,如下所示: StatusCode CreateResource(string name, string type) { Trace.LogEvent("BEGIN CreateResource name=" + name + " type=" + type); StatusCode status = StatusC

当一个方法有多个退出点时,我试图找出跟踪该方法返回值的最佳方法。我有几十种方法要添加跟踪。我会把我试过的东西再看一遍

首先,我尝试重构每个方法,使其有一个单一的退出点,如下所示:

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;
}
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);
    }
}
但是嵌套的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;
}
问题是如何防止将来其他开发人员(或我)调用wrapped方法并绕过跟踪,从而导致wrapped方法的名称荒谬(且矛盾)。我可以为内部方法定义并调用一个匿名方法,但在整个项目中使用这种模式相当混乱

我发现的最可靠、可读性最强的方法是这样的,在我看来这还行,但我怀疑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;
}
这里的缺点是,在返回之前可能会忘记设置“status”变量,在这种情况下将不会执行跟踪

我的问题是,有没有这样做的最佳实践?我需要重构到一个单一的退出点吗?是否有某种方法可以防止从其他地方调用包装的方法?允许绕过跟踪的危险是否比重构到单个出口点的不整洁更严重


p、 我不想引入面向方面编程之类的繁重内容。

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

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

大概是这样的:

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;
}
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模式就是最好的解决方案(只是一个代码草图):

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

new LoggedAImpl(new AImpl()); //pass it everywhere A is needed
使用这个似乎优雅和相对无痛

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

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

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


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

这些建议对你有用吗?我正在平衡简单性和“正确性”,所以我接受了这个答案,因为它简单,尽管其他答案更“正确”(特别是“使用AOP”)。我同意,这种方法确实有其缺点。我主要坚持使用它,因为我继承了一个很大的代码库,并且很多日志条目都是定制的,所以重构日志记录将不是一件容易的事情。