<kbd id="afajh"><form id="afajh"></form></kbd>
<strong id="afajh"><dl id="afajh"></dl></strong>
    <del id="afajh"><form id="afajh"></form></del>
        1. <th id="afajh"><progress id="afajh"></progress></th>
          <b id="afajh"><abbr id="afajh"></abbr></b>
          <th id="afajh"><progress id="afajh"></progress></th>

          .NET Core 通過Roslyn代碼分析技術(shù)規(guī)范提升代碼質(zhì)量

          共 9815字,需瀏覽 20分鐘

           ·

          2022-10-15 10:31


          轉(zhuǎn)自:Eric zhou
          cnblogs.com/tianqing/p/12815747.html

          前言


          隨著團(tuán)隊(duì)越來越多,越來越大,需求更迭越來越快,每天提交的代碼變更由原先的2位數(shù),暴漲到3位數(shù),每天幾百次代碼Check In,補(bǔ)丁提交,大量的代碼審查消耗了大量的資源投入。


          如何確保提交代碼的質(zhì)量和提測(cè)產(chǎn)品的質(zhì)量,這兩個(gè)是非常大的挑戰(zhàn)。


          工欲善其事,必先利其器。在上述需求背景下,今年我們準(zhǔn)備用工具和技術(shù),全面把控并提升代碼質(zhì)量和產(chǎn)品提測(cè)質(zhì)量。即:


          1、代碼質(zhì)量提升:通過自定義代碼掃描規(guī)則,將有問題的代碼、不符合編碼規(guī)則的代碼掃描出來,禁止簽入


          2、產(chǎn)品提測(cè)質(zhì)量:通過單元測(cè)試覆蓋率和執(zhí)行通過率,嚴(yán)控產(chǎn)品提交質(zhì)量,覆蓋率和通過率達(dá)不到標(biāo)準(zhǔn),無(wú)法提交測(cè)試。


          準(zhǔn)備用2篇文章,和大家分享我們是如何提升代碼質(zhì)量和產(chǎn)品提測(cè)質(zhì)量的。


          先分享第一篇:通過Roslyn代碼分析全面提升代碼質(zhì)量。


          一、什么是Roslyn


          Roslyn 是微軟開源的 .NET 編譯平臺(tái)(.NET Compiler Platform)。  編譯平臺(tái)支持 C# 和 Visual Basic 代碼編譯,并提供豐富的代碼分析 API。


          利用Roslyn可以生成代碼分析器和代碼修補(bǔ)程序,從而發(fā)現(xiàn)和更正編碼錯(cuò)誤。 


          分析器不僅理解代碼的語(yǔ)法和結(jié)構(gòu),還能檢測(cè)應(yīng)更正的做法。代碼修補(bǔ)程序建議一處或多處修復(fù),以修復(fù)分析器發(fā)現(xiàn)的編碼錯(cuò)誤。


          我們寫下面一堆代碼,Roslyn編譯器會(huì)有如下提示: 



          通過編寫分析器和代碼修補(bǔ)程序,主要服務(wù)以下場(chǎng)景:  


          • 強(qiáng)制執(zhí)行團(tuán)隊(duì)編碼標(biāo)準(zhǔn)(Local)


          • 提供庫(kù)包方面的指導(dǎo)約束(Nuget)


          • 提供代碼分析器相關(guān)的VSIX擴(kuò)展插件(Visual Studio Marketplace)


          Roslyn是如何做到代碼分析的呢?這背后依賴于一套強(qiáng)大的語(yǔ)法分析和API:



          圖中:Language Service:語(yǔ)言層面的服務(wù),可以簡(jiǎn)單理解為我們?cè)赩S中編碼時(shí),可以實(shí)現(xiàn)的語(yǔ)法高亮、查找所有引用、重命名、轉(zhuǎn)到定義、格式化、抽取方法等操作


          Compiler API:編譯器API,這里提供了Syntax Tree API代碼語(yǔ)法樹API,Symbol API代碼符號(hào)API


          Binding and Flow Anllysis APIs綁定和流分析API(https://joshvarty.com/2015/02/05/learn-roslyn-now-part-8-data-flow-analysis/),


          Emit API編譯反射發(fā)出API(https://joshvarty.com/2016/01/16/learn-roslyn-now-part-16-the-emit-api/)


          這里我們?cè)敿?xì)看一下語(yǔ)法樹、符號(hào)、語(yǔ)義模型、工作區(qū):


          1、語(yǔ)法樹是一種由編譯器 API 公開的基礎(chǔ)數(shù)據(jù)結(jié)構(gòu)。這些樹表示源代碼的詞法和語(yǔ)法結(jié)構(gòu)。其包含:  


          • 語(yǔ)法節(jié)點(diǎn):是語(yǔ)法樹的一個(gè)主要元素。這些節(jié)點(diǎn)表示聲明、語(yǔ)句、子句和表達(dá)式等語(yǔ)法構(gòu)造。


          • 語(yǔ)法標(biāo)記:表示代碼的最小語(yǔ)法片段。語(yǔ)法標(biāo)記包含關(guān)鍵字、標(biāo)識(shí)符、文本和標(biāo)點(diǎn)。


          • 瑣碎內(nèi)容:對(duì)正常理解代碼基本上沒有意義的源文本部分,例如空格、注釋和預(yù)處理器指令。


          • 范圍:每個(gè)節(jié)點(diǎn)、標(biāo)記或瑣碎內(nèi)容在源文本內(nèi)的位置和包含的字符數(shù)。


          • 種類:標(biāo)識(shí)節(jié)點(diǎn)、標(biāo)記或瑣碎內(nèi)容所表示的確切語(yǔ)法元素。


          • 錯(cuò)誤:表示源文本中包含的語(yǔ)法錯(cuò)誤。


          看一張語(yǔ)法樹的圖:



          2、符號(hào):符號(hào)表示源代碼聲明的不同元素,或作為元數(shù)據(jù)從程序集中導(dǎo)出。每個(gè)命名空間、類型、方法、屬性、字段、事件、參數(shù)或局部變量都由符號(hào)表示。


          3、語(yǔ)義模型:語(yǔ)義模型表示單個(gè)源文件的所有語(yǔ)義信息??墒褂谜Z(yǔ)義模型查找到以下內(nèi)容:   


          • 在源中特定位置引用的符號(hào)。


          • 任何表達(dá)式的結(jié)果類型。


          • 所有診斷(錯(cuò)誤和警告)。


          • 變量流入和流出源區(qū)域的方式。


          • 更多推理問題的答案。


          4、工作區(qū):工作區(qū)是對(duì)整個(gè)解決方案執(zhí)行代碼分析和重構(gòu)的起點(diǎn)。相關(guān)的API可以實(shí)現(xiàn):


          將解決方案中項(xiàng)目的全部相關(guān)信息組織為單個(gè)對(duì)象模型,可讓用戶直接訪問編譯器層對(duì)象模型(如源文本、語(yǔ)法樹、語(yǔ)義模型和編譯),而無(wú)需分析文件、配置選項(xiàng),或管理項(xiàng)目?jī)?nèi)依賴項(xiàng)。



          了解了Roslyn的大致情況之后,我們開始基于Roslyn做一些“不符合編程規(guī)范要求(團(tuán)隊(duì)自定義的)”的代碼分析。


          二、基于Roslyn進(jìn)行代碼分析


          接下來講通過Show case的方法,通過實(shí)際的場(chǎng)景和大家分享。在我們編寫實(shí)際的代碼分析器之前,我們先把開發(fā)環(huán)境準(zhǔn)備好  :


          使用VS2017創(chuàng)建一個(gè)Analyzer with Code Fix工程


          因?yàn)槲冶緳C(jī)的VS2019找了好久沒找到對(duì)應(yīng)的工程,這個(gè)章節(jié),使用VS2017吧



          創(chuàng)建完成會(huì)有兩個(gè)工程:


           


          其中,TeldCodeAnalyzer.Vsix工程,主要用以生成VSIX擴(kuò)展文件


          TeldCodeAnalyzer工程,主要用于編寫代碼分析器。


           工程轉(zhuǎn)換好之后,我們開始編碼吧。


          1、catch 吞掉異常場(chǎng)景


          問題:catch吞掉異常后,線上很難排查問題,同時(shí)確定哪塊代碼有問題


          示例代碼:


          try
          {
          var logService = HSFService.Proxy<ILogService>();
          logService.SendMsg(new SysActionLog());
          }
          catch (Exception ex)
          {
          }


          需求:當(dāng)開發(fā)人員在catch吞掉異常時(shí),給與編程提示:異常吞掉時(shí)必須上報(bào)監(jiān)控或者日志


          明確了上述需要,我們開始編寫Roslyn代碼分析器。ExceptionCatchWithMonitorAnalyzer



          我們?cè)敿?xì)解讀一下:


          ① ExceptionCatchWithMonitorAnalyzer必須繼承抽象類DiagnosticAnalyzer


          ② 重寫方法SupportedDiagnostics,注冊(cè)代碼掃描規(guī)則:DiagnosticDescriptor    


          internal static DiagnosticDescriptor Rule = new DiagnosticDescriptor(DiagnosticId, Title, MessageFormat, Category, DiagnosticSeverity.Warning, isEnabledByDefault: true, description: Description);
          public override ImmutableArray<DiagnosticDescriptor> SupportedDiagnostics => ImmutableArray.Create(Rule);


          ③ 重寫方法Initialize,注冊(cè)Microsoft.CodeAnalysis.SyntaxNode完成Catch語(yǔ)句的語(yǔ)義分析后的事件Action

          public override void Initialize(AnalysisContext context)
          {

          context.ConfigureGeneratedCodeAnalysis(GeneratedCodeAnalysisFlags.Analyze | GeneratedCodeAnalysisFlags.None);

          context.EnableConcurrentExecution();
          context.RegisterSyntaxNodeAction(AnalyzeDeclaration,
          SyntaxKind.CatchClause);

          }


          ④ 實(shí)現(xiàn)語(yǔ)法分析AnalyzeDeclaration,檢查對(duì)catch語(yǔ)句中代碼實(shí)現(xiàn)   


          private 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()));
          }
          }


          代碼實(shí)現(xiàn)后的效果(直接調(diào)試VSIX工程即可)



          代碼編譯后也有對(duì)應(yīng)Warnning提示


          2、在For循環(huán)中進(jìn)行服務(wù)調(diào)用


          問題:for循環(huán)中調(diào)用RPC服務(wù),每次訪問都會(huì)發(fā)起一次RPC請(qǐng)求,如果循環(huán)次數(shù)太多,性能很差,建議使用批量處理的RPC方法


          示例代碼:


          foreach (var item in items)
          {
          var logService = HSFService.Proxy<ILogService>();
          logService.SendMsg(new SysActionLog());
          }


          需求:當(dāng)開發(fā)人員在For循環(huán)中調(diào)用HSF服務(wù)時(shí),給與編程提示:不建議在循環(huán)中調(diào)用HSF服務(wù), 建議調(diào)用批量處理方法.


          明確了上述需要,我們開始編寫Roslyn代碼分析器。HSFForLoopAnalyzer  


          [DiagnosticAnalyzer(LanguageNames.CSharp)]
          public sealed class HSFForLoopAnalyzer : DiagnosticAnalyzer
          {
          public const string DiagnosticId = "TA001";
          internal const string Title = "增加循環(huán)中HSF服務(wù)調(diào)用檢查";
          public const string MessageFormat = "不建議在循環(huán)中調(diào)用HSF服務(wù), 建議調(diào)用批量處理方法.";
          internal const string Category = "CodeSmell";
          internal static DiagnosticDescriptor Rule = new DiagnosticDescriptor(DiagnosticId, Title, MessageFormat, Category,
          DiagnosticSeverity.Warning, isEnabledByDefault: true);
          public override ImmutableArray<DiagnosticDescriptor> 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;
          }
          }
          }
          }
          }
          }
          }


          基本的實(shí)現(xiàn)方式,和上一個(gè)差不多,唯一不同的邏輯是在實(shí)際的代碼分析過程中,AnalyzeMethodForLoop。大家可以根據(jù)自己的需要寫一下。


          實(shí)際的效果:



          還有幾個(gè)代碼檢查場(chǎng)景,基本都是同樣的實(shí)現(xiàn)思路,再次不一一羅列了。


          在這里還可以自動(dòng)完成代理修補(bǔ)程序,這個(gè)地方我們還在研究中,可能每個(gè)業(yè)務(wù)代碼的場(chǎng)景不同,很難給出一個(gè)通用的改進(jìn)代碼,所以這個(gè)地方等后續(xù)我們完成后,再和大家分享。


          三、通過Roslyn實(shí)現(xiàn)靜態(tài)代碼掃描


          線上很多代碼已經(jīng)寫完了,發(fā)布上線了,對(duì)已有的代碼進(jìn)行代碼掃描也是非常重要的。因此,我們對(duì)catch吞掉異常的代碼進(jìn)行了一次集中掃描和改進(jìn)。


          那么基于Roslyn如何實(shí)現(xiàn)靜態(tài)代碼掃描呢?主要的步驟有:


          ① 創(chuàng)建一個(gè)編譯工作區(qū)MSBuildWorkspace.Create()


          ② 打開解決方案文件OpenSolutionAsync(slnPath);  


          ③ 遍歷Project中的Document


          ④ 拿到代碼語(yǔ)法樹、找到Catch語(yǔ)句CatchClauseSyntax


          ⑤ 判斷是否有throw語(yǔ)句,如果沒有,收集數(shù)據(jù)進(jìn)行通知改進(jìn)


          看一下具體代碼實(shí)現(xiàn):


          先看一下Nuget引用:


          Microsoft.CodeAnalysis

          Microsoft.CodeAnalysis.Workspaces.MSBuild



          代碼的具體實(shí)現(xiàn):



          public async Task<List<CodeCheckResult>> CheckSln(string slnPath)
          {
          var slnFile = new FileInfo(slnPath);
          var results = new List<CodeCheckResult>();
          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().OfType<ThrowStatementSyntax>().Count() == 0)
          {
          results.Add(new CodeCheckResult()
          {
          Sln = slnFile.Name,
          ProjectName = project.Name,
          ClassName = programDeclaration.Identifier.Text,
          MethodName = methodDeclaration.Identifier.Text,
          });
          }
          }
          }
          }
          }
          }
          }
          }
          return results;
          }


          以上是通過Roslyn代碼分析全面提升代碼質(zhì)量的一些具體實(shí)踐,分享給大家。

          瀏覽 45
          點(diǎn)贊
          評(píng)論
          收藏
          分享

          手機(jī)掃一掃分享

          分享
          舉報(bào)
          評(píng)論
          圖片
          表情
          推薦
          點(diǎn)贊
          評(píng)論
          收藏
          分享

          手機(jī)掃一掃分享

          分享
          舉報(bào)
          <kbd id="afajh"><form id="afajh"></form></kbd>
          <strong id="afajh"><dl id="afajh"></dl></strong>
            <del id="afajh"><form id="afajh"></form></del>
                1. <th id="afajh"><progress id="afajh"></progress></th>
                  <b id="afajh"><abbr id="afajh"></abbr></b>
                  <th id="afajh"><progress id="afajh"></progress></th>
                  www.五月婷婷 | 一区二区三区四区免费观看 | 亚洲欧美性爱在线视频 | 一级肏逼网 | 国产免费高清AV影视 |