Il presente articolo è stato tradotto automaticamente.

Il programmatore al lavoro

Aumento di Roslyn, parte 2: Diagnostica di scrittura

Ted Neward
Joe Hummel

Ted NewardOrmai, i lettori avranno sentito molto il ronzio che circonda le strategie che Microsoft sembra perseguire per la prossima generazione di strumenti di sviluppo Microsoft: più open source, multipiattaforma più, più apertura e più trasparenza. "Roslyn" — il nome di codice per il progetto piattaforma compilatore .NET — costituisce una parte importante di quella storia, essendo la prima volta che Microsoft ha davvero commesso infrastruttura strumento compilatore di alta qualità per un modello di sviluppo aperto. Con l'annuncio che Roslyn è ora il compilatore utilizzato dai team Microsoft .NET Framework stessi costruire .NET, Roslyn ha raggiunto un certo grado di "inception": La piattaforma e i suoi strumenti di lingua sono ora in costruzione dalla piattaforma ed i suoi strumenti di linguaggio. E, come si vedrà in questo articolo, è possibile utilizzare gli strumenti del linguaggio per costruire ulteriori strumenti di linguaggio per aiutarvi a costruire per la piattaforma.

Confuso? Don' t essere — tutto farò senso appena un po'.

' Ma noi Don' t farlo '

Poiché la prima programmatrice ha iniziato a lavorare con il programmatore secondo — e lo trovò "facendo male," almeno nel parere del programmatore prima — squadre hanno lottato per creare una parvenza di unità e coerenza nel codice della strada è scritto, il grado di controllo errori fatto, il modo in cui gli oggetti vengono utilizzati e così via. Storicamente, questa è stata la provincia di "standard di codifica," essenzialmente un insieme di regole che ogni programmatore dovrebbe per seguire quando si scrive codice per l'azienda. A volte, i programmatori anche arriverei leggerli. Ma senza alcun tipo di applicazione coerente e costante — solitamente attraverso quella antica pratica di "revisione del codice" durante il quale ogni­corpo bickers sopra dove le parentesi graffe devono andare e cosa devono essere denominate le variabili — standard di codifica davvero finiscono per avere poco impatto complessivo sulla qualità del codice.

Nel corso del tempo, come strumenti per le lingue ha ottenuto più maturi, gli sviluppatori ha iniziato alla ricerca di strumenti stessi per fornire questo livello di imposizione. Dopo tutto, se c'è una cosa che un computer è bravo, ripetutamente esegue lo stesso tipo di analisi dettagliata, più e più volte, senza fallire o esitazione o errore. Ricordo, che è parte del lavoro di un compilatore in primo luogo: Scoprire gli errori umani comuni che possono portare a codice soggetta e presto fallire così i programmatori sono tenuti a risolverli prima che gli utenti finali a vederli. Strumenti che analizzare il codice, alla ricerca di modelli di errore, vengono chiamati "strumenti di analisi statica" e possono aiutare a identificare il bug a lungo prima di eseguire anche gli unit test.

Storicamente in .NET Framework, è stato difficile costruire e mantenere tali strumenti. Strumenti di analisi statica richiedono uno sforzo significativo sviluppo e devono essere aggiornati come linguaggi e librerie evolvono; per le aziende che lavorano in c# e Visual Basic .NET, raddoppia lo sforzo. Strumenti di analisi binario, come FxCop, lavorano a livello Intermediate Language (IL), evitando la complessità di linguaggio. Tuttavia, per lo meno, c'è una perdita strutturale di informazioni nella traduzione dall'origine per IL, rendendolo che molto più difficile da correlare problemi al livello dove il funzionamento — la fonte. Strumenti di analisi binario anche eseguire dopo la compilazione, evitando IntelliSense-come feedback durante il processo di programmazione.

Roslyn, tuttavia, è stato costruito fin dall'inizio per essere esteso. Roslyn utilizza il termine "analizzatore" per descrivere le estensioni di analisi del codice sorgente che possono — e — eseguito in background mentre devel­pers la programmazione. Creando un analizzatore, potete chiedere Roslyn applicare tipi aggiuntivi, di ordine superiore di "regole", aiutando ad per eliminare i bug senza dover eseguire ulteriori strumenti.

Cosa potrebbe andare storto?

È un giorno triste, triste ammetterlo, ma periodicamente vediamo il codice come questo:

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
}

Spesso, quel TODO è scritto con le migliori intenzioni. Ma, come dice il proverbio, la strada alla perdizione è lastricata di buone intenzioni. Naturalmente, lo standard di codifica dice questo è male, ma è solo una violazione se qualcuno ti cattura. Certo, una scansione di file di testo avrebbe rivelato il "TODO", ma il codice è disseminato di TODOs, nessuno dei quali si nascondono errori come brutti come questo. E, naturalmente, si trovano solo questa riga di codice dopo un bombe principali demo in silenzio e tu lentamente, dolorosamente backtrack la devastazione fino a trovare che questo codice, che dovrebbe aver fallito rumorosamente con un'eccezione, invece semplicemente inghiottito esso e ha permesso al programma di proseguire nella beata ignoranza del suo destino incombente.

Lo standard di codifica probabilmente ha una cassa per questo: Buttare sempre l'eccezione, o registrare sempre l'eccezione ad un flusso standard diagnostico o entrambi, o... ma, ancora una volta, senza l'applicazione, è solo un documento di carta che nessuno legge.

Con Roslyn, è possibile costruire una diagnostica che rileva questo e anche (se configurato per farlo) funziona con Visual Studio Team Foundation Server per impedire questo codice dal mai essere controllato fino a che il blocco catch vuoto è fisso.

Roslyn diagnostica

Stesura di questo documento, progetto Roslyn è una versione di anteprima, installata come parte di Preview Visual Studio 2015. Una volta installati i modelli di Visual Studio 2015 anteprima SDK e Roslyn SDK, diagnostica può essere scritto utilizzando il modello di estensibilità fornita, diagnostica con codice Difficoltà (NuGet + VSIX). Per iniziare, come mostrato Figura 1, selezionare il modello diagnostico e denominare il progetto EmptyCatchDiagnostic.

diagnostica con codice Fix (NuGet + VSIX) modello di progetto
Figura 1 diagnostica con codice Fix (NuGet + VSIX) modello di progetto

Il secondo passo è quello di scrivere un analizzatore del nodo di sintassi che cammina l'albero (Sintattica astratta), alla ricerca di blocchi catch vuoto. Un piccolo frammento AST è mostrato Figura 2. La buona notizia è che il compilatore di Roslyn cammina l'AST per voi. È solo necessario fornire il codice per analizzare i nodi di interesse. (Per quelli familiare con modelli dal design classici "Banda dei quattro", questo è il modello del visitatore al lavoro). L'analizzatore deve ereditare dalla classe base astratta DiagnosticAnalyzer e implementare questi due metodi:

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

Roslyn astratto albero di sintassi per il frammento di codice: Se (punteggio > 100) grado = "A + +";
Figura 2 Roslyn astratto albero di sintassi per il frammento di codice: Se (punteggio > 100) grado = "A + +";

Il metodo SupportedDiagnostics è un semplice, restituendo una descrizione di ciascun analizzatore che stai offrendo a Roslyn. Il metodo Initialize è dove si registra il codice analizzatore con Roslyn. Durante l'inizializzazione è fornire Roslyn con due cose: il tipo di nodi in cui siete interessati; e il codice da eseguire quando uno di questi nodi viene rilevato durante la compilazione. Perché Visual Studio esegue la compilazione in background, queste chiamate si verificano mentre l'utente modifica, fornendo un feedback immediato su possibili errori.

Iniziare modificando il codice del modello pre-generati in quello che ti serve per la diagnostica di catch vuoto. Questo può essere trovato nel file di codice sorgente DiagnosticAnalyzer.cs entro le EmptyCatch­progetto diagnostico (la soluzione conterrà ulteriori progetti puoi tranquillamente ignorarlo). Nel codice che segue, quello che si vede in neretto sono i cambiamenti in relazione al codice pre-generato. In primo luogo, alcune stringhe che descrivono il nostro diagnostico:

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

Il metodo SupportedDiagnostics è corretto; dovete solo cambiare il metodo Initialize per registrare la vostra routine di analisi personalizzato sintassi, AnalyzeSyntax:

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

Come parte della registrazione, si noti che informiate Roslyn che siete solo interessati a clausole catch all'interno dell'AST. Questo tagli giù il numero di nodi alimentato a voi e aiuta anche a mantenere l'analizzatore pulito, semplice e single-purpose.

Durante la compilazione, quando un nodo della clausola catch viene rilevato nell'AST, viene chiamato il metodo di analisi AnalyzeSyntax. Questo è dove si guarda al numero di istruzioni in un blocco catch, e se tale numero è zero, si visualizza un avviso diagnostico perché il blocco è vuoto. Come mostrato Figura 3, quando l'analizzatore rileva un blocco catch vuoto, si crea un nuovo avviso di diagnostico, posizionarla nella posizione della parola chiave catch e segnalarlo.

Figura 3 incontrando una clausola 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);
  }
}

Il terzo passo è possibile creare ed eseguire la diagnostica. Quello che succede dopo è davvero interessante e un senso una volta che ci pensi. Avete appena costruito una diagnostica basata sul compilatore — così come provarlo? A partire da Visual Studio, installando la diagnostica, l'apertura di un progetto con blocchi catch vuoto e vedere cosa succede! Questo è raffigurato in Figura 4. Il tipo di progetto predefinito è un installatore VSIX, così quando si "esegue" il progetto, Visual Studio si avvia un'altra istanza di Visual Studio e viene eseguito il programma di installazione per esso. Una volta che tale seconda istanza è alto, è possibile testarla. Ahimè, test automatici di diagnostica è un po' oltre l'ambito del progetto, per ora, ma se la diagnostica si mantiene semplice e orientato al singolo, quindi non è troppo difficile testare manualmente.

Visual Studio esegue una diagnostica di blocco Catch vuoto in un'altra istanza di Visual Studio
Figura 4 Visual Studio esegue una diagnostica di blocco Catch vuoto in un'altra istanza di Visual Studio

Don' t stare lì, risolvere il problema!

Purtroppo, uno strumento che rileva un errore che potrebbe facilmente risolvere — ma non — è davvero solo fastidioso. Sorta di come quello tuo cugino che ha guardato a lottare per aprire la porta per ore prima di decidere di menzionare che è bloccato, quindi guardato si fatica a trovare un altro modo in per ancora più tempo prima che lui aveva la chiave.

Roslyn non vuole essere quel ragazzo.

Una correzione codice fornisce uno o più suggerimenti allo sviluppatore — suggerimenti per eventualmente correggere il problema rilevato dall'analizzatore. Nel caso di un vuoto blocco catch, un fix codice facile è di aggiungere un'istruzione throw, in modo che qualsiasi eccezione catturato immediatamente è rigenerata. Figura 5 illustra come la correzione del codice appare allo sviluppatore in Visual Studio, come descrizione comando familiare.

codice Fix suggerendo un tiro entro il vuoto blocco Catch
Figura 5 codice Fix suggerendo un tiro entro il vuoto blocco Catch

In questo caso focalizzare la tua attenzione su altri file di origine pre-generati nel progetto CodeFixProvider.cs. Il vostro compito è ereditare dalla classe base astratta CodeFixProvider e implementare tre metodi. Il metodo principale è ComputeFixesAsync, che offre suggerimenti per gli sviluppatori:

public sealed override async Task ComputeFixesAsync(CodeFixContext context)

Quando l'analizzatore segnala un problema, questo metodo viene chiamato da Visual Studio IDE per vedere se ci sono correzioni codice suggerito. Se è così, IDE viene visualizzata una descrizione comandi contenente i suggerimenti, da cui lo sviluppatore può selezionare. Se viene selezionata una dato documento — che denota l'AST per il file di origine — è aggiornato con la correzione suggerita.

Questo implica che una correzione del codice non è altro che una modifica suggerita per l'AST. Modificando l'AST, il cambiamento è portato le fasi rimanenti del compilatore, come se lo sviluppatore aveva scritto quel codice. In questo caso, il suggerimento è quello di aggiungere un'istruzione throw. Figura 6 è una rappresentazione astratta di quello che succede su.

aggiornando l'albero sintattico astratto
Figura 6 aggiornando l'albero sintattico astratto

Così il metodo costruisce un nuovo sottoalbero per sostituire il sottoalbero di blocco catch esistenti nell'AST. Questo nuovo fondo sottostruttura si accumulano: lanciare una nuova istruzione, quindi una lista per contenere la dichiarazione, poi un blocco per la lista e, infine, una cattura di ancorare il blocco di ambito:

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

Il passo successivo è quello di afferrare la radice dell'AST per questo file sorgente, trovare il blocco catch identificato dall'analizzatore e costruire un nuovo AST. Per esempio, newRoot denota un AST appena radicata per questo file di origine:

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.

L'ultimo passo è quello di registrare un'azione di codice che richiama il fix e aggiornare l'AST:

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

Per una serie di buone ragioni, la maggior parte delle strutture di dati in Roslyn sono immutabili, compreso l'AST. Questa è una scelta particolarmente buona qui, perché non si vuole aggiornare l'AST a meno che lo sviluppatore seleziona effettivamente la correzione del codice. Poiché l'AST esistente è immutabile, il metodo restituisce un nuovo AST, che sostituisce l'attuale AST dall'IDE se è selezionata la correzione del codice.

Potreste essere interessati che immutabilità arriva presso l'alto costo di consumo di memoria. Se l'AST è immutabile, che implica che una copia completa è necessaria ogni volta che viene apportata una modifica? Fortunatamente, solo le differenze sono memorizzate nell'AST (che è più facile memorizzare i Delta rispetto ad affrontare i problemi della concorrenza e la coerenza che creerebbe rendendo l'AST interamente modificabile) per ridurre al minimo la quantità di copia che si verifica per garantire l'immutabilità.

Nuovo terreno di rottura

Roslyn rompe un nuovo terreno aprendo il compilatore (e IDE, pure!) in questo modo. Per anni, c# ha propagandato stessa come un linguaggio "fortemente tipizzato", suggerendo che compilazione questionario aiuta a ridurre gli errori. Infatti, c# ha preso anche a pochi passi per cercare di evitare gli errori più comuni da altre lingue (ad esempio trattando i confronti integer come valori booleani, che porta il famigerato "se (x = 0)" bug che spesso fanno male agli sviluppatori C++). Ma i compilatori hanno sempre avuto essere estremamente selettivo su quali regole avrebbero potuto o vuoi applicare, perché tali decisioni erano tutto il settore, e diverse organizzazioni spesso avevano opinioni diverse su ciò che era "troppo stretto" o "troppo lento". Ora, con Microsoft aprendo le interiora del compilatore per gli sviluppatori, è possibile iniziare ad applicare le "regole della casa" sul codice, senza dover diventare esperti del compilatore sul proprio.

Controllare la pagina del progetto di Roslyn a roslyn.codeplex.com per dettagli su come iniziare con Roslyn. Se si desidera immergersi più profondamente l'analisi e la lexing, numerosi libri sono disponibili, tra cui il venerabile "Dragon Book", pubblicato ufficialmente come "compilatori: Principi, tecniche & Tools "(Addison Wesley, 2006) di Aho, Lam, Sethi e Ullman. Per coloro che sono interessati in modo più.NET-centriche avvicinarsi, considerare "compilazione per il .NET Common Language Runtime (CLR)" (Prentice Hall, 2001) di John Gough, o di Ronald Mak "scrittura di compilatori e Interpeters: Un approccio di ingegneria del Software"(Wiley, 2009).

Codificazione felice!


Ted Neward è CTO di iTrellis, una società di consulenza servizi. Ha scritto più di 100 articoli e autore di una dozzina di libri, tra cui "Professional F # 2.0" (Wrox, 2010). Egli è un MVP F # e parla a conferenze in tutto il mondo. Egli consulta e mentors regolarmente — contattarlo al ted@tedneward.com o ted@itrellis.com se siete interessati.

Joe Hummel, pH. d, è un professore associato di ricerca presso l'Università dell'Illinois, Chicago, un creatore di contenuti per Pluralsight, un Visual C++ MVP e consulente privato. Ha conseguito un pH.d. presso la UC Irvine nel campo del calcolo ad alte prestazioni ed è interessata in tutte le cose in parallele. Egli risiede a Chicago, e quando egli non è vela può essere raggiunto a joe@joehummel.net.

Grazie al seguente Microsoft esperto tecnico per la revisione di questo articolo: Kevin Pilch-Bisson