本文章是由機器翻譯。

程式設計師雜談

崛起的羅斯林,第 2 部分:寫作診斷

Ted Neward
Joe Hummel

Ted Neward到目前為止,讀者會聽過很多的嗡嗡聲圍繞微軟似乎微軟開發工具的下一代追求的戰略:更多打開源,更多的跨平臺、 更開放、 更大的透明度。"羅斯林"— — 代號為.NET 編譯器平臺專案 — — 形成的那個故事的主要部分,是第一次,微軟已真的致力生產品質編譯器工具基礎結構開放的開發模式。隨著宣佈 Roslyn 現在是 Microsoft.NET 框架團隊自己用於生成.NET 編譯器,羅斯林取得一定程度的"盜夢空間 》:綱要 》 和其語言工具現時正興建由平臺和其語言工具。,你會看到在這篇文章中,您可以使用語言工具打造更多的語言工具,可以説明您構建的平臺。

困惑嗎?Don使 — — 它會全部意義只是有點。

' 但我們Don別,'

自從第一個程式師開始與第二個程式師的工作 — — 找到了他"做錯了,"至少在第一個程式師看來 — — 團隊在竭力打造一些表面上的團結和這樣的代碼的一致性寫,程度的錯誤檢查完成,等等使用物件的方式。從歷史上看,這是該省"編碼標準,"本質上是一套每個程式師應該在為公司編寫代碼時應遵循的規則。有時,程式師甚至讀寫去為止。但沒有任何一種連貫和一致的執法 — — 通常是通過"代碼審查"其間,歷史悠久的實踐每個­身體 bickers 結束大括弧應該去哪裡,什麼變數應該被命名為 — — 編碼標準最終真的對代碼品質整體產生小的影響。

隨著時間推移,作為語言工具得到更加成熟,開發人員開始尋找到工具本身來提供這種級別的執法。畢竟,如果有一台電腦是擅長的一件事,它重複執行相同種類的詳細分析,遍又一遍,沒有失敗或猶豫或錯誤。請記住,這是一個編譯器的工作的一部分放在第一位:發現常見的人類錯誤可以導致出錯的代碼,和早失敗,所以程式師需要來解決這些問題之前,最終使用者看到他們。工具來分析代碼,尋找錯誤模式,被稱為"靜態分析工具",可以説明識別 bug,很久之前你甚至運行單元測試。

從歷史上看在.NET Framework 中,一直難以建立和維持這樣的工具。靜態分析工具需要大量的開發工作,並且必須更新作為語言和庫進化 ; 在 C# 和Visual Basic.NET 工作的公司,努力增加一倍。二進位分析工具如 FxCop,工作在中間語言 (IL) 一級,避免語言的複雜性。然而,至少,還有從原始程式碼轉換為 IL,使它更多的資訊結構損失難問題涉及的水準程式師工作的地方 — — 源。二進位分析工具也運行經過編譯,防止IntelliSense-喜歡在程式設計過程中的回饋。

羅斯林,然而,建成從一開始就會延長。羅斯林使用術語"分析器"來描述可以的原始程式碼分析擴展 — — 做 — — 在同時開發後臺運行­戲院所使用的程式設計。通過創建分析器,你可以問 Roslyn 強制執行附加的高階各種各樣的"規則",説明消除 bug,而不必運行額外的工具。

什麼錯呢?

這是悲哀的悲哀的一天,要承認這一點,但我們定期看到這樣的代碼:

try
{
  int x = 5; int y = 0;
  // Lots of code here
  int z = x / y;
}
catch (Exception ex)
{
  // TODO: come back and figure out what to do here
}

通常情況下,該 TODO 是用最好的意圖寫的。但是,正如古語有雲,由善意鋪滅亡之路。當然,該編碼標準說,這是壞的但它是不僅違反了,如果有人抓住你。當然,文字檔中掃描將顯示"TODO",但代碼充斥著托,其中沒有一個隱藏醜得跟這一樣的錯誤。當然,只有,你會默默地後主要演示炸彈中找到下面這行代碼和你慢慢地,痛苦地回溯破壞直到你發現此代碼中,應該大聲已經失敗,出現異常,相反只是把它吞了,允許程式繼續在幸福的無知的其即將到來的厄運。

可能的編碼標準這方面的一個案例:總是引發異常,或總是至標準的診斷流或兩者兼而有之,記錄異常,或...... 但是,再一次,如果沒有執法、 它是只是沒人去讀的紙質文檔。

與羅斯林,您可以生成捕捉這診斷和甚至 (配置時這樣做) 作品與Visual StudioTeam Foundation伺服器以防止究竟此代碼簽入直到那空 catch 塊固定的。

羅斯林診斷

在撰寫本文時,專案羅斯林是預覽版,作為Visual Studio2015年預覽的一部分安裝。一旦安裝的Visual Studio2015年預覽 SDK 和羅斯林 SDK 的範本,則可以使用所提供的可擴充性範本,代碼修復 (NuGet + VSIX) 診斷編寫診斷。若要開始,如中所示圖 1、 選擇診斷範本和命名專案 EmptyCatchDiagnostic。

診斷與代碼修復 (NuGet + VSIX) 專案範本
圖 1 診斷與代碼修復 (NuGet + VSIX) 專案範本

第二步是編寫一個走抽象語法樹 (AST),尋找空的 catch 塊的語法節點分析儀。一個小小的 AST 片段所示圖 2。好消息是羅斯林編譯器為你走 AST。你只需要提供代碼來分析感興趣的節點。(對於那些熟悉經典"崗四個"設計模式,這是訪問者模式工作。您的分析器必須繼承自抽象基類 DiagnosticAnalyzer 和實施這兩種方法:

public abstract
  ImmutableArray<DiagnosticDescriptor> SupportedDiagnostics { get; }
public abstract void Initialize(AnalysisContext context);

Roslyn 抽象語法樹的代碼片段:如果 (得分>100) 級 ="A + +";
圖 2 Roslyn 抽象語法樹的代碼片段:如果 (得分>100) 級 ="A + +";

SupportedDiagnostics 方法是簡單的一個,返回一個描述每個分析儀,你提供到英國羅斯林。初始化方法是,您註冊您的分析器代碼與羅斯林。在初始化過程中,您向羅斯林提供兩件事: 那種節點,你有興趣 ; 和這些節點中的一個在編譯期間遇到時要執行的代碼。因為Visual Studio在後臺執行編譯,這些調用將會時發生使用者正在編輯,對可能出現的錯誤提供即時回饋。

通過修改成你所需要的為你空抓診斷的預生成的範本代碼開始。這可在原始程式碼檔中的 EmptyCatch DiagnosticAnalyzer.cs­(該解決方案將包含更多的專案,你可以放心地忽略) 的診斷專案。在後面的代碼中,您看到以黑體字是關於預生成的代碼的變化。第一,描述我們診斷某些字串:

internal const string Title = "Catch Block is Empty";
internal const string MessageFormat =  
  "'{0}' is empty, app could be unknowingly missing exceptions";
internal const string Category = "Safety";

生成的 SupportedDiagnostics 方法是正確的 ; 你只需要改變來註冊您的自訂編寫語法分析常式,AnalyzeSyntax 的初始化方法:

public override void Initialize(AnalysisContext context)
{
  context.RegisterSyntaxNodeAction<SyntaxKind>(
    AnalyzeSyntax, SyntaxKind.CatchClause);
}

作為註冊過程的一部分,請注意你告知 Roslyn 你只是感興趣的 catch 子句內 AST。這樣減少了節點的數目喂給你們,也有助於保持潔淨的、 簡單的、 單一用途的分析儀。

在編譯期間中 AST,, 遇到一個 catch 子句節點時,將調用您的分析方法 AnalyzeSyntax。這是在那裡你看看在 catch 塊中的語句的數量,如果這一數位為零,則顯示一個診斷的警告,因為塊是空的。如中所示圖 3,當您的分析器發現空 catch 塊中,您創建一個新的診斷警告、 位置 catch 關鍵字的位置和報告它。

圖 3 遇到 Catch 子句

// Called when Roslyn encounters a catch clause.
private static void AnalyzeSyntax(SyntaxNodeAnalysisContext context)
{
  // Type cast to what we know.
  var catchBlock = context.Node as CatchClauseSyntax;
  // If catch is present we must have a block, so check if block empty?
  if (catchBlock?.Block.Statements.Count == 0)
  {
    // Block is empty, create and report diagnostic warning.
    var diagnostic = Diagnostic.Create(Rule,
      catchBlock.CatchKeyword.GetLocation(), "Catch block");
    context.ReportDiagnostic(diagnostic);
  }
}

第三步是生成並運行診斷程式。下一步會發生什麼真的很有趣,和一旦你想想看,有一定的道理。你只是建一個編譯器驅動的診斷 — — 你所以如何測試它呢?通過在開始了Visual Studio,安裝的診斷,與空 catch 塊打開專案並查看發生了什麼 !這描繪在圖 4。預設的專案類型是一個 VSIX 的安裝程式,所以當你"跑"專案,Visual StudioVisual Studio的另一個實例啟動並運行安裝程式。一旦有那第二個實例時,可以對其進行測試。唉,自動化測試的診斷是有點超出了該專案的範圍現在,但是如果診斷程式保持簡單、 單一集中,然後不是很難手動測試。

Visual Studio的Visual Studio的另一個實例中運行空抓塊診斷
圖 4 Visual Studio的Visual Studio的另一個實例中運行空抓塊診斷

Don不只是站在那兒,修復它 !

不幸的是,一個指出錯誤它可能很容易修復的工具 — — 但並不 — — 是真的只是令人討厭。有點像那樣你的堂兄,看著你掙扎幾個小時才決定提它鎖著的打開門然後看著你很難找到另一種方式在提及他有鑰匙之前甚至更長時間。

羅斯林不想要那個傢伙。

代碼修復向開發人員提供一個或多個建議 — — 希望修復分析器檢測到該問題的建議。在一個空的 catch 塊,一個簡單的代碼修復是添加 throw 語句,以便捕獲任何異常立即再次引發異常。圖 5 說明了代碼修復到Visual Studio,開發人員熟悉的工具提示的顯示方式。

代碼修復建議內空拋 Catch 塊
圖 5 代碼修復建議內空拋 Catch 塊

在本例中將重點放在專案中,CodeFixProvider.cs 的其他預生成的原始程式碼檔。 你的工作是從抽象基類 CodeFixProvider 繼承並實現三種方法。關鍵方法是 ComputeFixesAsync,為開發人員提供建議:

public sealed override async Task ComputeFixesAsync(CodeFixContext context)

當分析器報告問題時,將由Visual StudioIDE,看看是否有任何推薦的代碼修補程式調用此方法。如果是這樣,IDE 將顯示工具提示,包含開發人員可從中選擇的建議。如果選中其中一個,則給定的文檔 — — 表示原始檔案的 AST — — 更新帶有建議修復方法。

這意味著代碼修復是 AST 提議修改而已。通過修改 AST,變動被執行對編譯器,餘下的各期,如果開發人員寫了該代碼。在這種情況下,這項建議是添加 throw 語句。圖 6 是抽象描述的事情上。

更新的抽象語法樹
圖 6 更新的抽象語法樹

所以你的方法生成一個新的子樹,以取代現有的 catch 塊子樹在 AST 中。建立這個新的子樹底部: 新拋出的語句,然後要包含語句,然後一個塊範圍清單中,並最終抓住錨固塊的清單:

public sealed override async Task ComputeFixesAsync(
  CodeFixContext context)
{
  // Create a new block with a list that contains a throw statement.
  var throwStmt = SyntaxFactory.ThrowStatement();
  var stmtList = new SyntaxList<StatementSyntax>().Add(throwStmt);
  var newBlock = SyntaxFactory.Block().WithStatements(stmtList);
  // Create a new, replacement catch block with our throw statement.
  var newCatchBlock = SyntaxFactory.CatchClause().WithBlock(newBlock).
    WithAdditionalAnnotations(
    Microsoft.CodeAnalysis.Formatting.Formatter.Annotation);

下一步是抓住此原始檔案 AST 的根源,找到確定由分析器的 catch 塊,建立新的 AST。例如,橋表示新根的 AST 為此原始檔案:

var root = await context.Document.GetSyntaxRootAsync(
    context.CancellationToken).ConfigureAwait(false);
  var diagnostic = context.Diagnostics.First();
  var diagnosticSpan = diagnostic.Location.SourceSpan;
  var token = root.FindToken(diagnosticSpan.Start); // This is catch keyword.
  var catchBlock = token.Parent as CatchClauseSyntax; // This is catch block.
  var newRoot = root.ReplaceNode(catchBlock, newCatchBlock); // Create new AST.

最後一步是註冊將調用您的修補程式和更新 AST 的代碼操作:

var codeAction =
    CodeAction.Create("throw", context.Document.WithSyntaxRoot(newRoot));
  context.RegisterFix(codeAction, diagnostic);
}

各種好的理由,大多數的資料結構,在羅斯林是不可變的包括 AST。這是一個特別好的選擇,因為你不想更新 AST,除非開發人員實際上選擇代碼修復。由於現有的 AST 是不可變的該方法將返回新的 AST,代替當前的 AST 由 IDE 如果選中了該代碼修補程式。

你可能會擔心不變性代價記憶體消耗高。如果 AST 是不可變的這是否意味著每次進行更改時需要一個完整的副本嗎?幸運的是,唯一的差異都存儲在 AST (,理由是它易於儲存比要處理的併發性和一致性的問題,製作完全可變的 AST 將創建增量) 儘量將大量複製發生,確保不變性。

新的突破

羅斯林通過開放編譯器 (和 IDE 中,以及 !) 這樣一些新的突破。多年來,C# 具有標榜自己是"強型別"的語言,建議預先編譯説明減少錯誤。實際上,C# 甚至走了幾步,試圖避免常見的錯誤,從其他語言 (例如導致臭名昭著的布林值被視為整數比較"如果 (x = 0)"往往會傷害 c + + 開發人員的 bug)。編譯器總是不得不要極具選擇性有關他們可能或將適用什麼規則,因為這些決定了全行業,而不同的組織通常有不同的看法,什麼是"太嚴格"或"太寬鬆"。現在,微軟開放編譯器的內臟給開發人員,您可以開始執行"家規"的代碼,而無需成為你自己的編譯器專家。

查閱在羅斯林專案頁面 roslyn.codeplex.com 如何入門 Roslyn 有關的詳細資訊。如果你想要更深入解析和詞法分析,很多書都可用,包括作為正式出版的令人尊敬"龍書""編譯器:原則、 技術&工具"(艾迪生 Wesley,2006年) 由霍、 林、 Sethi 和厄爾曼。對於那些感興趣的更多。以網路為中心方法,考慮"編譯的.NET 公共語言運行時 (CLR)"(普倫蒂斯霍爾,2001年) 由John高夫或夏麥"寫編譯器和 Interpeters:一種軟體工程方法"(威利,2009 年)。

您編碼愉快 !


Ted Neward 是 iTrellis,一家諮詢服務公司的 CTO。他已寫超過 100 篇文章,寫了十幾本書,包括"專業 F # 2.0" (Wrox,2010年)。他是 F # MVP,在世界各地的會議上講話。他提供諮詢和指導定期 — — 達到他在 ted@tedneward.comted@itrellis.com 如果你有興趣。

Joe Hummel,博士, 是在芝加哥,伊利諾伊大學的內容建立者 Pluralsight、 Visual c + + MVP,和一名私人顧問研究副教授。他獲得博士學位 加州大學歐文分校的高性能計算領域是平行的所有東西感興趣。他居住在芝加哥地區,和當他並不帆船可以到達在 joe@joehummel.net

感謝以下的微軟技術專家對本文的審閱:Kevin皮爾希森