<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ì)量?

          共 10203字,需瀏覽 21分鐘

           ·

          2021-09-10 10:13


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

          前言


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


          如何確保提交代碼的質(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ì)量,覆蓋率和通過率達不到標(biāo)準(zhǔn),無法提交測試。


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


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


          一、什么是Roslyn


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


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


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


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



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


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


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


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


          Roslyn是如何做到代碼分析的呢?這背后依賴于一套強大的語法分析和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/)


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


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


          • 語法節(jié)點:是語法樹的一個主要元素。這些節(jié)點表示聲明、語句、子句和表達式等語法構(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)容:   


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


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


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


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


          • 更多推理問題的答案。


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


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



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


          二、基于Roslyn進行代碼分析


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


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


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



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


           


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


          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



          我們詳細解讀一下:


          ① 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)中進行服務(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)思路,再次不一一羅列了。


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


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


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


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


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


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


          ③ 遍歷Project中的Document


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


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


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

          回復(fù) 【關(guān)閉】學(xué)關(guān)
          回復(fù) 【實戰(zhàn)】獲取20套實戰(zhàn)源碼
          回復(fù) 【被刪】學(xué)
          回復(fù) 【訪客】學(xué)
          回復(fù) 【小程序】學(xué)獲取15套【入門+實戰(zhàn)+賺錢】小程序源碼
          回復(fù) 【python】學(xué)微獲取全套0基礎(chǔ)Python知識手冊
          回復(fù) 【2019】獲取2019 .NET 開發(fā)者峰會資料PPT
          回復(fù) 【加群】加入dotnet微信交流群


          微信8.0大更新,附最新內(nèi)測版下載地址!


          有人靠"搶茅臺"月入百萬,腳本曝光,開源可用!



          瀏覽 64
          點贊
          評論
          收藏
          分享

          手機掃一掃分享

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

          手機掃一掃分享

          分享
          舉報
          <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>
                  中文字幕欧美色图 | 看看欧美黑人操逼免费的 | 日本乱伦中文字幕 | AAA黄色片 | 国产99久久99热 |