Dieser Artikel wurde maschinell übersetzt.

Programmiererpraxis

Aufstieg der Roslyn, Teil 2: Schreiben-Diagnose

Ted Neward
Joe Hummel, Ph.D

Ted NewardJetzt haben Leser gehört ein Großteil der Buzz rund um die Strategien, die Microsoft für die nächste Generation der Microsoft-Entwickler-Tools zu verfolgen scheint: mehr open Source, mehr Cross-Plattform, mehr Offenheit und mehr Transparenz. "Roslyn" – der Codename für das .NET Compiler Plattform-Projekt — bildet einen großen Teil dieser Geschichte, wird das erste Mal, dass Microsoft hat wirklich verpflichtet Produktionsqualität Compiler-Tool-Infrastruktur ein offenes Entwicklungsmodell. Mit der Ankündigung, dass Roslyn jetzt der Compiler anhand der Microsoft .NET Framework Teams selbst baut .net, Roslyn erzielt ein gewisses Maß an "Gründung": Die Plattform und ihre Sprachwerkzeuge werden jetzt durch die Plattform und die Sprachtools gebaut. Und, wie Sie in diesem Artikel sehen, können Sie die Sprachtools weitere Sprache-Buildtools damit Sie für die Plattform erstellen können.

Verwirrt? Don' t sein — es werden alle Sinn nur ein bisschen.

"Aber wir Don' t tun"

Seit dem Start des ersten Programmierers arbeiten mit dem zweiten Programmierer – und fand ihn "machen es falsch," zumindest der erste programmer's Meinung nach — Mannschaften haben gekämpft, um einen Anschein der Einheit erstellen und Konsistenz in der Weise Code geschrieben, des Grades der Fehlerüberprüfung getan, die Art und Weise, in der Objekte und so weiter verwendet werden. Dies wurde in der Vergangenheit der Provinz "coding-Standards," im Wesentlichen eine Reihe von Regeln, die jeder Programmierer sollen folgen, beim Schreiben von Code für das Unternehmen. Manchmal gehen Programmierer sogar so weit, sie zu lesen. Aber ohne jede Art von kohärenten und einheitliche Durchsetzung — in der Regel durch das altehrwürdige Praxis "Code Review", in dem jeder­Körper Bickers über wo die geschweiften Klammern gehen sollte und was die Variablen benannt werden sollte — coding-Standards am Ende wirklich mit wenig Auswirkungen insgesamt auf Codequalität.

Im Laufe der Zeit als Sprachtools reifer, bekam suchte Entwickler Tools selbst um diese Durchsetzung gewährleisten. Schließlich ist eine Sache, die ein Computer gut ist, führt es immer wieder die gleichen Arten von detaillierten Analyse, immer und immer wieder, ohne zu zögern, Fehler oder Fehler. Denken Sie daran, das gehört zum Job eines Compilers ist in erster Linie: Entdecken Sie gemeinsame menschliche Fehler, die dazu führen, dass fehlerbehaftete Code und früh scheitern, so dass Programmierer erforderlich sind, um sie zu beheben, bevor Endbenutzer sie sehen können. Tools, die Analysieren von Code, auf der Suche nach Fehler-Muster, werden als "Statische Analysetools" bezeichnet und können helfen, Fehler zu erkennen, lange bevor Sie auch die Unit-Tests ausführen.

Historisch in das .NET Framework war es schwer zu bauen und pflegen solche Werkzeuge. Statische Analysetools erfordern einen erheblichen Entwicklungsaufwand und müssen aktualisiert werden, als Sprachen und Bibliotheken entwickeln; für Unternehmen, die in c# und Visual Basic .NET verdoppelt sich der Aufwand. Binäre Analyse-Tools, wie FxCop, arbeiten auf der Ebene der Intermediate Language (IL), Sprache-Komplexität zu vermeiden. Am allerwenigsten, ist jedoch ein struktureller Verlust von Informationen in der Übersetzung von Quelle zu IL, dass es viel mehr Probleme zurück beziehen sich auf die Ebene wo der Programmierer arbeitet schwer — die Quelle. Binäre Analyse-Tools laufen auch nach der Kompilierung, Verhinderung von IntelliSense-wie Feedback während der Programmplanung.

Roslyn, wurde jedoch von Anfang an verlängert werden. Roslyn verwendet den Begriff "Analyzer" Quellcode-Analyse-Erweiterungen zu beschreiben, die können — und — im Hintergrund während Devel­Opers programmieren. Erstellen Sie einen Analysator, können Sie Roslyn zusätzliche, höherer Ordnung Arten von "Regeln" helfen, um Fehler zu beseitigen, ohne zusätzliche Tools ausführen erzwingen bitten.

Was könnte schief gehen?

Es ist ein Tag traurig, traurig, das zuzugeben, aber wir sehen regelmäßig Code wie folgt:

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
}

Oft wird die TODO mit den besten Absichten geschrieben. Aber, wie das alte Sprichwort sagt, der Weg ins Verderben ist mit guten Vorsätzen gepflastert. Der Codierungsstandard sagt natürlich, das ist schlecht, aber es ist nur eine Verletzung, wenn jemand dich erwischt. Ein Text-Datei-Scan würde das "TODO" zeigen, aber der Code ist übersät mit TODOs, von denen keine Störungen so hässlich wie das versteckt sind. Und, natürlich, nur finden Sie diese Codezeile nach einer großen Demo-Bomben im Hintergrund und Sie langsam, schmerzhaft zurückverfolgen der Verwüstung bis Sie feststellen, dass dieser Code, die lautstark mit einer Ausnahme nicht haben sollte, stattdessen einfach es schluckte und das Programm erlaubt weiterhin in völliger Unkenntnis der seiner drohenden Untergang.

Der Codierungsstandard wahrscheinlich hat Argumente dafür: Immer die Ausnahme, oder melden Sie immer die Ausnahme auf eine standard-Diagnose-Strom oder beides, oder... aber wieder, ohne Durchsetzung, es ist nur ein Papierdokument, die niemand liest.

Mit Roslyn können Sie erstellen eine Diagnose, die diese Fänge und auch (wenn konfiguriert) arbeitet mit Visual Studio Team Foundation Server verhindert, dass dieser Code überhaupt nicht eingecheckt bis dahin leeren Catch-Block fixiert ist.

Roslyn-Diagnose

Während ich dies schreibe ist Projekt Roslyn eine Vorabversion, die als Teil des Visual Studio 2015 Preview installiert. Sobald die Vorlagen Visual Studio 2015 Vorschau SDK und Roslyn SDK installiert sind, kann die Diagnose mithilfe der bereitgestellten Vorlage der Erweiterbarkeit, Diagnose mit Code korrigieren (NuGet + VSIX) geschrieben werden. Zu starten, wie in dargestellt, Abbildung 1, wählen Sie die Diagnose Vorlage und nennen Sie das Projekt EmptyCatchDiagnostic.

Diagnose mit Code Fix (NuGet + VSIX)-Projektvorlage
Abbildung 1-Diagnose mit Code Fix (NuGet + VSIX)-Projektvorlage

Der zweite Schritt besteht darin eine Syntax-Knoten-Analyzer schreiben, in dem die Abstract Syntax Tree (AST), auf der Suche nach leeren Catch-Blöcke geführt. Ein winziges AST erscheint Abbildung 2. Die gute Nachricht ist, dass der Roslyn-Compiler die AST für Sie geht. Sie brauchen nur Code um den Knoten des Interesses zu analysieren bereitstellen. (Für diese vertraute mit klassischen "Gang-of-Four"-Entwurfsmuster ist dies das Besuchermuster am Arbeitsplatz). Ihre Analyzer von der abstrakten Basisklasse DiagnosticAnalyzer erbt und implementieren diese beiden Methoden:

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

Roslyn Abstract Syntax Tree für das Codefragment: Wenn (Score > 100) Grade = "A++";
Abbildung 2 Roslyn Abstract Syntax Tree für das Codefragment: Wenn (Score > 100) Grade = "A++";

Die SupportedDiagnostics-Methode ist eine einfache, wieder eine Beschreibung jedes Analyzer, die Sie zu Roslyn anbieten. Die Initialize-Methode ist, in dem Sie Ihren Code Analyzer mit Roslyn registrieren. Während der Initialisierung bieten Sie Roslyn mit zwei Dinge: die Art der Knoten, in der Sie interessiert; und den Code ausführen, wenn einer dieser Knoten während der Kompilierung aufgetreten ist. Da Visual Studio Kompilierung im Hintergrund durchführt, werden diese Anrufe auftreten, während der Benutzer bearbeitet wird, sofortiges Feedback zu möglichen Fehlern.

Starten Sie durch Ändern des vorgenerierten Vorlagencodes in was Sie für Ihre leeren Catch-Diagnose benötigen. Dies finden Sie in der Quellcodedatei DiagnosticAnalyzer.cs innerhalb der EmptyCatch­Diagnose Projekt (die Lösung enthält weitere Projekte, die Sie ignorieren können). Im Code, der folgt, in Fettschrift angezeigt, werden die Änderungen in Bezug auf den bereits generierten Code. Zunächst einige Zeichenfolgen, beschreibt unsere Diagnose:

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

Die generierte SupportedDiagnostics Methode ist korrekt; Sie müssen nur die Initialize-Methode, um Registrieren Sie Ihre benutzerdefinierte Syntax-Analyse-Routine, AnalyzeSyntax zu ändern:

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

Beachten Sie im Rahmen der Registrierung, dass Sie Roslyn zu informieren, dass Sie nur in Catch-Klauseln innerhalb der AST interessiert. Diese Schnitte nach unten auf die Anzahl der Knoten an Sie verfüttert und hilft auch den Analyzer sauber, einfach und Single-gedachte halten.

Wenn ein Catch-Klausel-Knoten in die AST entdeckt wird, wird während der Kompilierung Ihre Analysemethode AnalyzeSyntax aufgerufen. Dies ist, wo man sieht die Anzahl der Anweisungen im Catch-Block, und wenn diese Anzahl 0 (null) ist, Sie eine diagnostische Warnung anzeigen, weil der Block leer ist. Siehe Abbildung 3, wenn Ihre Analyzer einen leeren Catch-Block, findet eine neue Diagnose-Warnung zu erstellen, platzieren Sie es an der Stelle des Catch-Schlüsselwort und melden Sie es.

Abbildung 3 Begegnung mit einer Catch-Klausel

// 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);
  }
}

Der dritte Schritt ist erstellen und Ausführen der Diagnose. Was als nächstes passiert ist wirklich interessant und sinnvoll, wenn man darüber nachdenkt. Sie haben gerade eine Compiler-gesteuerte Diagnose erstellt — wie testen Sie es? Von Visual Studiostarten, Installation von der Diagnose, ein Projekt mit leeren Catch-Blöcke öffnen und sehen, was passiert! Dies wird dargestellt Abbildung 4. Der Standardtyp ist ein VSIX-Installer, also wenn Sie "das Projekt ausführen", Visual Studio eine weitere Instanz des Visual Studio und das Installationsprogramm für sie führt. Wenn die zweite Instanz ist, können Sie es testen. Ach, automatisiertes Testen von Diagnose ist ein bisschen im Rahmen des Projektes jetzt, aber wenn die Diagnose einfach und einzelnen ausgerichtete gehalten werden, dann ist es nicht allzu schwer testen manuell.

Visual Studio ein leeren Catch-Block-Diagnose in einer anderen Instanz von Visual Studio ausführen
Abbildung 4 Visual Studio ein leeren Catch-Block-Diagnose in einer anderen Instanz von Visual Studio ausführen

Don' t nur dort stehen, Fix It!

Leider ein Tool, das einen Fehler hingewiesen, dass es leicht beheben könnte — aber nicht — ist wirklich nur ärgerlich. Irgendwie so sah dein Cousin sah Sie kämpfen, um die Tür zu öffnen, stundenlang vor der Entscheidung zu erwähnen, dass es gesperrt ist, dann Sie einen anderen Weg in für finden bevor Sie erwähnen, dass er den Schlüssel hat noch länger zu kämpfen.

Roslyn will nicht dieser Kerl sein.

Ein Codeupdate enthält einen oder mehrere Vorschläge für den Entwickler — Vorschläge, um hoffentlich das Problem erkannt von Analyzer zu beheben. Im Falle einer leeres catch-Block, eine einfache Code-Lösung ist eine Throw-Anweisung hinzufügen, so dass jede Ausnahme fing sofort erneut ausgelöst wird. Abbildung 5 zeigt, wie das Codeupdate für den Entwickler im Visual Studio, als vertraute QuickInfo angezeigt wird.

Codefix vorschlagen einen Wurf innerhalb des leeren Catch-Block
Abbildung 5 Codefix vorschlagen einen Wurf innerhalb des leeren Catch-Block

Konzentrieren Sie in diesem Fall sich auf die anderen vorgenerierten Quelldatei im Projekt CodeFixProvider.cs. Ihre Aufgabe ist es, von der abstrakten Basisklasse CodeFixProvider erben und drei Methoden implementieren. Die wichtigste Methode ist ComputeFixesAsync, die Vorschläge für den Entwickler bietet:

public sealed override async Task ComputeFixesAsync(CodeFixContext context)

Wenn der Analyzer ein Problem meldet, wird diese Methode aufgerufen, durch das Visual Studio IDE, um festzustellen, ob alle vorgeschlagenen Code-Fixes. Wenn ja, zeigt die IDE eine QuickInfo mit den Vorschlägen, aus denen der Entwickler auswählen kann. Wenn eine ausgewählt ist, das angegebene Dokument — die kennzeichnet der AST für die Quelldatei — mit das vorgeschlagene Update aktualisiert wird.

Dies bedeutet, dass ein Codefix nichts anderes als eine vorgeschlagene Änderung an die AST ist. Durch Ändern der AST, die Änderung, die übrigen Phasen des Compilers, erfolgt durch als ob der Entwickler den Code geschrieben hatte. In diesem Fall ist der Vorschlag eine Throw-Anweisung hinzufügen. Abbildung 6 ist eine abstrakte Darstellung davon, was auf.

Aktualisierung der abstrakten Syntaxbaum
Abbildung 6 Aktualisierung der abstrakten Syntaxbaum

Also baut Ihre Methode eine neue Unterstruktur um die vorhandenen Catch Block Unterstruktur in den AST zu ersetzen. Diese neuen Teilbaum unten aufzubauen: eine neue throw Anweisung und dann eine Liste enthält die Anweisung, dann einen Block zum Zweck der Liste und schließlich einen Haken, den Block zu verankern:

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

Der nächste Schritt ist, greifen die Wurzel der AST für diese Quelldatei, finden den Catch-Block von Analyzer identifiziert und bauen eine neue AST. Z. B. kennzeichnet NewRoot eine neu bewurzelten AST für diese Quelldatei:

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.

Der letzte Schritt ist eine Code-Aktion registriert, die Ihren Fix aufrufen und aktualisieren die AST:

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

Für eine Vielzahl von guten Gründen sind die meisten Datenstrukturen in Roslyn unveränderlich, einschließlich die AST. Dies ist eine besonders gute Wahl, weil Sie nicht wollen, die AST zu aktualisieren, es sei denn, der Entwickler tatsächlich das Codeupdate auswählt. Da die vorhandene AST unveränderlich ist, gibt die Methode eine neue AST, die für die aktuellen AST vom IDE ersetzt wird, wenn das Codeupdate aktiviert ist.

Sie möglicherweise betroffenen Unveränderlichkeit an die hohen Kosten der Speicherverbrauch kommt. Wenn die AST unveränderlich ist, bedeutet das, dass eine vollständige Kopie nötig ist, jedes Mal, wenn eine Änderung vorgenommen wird? Glücklicherweise sind nur die Unterschiede in den AST gespeichert (mit der Begründung, dass es einfacher ist, speichern die Deltas als zufügen mit der Parallelität und Konsistenz Fragen, die machen die AST komplett veränderbare schaffen sollte) um den Betrag des Kopierens zu minimieren, der auftritt, um die Unveränderlichkeit zu gewährleisten.

Neuland

Roslyn beschreitet einige neue Wege durch die Öffnung der Compiler (und auch der IDE!) auf diese Weise. Jahrelang hat c# angepriesen selbst als eine "Typisierung" Sprache, was darauf hindeutet, dass vorab Kompilierung hilft Fehler zu vermeiden. Nahm in der Tat sogar ein paar Schritte zu versuchen, aus anderen Sprachen häufige Fehler vermeiden (z. B. Behandlung von Ganzzahl Vergleiche als boolesche Werte, was zu den berüchtigten "Wenn (X = 0)" Fehler, die C++-Entwickler oft verletzt). Aber Compiler mussten immer extrem wählerisch, welche Regeln sie könnte oder würde gelten, werden, weil diese Entscheidungen branchenweiten waren und verschiedene Organisationen hatten oft unterschiedliche Meinungen was "zu streng" oder "zu locker." Jetzt mit Microsoft Öffnung des Compilers Innereien für Entwickler, können Sie beginnen, die "Hausregeln" auf Code zu erzwingen, ohne zu Compiler Experten auf eigene Faust.

Schauen Sie sich die Roslyn-Projektseite bei roslyn.codeplex.com für Details zum Einstieg in Roslyn. Wenn Sie analysieren und lexikalische tiefer eintauchen möchten, sind zahlreiche Bücher zur Verfügung, darunter die ehrwürdige "Dragon Book" offiziell veröffentlicht als "Compiler: Prinzipien, Techniken & Tools "(Addison Wesley, 2006) von Aho, Lam, Sethi und Ullman. Für Interessenten an einer mehr.NET-centric nähern, sollten Sie "Kompilieren für die .NET Common Language Runtime (CLR)" (Prentice Hall, 2001) von John Gough, oder Ronald Mak's "Schreiben von Compilern und Interpeters: Ein Software-Engineering-Ansatz"(Wiley, 2009).

Glücklich Codierung!


Ted Neward ist CTO von iTrellis, ein Beratungsunternehmen Service. Er hat mehr als 100 Artikel geschrieben und ein Dutzend Bücher, darunter "professionelle f# 2.0" (Wrox, 2010). Er ist ein F#-MVP und spricht auf Konferenzen auf der ganzen Welt. Er berät und Mentoren regelmäßig — erreichen ihn bei ted@tedneward.com oder ted@itrellis.com Wenn Sie interessiert sind.

Joe Hummel, Ph.D, ist ein Research Associate Professor an der University of Illinois, Chicago, ein Urheber des Inhalts für Pluralsight, ein Visual C++-MVP und private Berater. Er besitzt einen Ph.d. bei UC Irvine im Bereich High Performance computing und interessiert alles parallel. Er lebt in der Region Chicago, und wann er Segeln ist nicht erreichbar bei joe@joehummel.net.

Unser Dank gilt dem folgenden technischen Experten von Microsoft für die Durchsicht dieses Artikels: Kevin Pilch-Bisson