- A+
本系列其它文章
使用基于Roslyn的编译时AOP框架来解决.NET项目的代码复用问题
Metalama简介1. 不止是一个.NET跨平台的编译时AOP框架
Metalama简介2.利用Aspect在编译时进行消除重复代码
代码分析
这里所说的代码分析,是可以通过一些自定义的方法,在使用不符合条件的代码时产生错误或警告。
如果配合CI并在每次持续集成时,都向团队分发警告和错误。团队也在开发时遵守谁产生的警告,谁解决的团队约定,那么团队将不断减少技术债务,这样可以避免架构持续性的腐坏。
当然.NET
自身及一些三方工具如Resharper
已经提供了很多的代码分析功能,包括但不限于命名、代码调用等。但是有时想要更近一步地为团队增加更加定制化地代码分析,却没有对应的办法。
Metalama
中也提供了代码分析功能。
下面我们以几个示例来演示Metalama
中如何使用代码分析。
通用自定义代码分析示例Logger
(源码见最后)
以我们最初的Log示例为例,假设我们当前要引入ILogger
来记录日志,来替换当前的Console.WriteLine
。
interface ILogger { void Info(string message); } public class ConsoleLogger : ILogger { public void Info(string message) { Console.WriteLine(message); } }
那么Program
也要做出修改。
class Program { ILogger _logger = new ConsoleLogger(); public static void Main(string[] args) { var r = new Program().Add(1, 2); Console.WriteLine(r); } // 在这个方法中使用了下面的Attribute [LogAttribute] private int Add(int a, int b) { var result = a + b; return result; } }
而LogAttribute
也要进行修改。
public class LogAttribute : OverrideMethodAspect { public override dynamic? OverrideMethod() { meta.This._logger.Info(meta.Target.Method.ToDisplayString() + " 开始运行."); var result = meta.Proceed(); meta.This._logger.Info(meta.Target.Method.ToDisplayString() + " 结束运行."); return result; } }
接下来我们可以为LogAttribute
添加代码分析,要求LogAttribute
的方法的所在的类上,必须有_logger
且类型必须为ILogger
。
public class LogAttribute : OverrideMethodAspect { static DiagnosticDefinition<(INamedType DeclaringType, IMethod Method)> _loggerFieldNotFoundError = new( "DEMO01", Severity.Error, "类型'{0}'必须包含ILogger类型的字段 '_logger'因为使用了[Log]Aspect在'{1}'上."); // Entry point of the aspect. public override void BuildAspect(IAspectBuilder<IMethod> builder) { // 此处必须调用,否则目标方法将不会被覆盖,因为这里Override与BuildAspect共同使用了 base.BuildAspect(builder); // 验证字段 var loggerField = builder.Target.DeclaringType.Fields.OfName("_logger").SingleOrDefault(); if (loggerField == null || !loggerField.Type.Is(typeof(ILogger)) || loggerField.IsStatic) { // 报告异常 builder.Diagnostics.Report(_loggerFieldNotFoundError.WithArguments((builder.Target.DeclaringType, builder.Target)), builder.Target.DeclaringType); // 不执行Aspect builder.SkipAspect(); return; } } public override dynamic? OverrideMethod() { meta.This._logger.Info(meta.Target.Method.ToDisplayString() + " 开始运行."); var result = meta.Proceed(); meta.This._logger.Info(meta.Target.Method.ToDisplayString() + " 结束运行."); return result; } }
这样当我们代码中有错误,将会触发提示。
如果没有_logger
或 _logger
类型不对或为static
时则有以下提示
同时也可以在Aspect
中定义Eligibility
,在编译时检查Aspect
作用的目标是否符合要求。
下面的代码加到LogAttribute
就会检查Add
方法是否为非static
。
public override void BuildEligibility( IEligibilityBuilder<IMethod> builder ) { base.BuildEligibility( builder ); builder.MustBeNonStatic(); }
此时若将Add
修改为static
则会提示
error LAMA0037: The aspect 'Log' cannot be applied to 'Program.Add(int, int)' because 'Program.Add(int, int)' must be non-static.
自定义一个代码分析:要求当前方法只能在符合规则的命名空间中使用
当一个团队存在多个项目时,我们会约定这里的某些项目的命名必须符合某一规则。
例如,当我们构建一个微服务项目时,我们会要求所有的数据库调用,都发生在指定的命名空间中。
此时我们可以使用一个自定义的Aspect
构造一个方法的代码验证规则。
下面这个示例是要求调用函数的命名空间必须符合以.Tests
结尾的规则,否则给出警告
using Metalama.Framework.Aspects; using Metalama.Framework.Code; using Metalama.Framework.Diagnostics; using Metalama.Framework.Validation; namespace LogWithWarning { class ForTestOnlyAttribute : Aspect, IAspect<IDeclaration> { private static readonly DiagnosticDefinition<IDeclaration> _warning = new( "DEMO02", Severity.Warning, "'{0}' 只能在一个以 '.Tests'结尾的命名空间中使用"); public void BuildAspect(IAspectBuilder<IDeclaration> builder) { builder.WithTarget().RegisterReferenceValidator(this.ValidateReference, ReferenceKinds.All); } private void ValidateReference(in ReferenceValidationContext context) { if (!context.ReferencingType.Namespace.FullName.EndsWith(".Tests")) { context.Diagnostics.Report(_warning.WithArguments(context.ReferencedDeclaration)); } } } }
此时当我们在非.Tests
结尾的命名空间中调用时。
则会发生如下提示。