网站怎么做mip技术,外贸找客户软件,拼多多网站建设过程,网站开发如何学习随着团队越来越多#xff0c;越来越大#xff0c;需求更迭越来越快#xff0c;每天提交的代码变更由原先的2位数#xff0c;暴涨到3位数#xff0c;每天几百次代码Check In#xff0c;补丁提交#xff0c;大量的代码审查消耗了大量的资源投入。如何确保提交代码的质量和… 随着团队越来越多越来越大需求更迭越来越快每天提交的代码变更由原先的2位数暴涨到3位数每天几百次代码Check In补丁提交大量的代码审查消耗了大量的资源投入。如何确保提交代码的质量和提测产品的质量这两个是非常大的挑战。工欲善其事必先利其器。在上述需求背景下今年我们准备用工具和技术全面把控并提升代码质量和产品提测质量。即1. 代码质量提升通过自定义代码扫描规则将有问题的代码、不符合编码规则的代码扫描出来禁止签入2. 产品提测质量通过单元测试覆盖率和执行通过率严控产品提交质量覆盖率和通过率达不到标准无法提交测试。准备用2篇文章和大家分享我们是如何提升代码质量和产品提测质量的。今天分享第一篇通过Roslyn代码分析全面提升代码质量。一、什么是Roslyn Roslyn 是微软开源的 .NET 编译平台(.NET Compiler Platform)。 编译平台支持 C# 和 Visual Basic 代码编译并提供丰富的代码分析 API。 利用Roslyn可以生成代码分析器和代码修补程序从而发现和更正编码错误。 分析器不仅理解代码的语法和结构还能检测应更正的做法。代码修补程序建议一处或多处修复以修复分析器发现的编码错误。 我们写下面一堆代码Roslyn编译器会有如下提示 通过编写分析器和代码修补程序主要服务以下场景 强制执行团队编码标准Local提供库包方面的指导约束Nuget提供代码分析器相关的VSIX扩展插件Visual Studio Marketplace Roslyn是如何做到代码分析的呢这背后依赖于一套强大的语法分析和API 上图中Language Service语言层面的服务可以简单理解为我们在VS中编码时,可以实现的语法高亮、查找所有引用、重命名、转到定义、格式化、抽取方法等操作 Compiler API编译器API这里提供了Syntax Tree API代码语法树APISymbol API代码符号API Binding and Flow Anllysis APIs绑定和流分析APIhttps://joshvarty.com/2015/02/05/learn-roslyn-now-part-8-data-flow-analysis/ Emit API编译反射发出APIhttps://joshvarty.com/2016/01/16/learn-roslyn-now-part-16-the-emit-api/ 这里我们详细看一下语法树、符号、语义模型、工作区 1. 语法树是一种由编译器 API 公开的基础数据结构。 这些树表示源代码的词法和语法结构。其包含 语法节点是语法树的一个主要元素。这些节点表示声明、语句、子句和表达式等语法构造。语法标记表示代码的最小语法片段。语法标记包含关键字、标识符、文本和标点。琐碎内容对正常理解代码基本上没有意义的源文本部分例如空格、注释和预处理器指令。范围每个节点、标记或琐碎内容在源文本内的位置和包含的字符数。种类标识节点、标记或琐碎内容所表示的确切语法元素。错误表示源文本中包含的语法错误。 看一张语法树的图 2. 符号符号表示源代码声明的不同元素或作为元数据从程序集中导出。每个命名空间、类型、方法、属性、字段、事件、参数或局部变量都由符号表示。 3. 语义模型语义模型表示单个源文件的所有语义信息。可使用语义模型查找到以下内容 在源中特定位置引用的符号。任何表达式的结果类型。所有诊断错误和警告。变量流入和流出源区域的方式。更多推理问题的答案。 4. 工作区工作区是对整个解决方案执行代码分析和重构的起点。相关的API可以实现 将解决方案中项目的全部相关信息组织为单个对象模型可让用户直接访问编译器层对象模型如源文本、语法树、语义模型和编译而无需分析文件、配置选项或管理项目内依赖项。 了解了Roslyn的大致情况之后我们开始基于Roslyn做一些“不符合编程规范要求团队自定义的”的代码分析。二、基于Roslyn进行代码分析 接下来讲通过Show case的方法通过实际的场景和大家分享。在我们编写实际的代码分析器之前我们先把开发环境准备好 使用VS2017创建一个Analyzer with Code Fix工程 因为我本机的VS2019找了好久没找到对应的工程这个章节使用VS2017吧 创建完成会有两个工程 其中TeldCodeAnalyzer.Vsix工程主要用以生成VSIX扩展文件 TeldCodeAnalyzer工程主要用于编写代码分析器。 工程转换好之后我们开始编码吧。 1. catch 吞掉异常场景 问题catch吞掉异常后线上很难排查问题同时确定哪块代码有问题 示例代码123456789try{ var logService HSFService.ProxyILogService(); logService.SendMsg(new SysActionLog());}catch (Exception ex){ } 需求当开发人员在catch吞掉异常时给与编程提示异常吞掉时必须上报监控或者日志 明确了上述需要我们开始编写Roslyn代码分析器。ExceptionCatchWithMonitorAnalyzer 我们详细解读一下 ① ExceptionCatchWithMonitorAnalyzer必须继承抽象类DiagnosticAnalyzer ② 重写方法SupportedDiagnostics注册代码扫描规则DiagnosticDescriptor 1234internal static DiagnosticDescriptor Rule new DiagnosticDescriptor(DiagnosticId, Title, MessageFormat, Category, DiagnosticSeverity.Warning, isEnabledByDefault: true, description: Description); public override ImmutableArrayDiagnosticDescriptor SupportedDiagnostics ImmutableArray.Create(Rule); ③ 重写方法Initialize注册Microsoft.CodeAnalysis.SyntaxNode完成Catch语句的语义分析后的事件Action12345public override void Initialize(AnalysisContext context){ context.ConfigureGeneratedCodeAnalysis(GeneratedCodeAnalysisFlags.Analyze | GeneratedCodeAnalysisFlags.None); context.EnableConcurrentExecution(); context.RegisterSyntaxNodeAction(AnalyzeDeclaration, SyntaxKind.CatchClause);} ④ 实现语法分析AnalyzeDeclaration检查对catch语句中代码实现 123456789101112131415161718private void AnalyzeDeclaration(SyntaxNodeAnalysisContext context){ var catchClause (CatchClauseSyntax)context.Node; var block catchClause.Block; foreach (var statement in block.Statements) { if (statement is ThrowStatementSyntax) { return; } } if (Common.IsReallyContains(block, MonitorClient) false) { context.ReportDiagnostic(Diagnostic.Create(Rule, block.GetLocation())); }} 代码实现后的效果直接调试VSIX工程即可 代码编译后也有对应Warnning提示 2. 在For循环中进行服务调用 问题for循环中调用RPC服务每次访问都会发起一次RPC请求如果循环次数太多性能很差建议使用批量处理的RPC方法 示例代码foreach (var item in items){ var logService HSFService.ProxyILogService(); logService.SendMsg(new SysActionLog());} 需求当开发人员在For循环中调用HSF服务时给与编程提示不建议在循环中调用HSF服务, 建议调用批量处理方法. 明确了上述需要我们开始编写Roslyn代码分析器。HSFForLoopAnalyzer [DiagnosticAnalyzer(LanguageNames.CSharp)]public sealed class HSFForLoopAnalyzer : DiagnosticAnalyzer{ public const string DiagnosticId TA001; internal const string Title 增加循环中HSF服务调用检查; public const string MessageFormat 不建议在循环中调用HSF服务, 建议调用批量处理方法.; internal const string Category CodeSmell; internal static DiagnosticDescriptor Rule new DiagnosticDescriptor(DiagnosticId, Title, MessageFormat, Category, DiagnosticSeverity.Warning, isEnabledByDefault: true); public override ImmutableArrayDiagnosticDescriptor SupportedDiagnostics ImmutableArray.Create(Rule); public override void Initialize(AnalysisContext context) { context.RegisterSyntaxNodeAction(AnalyzeMethodForLoop, SyntaxKind.InvocationExpression); } private static void AnalyzeMethodForLoop(SyntaxNodeAnalysisContext context) { var expression (InvocationExpressionSyntax)context.Node; string exressionText expression.ToString(); if (Common.IsReallyContains(expression, HSFService.Proxy)) { var loop expression.Ancestors().FirstOrDefault(p p is ForStatementSyntax || p is ForEachStatementSyntax || p is DoStatementSyntax || p is WhileStatementSyntax); if (loop ! null) { var diagnostic Diagnostic.Create(Rule, expression.GetLocation()); context.ReportDiagnostic(diagnostic); return; } if (Common.IsReallyContains(expression, .) false) { var syntax expression.Ancestors().FirstOrDefault(p p is LocalDeclarationStatementSyntax); if (syntax ! null) { var declaration (LocalDeclarationStatementSyntax)syntax; var variable declaration.Declaration.Variables.SingleOrDefault(); var method declaration.Ancestors().First(p p is MethodDeclarationSyntax); var expresses method.DescendantNodes().Where(p p is InvocationExpressionSyntax); foreach (var express in expresses) { loop express.Ancestors().FirstOrDefault(p p is ForStatementSyntax || p is ForEachStatementSyntax || p is DoStatementSyntax || p is WhileStatementSyntax); if (loop ! null) { var diagnostic Diagnostic.Create(Rule, expression.GetLocation()); context.ReportDiagnostic(diagnostic); return; } } } } } }} 基本的实现方式和上一个差不多唯一不同的逻辑是在实际的代码分析过程中AnalyzeMethodForLoop。大家可以根据自己的需要写一下。 实际的效果 还有几个代码检查场景基本都是同样的实现思路再次不一一罗列了。 在这里还可以自动完成代理修补程序这个地方我们还在研究中可能每个业务代码的场景不同很难给出一个通用的改进代码所以这个地方等后续我们完成后再和大家分享。三、通过Roslyn实现静态代码扫描 线上很多代码已经写完了发布上线了对已有的代码进行代码扫描也是非常重要的。因此我们对catch吞掉异常的代码进行了一次集中扫描和改进。 那么基于Roslyn如何实现静态代码扫描呢主要的步骤有 ① 创建一个编译工作区MSBuildWorkspace.Create() ② 打开解决方案文件OpenSolutionAsync(slnPath); ③ 遍历Project中的Document ④ 拿到代码语法树、找到Catch语句CatchClauseSyntax ⑤ 判断是否有throw语句如果没有收集数据进行通知改进 看一下具体代码实现 先看一下Nuget引用 Microsoft.CodeAnalysis Microsoft.CodeAnalysis.Workspaces.MSBuild 代码的具体实现 public async TaskListCodeCheckResult CheckSln(string slnPath) { var slnFile new FileInfo(slnPath); var results new ListCodeCheckResult(); var solution await MSBuildWorkspace.Create().OpenSolutionAsync(slnPath); if (solution.Projects ! null solution.Projects.Count() 0) { foreach (var project in solution.Projects.ToList()) { var documents project.Documents.Where(x x.Name.Contains(.cs)); foreach (var document in documents) { var tree await document.GetSyntaxTreeAsync(); var root tree.GetCompilationUnitRoot(); if (root.Members null || root.Members.Count 0) continue; //member var firstmember root.Members[0]; //命名空间Namespace var namespaceDeclaration (NamespaceDeclarationSyntax)firstmember; foreach (var classDeclare in namespaceDeclaration.Members) { var programDeclaration classDeclare as ClassDeclarationSyntax; foreach (var method in programDeclaration.Members) { //方法 Method var methodDeclaration (MethodDeclarationSyntax)method; var catchNode methodDeclaration.DescendantNodes().FirstOrDefault(i i is CatchClauseSyntax); if (catchNode ! null) { var catchClause catchNode as CatchClauseSyntax; if (catchClause ! null || catchClause.Declaration ! null) { if (catchClause.DescendantNodes().OfTypeThrowStatementSyntax().Count() 0) { results.Add(new CodeCheckResult() { Sln slnFile.Name, ProjectName project.Name, ClassName programDeclaration.Identifier.Text, MethodName methodDeclaration.Identifier.Text, }); } } } } } } } } return results; } 以上是通过Roslyn代码分析全面提升代码质量的一些具体实践分享给大家。