在我看来,您有可能改进将此日志记录横切关注点添加到应用程序的方式。
这里的主要问题是,尽管您的解决方案阻止您对SomeClass.SomeMethod
(或任何调用的方法)进行任何更改,但您仍然需要对使用代码进行更改。换句话说,您打破了开放/封闭原则,它告诉我们必须可以在不更改任何现有代码的情况下进行此类更改。
您可能认为我在夸大其词,但您HandleServerError
的应用程序中可能已经有超过一百个调用,而且调用的数量只会越来越多。你很快就会在系统中添加更多的“功能装饰器”。您是否考虑过进行任何授权检查、方法参数验证、检测或审计跟踪?你必须承认,做事new Func<T>(() => someCall).HandleServerError()
感觉很乱,不是吗?
通过向系统引入正确的抽象,您可以解决所有这些问题,包括您实际问题的问题。
第一步是将给定的方法参数提升为Parameter Object:
public SomeMethodParameters
{
public int Id { get; set; }
public string Name { get; set; }
public Color Color { get; set; }
public decimal Quantity { get; set; }
public decimal Result { get; set; }
}
我们可以将它们作为一个对象一起传递,而不是将所有单独的参数传递给一个方法。你说这有什么用?继续阅读。
第二步是引入一个通用接口来隐藏SomeClass.SomeMethod
(或实际上任何方法)背后的实际逻辑:
public interface IMethodHandler<TParameter>
{
void Handle(TParameter parameter);
}
对于系统中的每个(业务)操作,您都可以编写一个IMethodHandler<TParameter>
实现。在您的情况下,您可以简单地创建一个包装调用的实现SomeClass.SomeMethod
,如下所示:
public class SomeMethodHandler
: IMethodHandler<SomeMethodParameters>
{
void Handle(SomeMethodParameters parameter)
{
parameter.Result = SomeClass.SomeMethod(
parameter.id,
parameter.Name,
parameter.Color,
parameter.Quantity);
}
}
做这样的事情可能看起来有点傻,但它可以让你快速实现这个设计,并将静态的逻辑移动SomeClass.SomeMethod
到SomeMethodHandler
.
第三步是让消费者依赖于一个IMethodHandler<SomeMethodParameters>
接口,而不是让他们依赖于系统中的一些静态方法(在你的情况下也是SomeClass.SomeMethod
)。想一想依赖这种抽象有什么好处。
这样做的一个有趣结果是,它使对消费者进行单元测试变得更加容易。但也许你对单元测试不感兴趣。但是您对松散耦合感兴趣。当消费者依赖这样的抽象而不是真正的实现(尤其是静态方法)时,您可以做各种疯狂的事情,例如添加横切关注点,例如日志记录。一个很好的方法是用装饰器IMethodHandler<T>
包装实现。这是您的用例的装饰器:
public class LoggingMethodHandlerDecorator<T>
: IMethodHandler<T>
{
private readonly IMethodHandler<T> handler;
public LoggingMethodHandlerDecorator(
IMethodHandler<T> handler)
{
this.handler = handler;
}
public void Handle(T parameters)
{
try
{
this.handler.Handle(parameters);
}
catch (Exception ex)
{
//******************************
//Code for logging will go here.
//******************************
ErrorHandlers.ThrowServerErrorException(ex);
throw;
}
}
}
看看Handle
这个装饰器的方法是如何包含你原来HandleServerError<T>
方法的代码的?实际上,它与您已经在做的事情并没有太大的不同,因为HandleServerError
“装饰”(或“扩展”)原始方法的行为具有新的行为。但我们现在使用的是对象,而不是使用方法调用。
所有这一切的好处是,这个单一的泛型LoggingMethodHandlerDecorator<T>
可以包装在每个IMethodHandler<T>
实现中,并且可以被每个消费者使用。通过这种方式,我们可以添加横切关注点,例如日志记录等,而无需消费者和方法都知道这一点。这就是开放/封闭原则。
但这还有其他一些非常好的东西。您最初的问题是关于如何获取有关方法名称和参数的信息。好吧,所有这些信息现在都很容易获得,因为我们已经将所有参数包装在一个对象中,而不是调用包装在Func
委托中的一些自定义方法。我们可以这样实现catch
子句:
string messageInfo = string.Format("<{0}>{1}</{0}>",
parameters.GetType().Name, string.Join("",
from property in parameters.GetType().GetProperties()
where property.CanRead
select string.Format("<{0}>{1}</{0}>",
property.Name, property.GetValue(parameters, null)));
这会将 TParameter 对象的名称及其值序列化为 XML 格式。或者您当然可以使用 .NETXmlSerializer
将对象序列化为 XML 或使用您需要的任何其他序列化。所有信息(如果在元数据中可用),这非常好。当您给参数对象一个好的且唯一的名称时,它允许您立即在日志文件中识别它。连同实际参数和可能的一些上下文信息(例如日期时间、当前用户等),您将拥有修复错误所需的所有信息。
LoggingMethodHandlerDecorator<T>
这个和你原来的有一个区别HandleServerError<T>
,那就是最后throw
一句话。您的实现实现了某种ON ERROR RESUME NEXT
可能不是最好的事情。当方法失败时继续(并返回默认值)实际上是否安全?根据我的经验,通常情况并非如此,并且继续这一点,可能会使编写消费类的开发人员认为一切都按预期工作,或者甚至可能使应用程序的用户认为一切都按预期进行(他的更改例如被保存了,而事实上他们没有)。通常对此您无能为力,并且将所有内容都包含在内catch
陈述只会使情况变得更糟,尽管我可以想象您想要记录此信息。不要被用户要求所迷惑,例如“应用程序必须始终工作”或“我们不想看到任何错误页面”。通过抑制所有错误来实现这些要求将无济于事,也不会解决根本原因。但尽管如此,如果你真的需要捕捉并继续,只需删除throw
语句`,你就会回到原来的行为。
如果您想了解更多关于这种系统设计方式的信息:从这里开始。