此文章由机器翻译。

孜孜不倦的程序员

崛起的罗斯林,第 2 部分:写作诊断

Ted Neward
Joe Hummel

Ted Neward到目前为止,读者会听过很多的嗡嗡声围绕微软似乎微软开发工具的下一代追求的战略:更多打开源,更多的跨平台、 更开放、 更大的透明度。"罗斯林"— — 代号为.NET 编译器平台项目 — — 形成的那个故事的主要部分,是第一次,微软已真的致力生产质量编译器工具基础结构开放的开发模式。随着宣布 Roslyn 现在是 Microsoft.NET 框架团队自己用于生成.NET 编译器,罗斯林取得一定程度的"盗梦空间 》:纲要 》 和其语言工具现时正兴建由平台和其语言工具。,你会看到在这篇文章中,您可以使用语言工具打造更多的语言工具,可以帮助您构建的平台。

困惑吗?别 — — 它会全部意义只是有点。

但我们没有这么做

自从第一个程序员开始与第二个程序员的工作 — — 找到了他"做错了,"至少在第一个程序员看来 — — 团队在竭力打造一些表面上的团结和这样的代码的一致性写,程度的错误检查完成,等等使用对象的方式。从历史上看,这是该省"编码标准,"本质上是一套每个程序员应该在为公司编写代码时应遵循的规则。有时,程序员甚至读写去为止。但没有任何一种连贯和一致的执法 — — 通常是通过"代码审查"其间,历史悠久的实践每个­身体 bickers 结束大括号应该去哪里,什么变量应该被命名为 — — 编码标准最终真的对代码质量整体产生小的影响。

随着时间推移,作为语言工具得到更加成熟,开发人员开始寻找到工具本身来提供这种级别的执法。毕竟,如果有一台电脑是擅长的一件事,它重复执行相同种类的详细分析,遍又一遍,没有失败或犹豫或错误。请记住,这是一个编译器的工作的一部分放在第一位:发现常见的人类错误可以导致出错的代码,和早失败,所以程序员需要来解决这些问题之前,最终用户看到他们。工具来分析代码,寻找错误模式,被称为"静态分析工具",可以帮助识别 bug,很久之前你甚至运行单元测试。

从历史上看在.NET Framework 中,一直难以建立和维持这样的工具。静态分析工具需要大量的开发工作,并且必须更新作为语言和库进化 ; 对于 C# 和 Visual Basic.NET 中工作的公司,努力增加一倍。二进制分析工具如 FxCop,工作在中间语言 (IL) 一级,避免语言的复杂性。然而,至少,还有从源代码转换为 IL,使它更多的信息结构损失难问题涉及的水平程序员工作的地方 — — 源。二进制分析工具还编译,在编程过程中防止类似智能感知的反馈之后运行。

罗斯林,然而,建成从一开始就会延长。罗斯林使用术语"分析器"来描述可以的源代码分析扩展 — — 做 — — 在同时开发后台运行­戏院所使用的编程。通过创建分析器,你可以问 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 Studio 团队基础服务器以防止究竟此代码签入直到那空 catch 块固定的。

罗斯林诊断

在撰写本文时,项目罗斯林是预览版本,安装 Visual Studio 2015 预览的一部分。一旦安装了 Visual Studio 2015 预览 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 Studio 的 Visual Studio 的另一个实例启动并运行安装程序。一旦有那第二个实例时,可以对其进行测试。唉,自动化测试的诊断是有点超出了该项目的范围现在,但是如果诊断程序保持简单、 单一集中,然后不是很难手动测试。

Visual Studio 在 Visual Studio 的另一个实例中运行空抓块诊断
图 4 Visual Studio 在 Visual Studio 的另一个实例中运行空抓块诊断

不要只是站在那里,修复它 !

不幸的是,一个指出错误它可能很容易修复的工具 — — 但并不 — — 是真的只是令人讨厌。有点像那样你的堂兄,看着你挣扎几个小时才决定提它锁着的打开门然后看着你很难找到另一种方式在提及他有钥匙之前甚至更长时间。

罗斯林不想要那个家伙。

代码修复向开发人员提供一个或多个建议 — — 希望修复分析器检测到该问题的建议。在一个空的 catch 块,一个简单的代码修复是添加 throw 语句,以便捕获任何异常立即再次引发异常。图 5 说明了代码修复到开发人员在 Visual Studio 中,作为一个熟悉的工具提示的显示方式。

代码修复建议内空抛 Catch 块
图 5 代码修复建议内空抛 Catch 块

在本例中将重点放在项目中,CodeFixProvider.cs 的其他预生成的源代码文件。 你的工作是从抽象基类 CodeFixProvider 继承并实现三种方法。关键方法是 ComputeFixesAsync,为开发人员提供建议:

public sealed override async Task ComputeFixesAsync(CodeFixContext context)

当分析器报告的问题时,通过 Visual Studio IDE,看看是否有任何推荐的代码修补程序调用此方法。如果是这样,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年) 由约翰 · 高夫或夏麦"写编译器和 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

衷心感谢以下 Microsoft 技术专家对本文的审阅:Kevin 皮尔希森