<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>

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

          共 9947字,需瀏覽 20分鐘

           ·

          2021-03-03 13:49


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

          前言


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


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


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


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


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


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


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


          一、什么是Roslyn


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


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


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


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



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


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


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


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


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



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


          Compiler API:編譯器API,這里提供了Syntax Tree API代碼語法樹API,Symbol API代碼符號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/)


          這里我們詳細(xì)看一下語法樹、符號、語義模型、工作區(qū):


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


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


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


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


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


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


          • 錯誤:表示源文本中包含的語法錯誤。


          看一張語法樹的圖:



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


          3、語義模型:語義模型表示單個源文件的所有語義信息。可使用語義模型查找到以下內(nèi)容:   


          • 在源中特定位置引用的符號。


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


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


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


          • 更多推理問題的答案。


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


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



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


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


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


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


          因為我本機(jī)的VS2019找了好久沒找到對應(yīng)的工程,這個章節(jié),使用VS2017吧



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


           


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


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


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


          1、catch 吞掉異常場景


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


          示例代碼:


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


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


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



          我們詳細(xì)解讀一下:


          ① ExceptionCatchWithMonitorAnalyzer必須繼承抽象類DiagnosticAnalyzer


          ② 重寫方法SupportedDiagnostics,注冊代碼掃描規(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,注冊Microsoft.CodeAnalysis.SyntaxNode完成Catch語句的語義分析后的事件Action

          public override void Initialize(AnalysisContext context)
          {

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

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

          }


          ④ 實現(xiàn)語法分析AnalyzeDeclaration,檢查對catch語句中代碼實現(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()));
          }
          }


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



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


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


          問題:for循環(huán)中調(diào)用RPC服務(wù),每次訪問都會發(fā)起一次RPC請求,如果循環(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ù)時,給與編程提示:不建議在循環(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;
          }
          }
          }
          }
          }
          }
          }


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


          實際的效果:



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


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


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


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


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


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


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


          ③ 遍歷Project中的Document


          ④ 拿到代碼語法樹、找到Catch語句CatchClauseSyntax


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


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


          先看一下Nuget引用:


          Microsoft.CodeAnalysis

          Microsoft.CodeAnalysis.Workspaces.MSBuild



          代碼的具體實現(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ì)量的一些具體實踐,分享給大家。


          好文章,我在看??

          瀏覽 50
          點贊
          評論
          收藏
          分享

          手機(jī)掃一掃分享

          分享
          舉報
          評論
          圖片
          表情
          推薦
          點贊
          評論
          收藏
          分享

          手機(jī)掃一掃分享

          分享
          舉報
          <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>
                  久久精品欧美一 | 搞黄视频网站无码动漫 | 丰满人妻一区二区三区性色 | 中文天堂在线视频 | 超碰在线2|