• Metalama简介3.自定义.NET项目中的代码分析


    本系列其它文章

    使用基于Roslyn的编译时AOP框架来解决.NET项目的代码复用问题
    Metalama简介1. 不止是一个.NET跨平台的编译时AOP框架
    Metalama简介2.利用Aspect在编译时进行消除重复代码

    代码分析

    这里所说的代码分析,是可以通过一些自定义的方法,在使用不符合条件的代码时产生错误或警告。
    如果配合CI并在每次持续集成时,都向团队分发警告和错误。团队也在开发时遵守谁产生的警告,谁解决的团队约定,那么团队将不断减少技术债务,这样可以避免架构持续性的腐坏。

    image

    当然.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时则有以下提示
    image

    同时也可以在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结尾的命名空间中调用时。
    则会发生如下提示。

    image

    引用

  • 相关阅读:
    网络流24题总结和题解
    NOIP复习之1 数学数论
    BZOJ3301 P2524 UVA11525 算法解释康托展开
    线段树与树状数组的综合运用
    P2261 bzoj1257 [CQOI2007]余数求和
    BZOJ 1968_P1403 [AHOI2005]约数研究--p2260bzoj2956-模积和∑----信息学中的数论分块
    P1064 金明的预算方案
    洛谷p1002 过河卒
    Luogu P3014 [USACO11FEB]牛线Cow Line
    Luogu P3927 SAC E#1
  • 原文地址:https://www.cnblogs.com/chsword/p/metalama_3.html
Copyright © 2020-2023  润新知