Code Analysis Design Reference

Keyworks: Code Analysis FxCop Code Review StyleCop Analyzers Roslyn

Conceitos

Code Analysis

Esta técnica permite analisar o código de uma assembly, seja no momento da sua compilação ou após esse momento, para detetar e reportar violações de regras de desenho ou de programação. Essas regras baseiam-se em boas práticas definidas para a Framework .NET e em outras próprias da PRIMAVERA. O objetivo final é que a build de uma determinada assembly falhe caso se verifiquem violações dessas regras.

Code Analysis Ruleset

Trata-se de um ficheiro que define as regras que devem ser aplicadas na análise de um determinado projeto ou conjunto de projetos (solução). Tipicamente esse ficheiro define os analyzers que devem ser usados, as regras que devem ser aplicadas e o seu resultado (se são ignoradas, provocam warnings ou provocam erros.

Roslyn

A .NET Compiler Platform (ou Roslyn) é uma iniciativa da Microsoft para produzir os compiladores de C# (e VB.NET) em open-source. Os compiladores incluídos na versão 2015 do Visual Studio foram produzidos em resultado desta iniciativa.

É também comum usar o nome Roslyn para referenciar o compilador C# 5.0.

A primeira versão desses compiladores disponibiliza uma API que, entre outras funcionalidades, permite a construção de diagnostic analyzers (ou code analyzers) para validar regras de code analysis durante a compilação (quando em versões anteriores do compilador C# esse tipo de análise só era possível após a compilação, sobre o IL gerado (ver FxCop).

Diagnostic Analyzer

Um analyzer implementado usando a API do Roslyn. Este tipo de analyzers permite fazer qualquer tipo de análise do código (sintática, semântica, antes ou depois da geração das assemblies). Trata-se de uma evolução em relação às regras FxCop que se tornou possível com o Roslyn e que tem ainda a vantagem de ser possível para cada regra, disponibilizar também um “code fixer” para fazer a correção do código no IDE do Visual Studio.

FxCop

Esta era a única tecnologia de code analysis disponível no Visual Studio antes da versão 2015 e que continua a ser suportada porque implementa um conjunto vasto de regras que ainda não estão disponíveis como diagnostic analyzers. Permite fazer unicamente análise estática das assemblies depois delas serem geradas.

StyleCop

O StyleCop é uma ferramenta particular de code analysis que faz análise da formatação do código propriamente dito (documentação, indentação, naming, etc.). Está disponível agora também como diagnostic analyzers.

Decisões

É evidente a pertinência de aplicar e validar regras de programação e de desenho aos componentes desenvolvidos pelas equipas de programação da PRIMAVERA. Desde logo para assegurar a coerência das práticas entre os vários programadores e as várias equipas.

Com esse objetivo em mente, esta tecnologia e ferramentas deve ser usada segundo as seguintes regras:

  • Todos os projetos .NET devem ter o code analysis ativo.
  • Deve aplicar-se um ruleset comum a todos as soluções e projetos. Em determinados casos, pode haver um ruleset específico (por exemplo, por questões resultantes das tecnologias usadas). Esses rulesets específicos devem ainda assim constar de uma lista restrita gerida pela equipa da Plataforma (não pela equipa do projeto em causa).
  • As builds devem sempre falhar quando se detetar uma violação do tipo Error.
  • A execução dos analyzers tem impacto na velocidade de desenvolvimento e na performance das máquinas dos developers. Por essa razão, nos projetos esses settings só devem estar ativos na configuração Release (para que o developer possa trabalhar em Debug sem ser impactado pela análise). O facto da build falhar porque executa em Release se houver uma violação assegura a qualidade do código. O programador deve, portanto, assegurar-se que o seu check-in não viola nenhuma regra, compilando o projeto em Release antes de o efetuar.

Implementação

Definição das Regras de Code Analysis

Os diagnostica analyzers disponíveis e as regras de code analysis aplicáveis aos projetos são definidas e publicadas pelo package NuGet Primavera.VisualStudio.Analyzers.

Code Analysis

Este package deve ser instalado em cada projeto e tratará da configuração do projeto e da instalação local dos analyzers e dos ficheiros de configuração:

Code Analysis

Code Analysis

Code Analysis

O ficheiro Primavera.VisualStudio.Analyzers.props define propriedades do projeto para ativar o code analysis (FxCop):

<?xml version="1.0" encoding="utf-8"?>
<Project ToolsVersion="4.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
  <!-- RunCodeAnalysis -->
  <PropertyGroup Condition=" '$(Configuration)' == 'Debug' ">
    <RunCodeAnalysis>false</RunCodeAnalysis>
  </PropertyGroup>
  <PropertyGroup Condition=" '$(Configuration)' == 'Release' ">
    <RunCodeAnalysis>true</RunCodeAnalysis>
  </PropertyGroup>
</Project>

O ficheiro Primavera.VisualStudio.Analyzers.props importa os vários analyzers para o projeto:

<?xml version="1.0" encoding="utf-8"?>
<Project ToolsVersion="4.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
  <!-- Analyzers -->
  <ItemGroup Condition=" '$(Configuration)' == 'Release' ">
    <Analyzer Include="$(MSBuildThisFileDirectory)..\analyzers\Newtonsoft.Json.dll" />
    <Analyzer Include="$(MSBuildThisFileDirectory)..\analyzers\StyleCop.Analyzers.CodeFixes.dll" />
    <Analyzer Include="$(MSBuildThisFileDirectory)..\analyzers\StyleCop.Analyzers.dll" />
    <Analyzer Include="$(MSBuildThisFileDirectory)..\analyzers\Primavera.VisualStudio.Analyzers.Async.dll" />
    <Analyzer Include="$(MSBuildThisFileDirectory)..\analyzers\Primavera.VisualStudio.Analyzers.Common.dll" />
    <Analyzer Include="$(MSBuildThisFileDirectory)..\analyzers\Primavera.VisualStudio.Analyzers.Core.dll" />
    <Analyzer Include="$(MSBuildThisFileDirectory)..\analyzers\Primavera.VisualStudio.Analyzers.CoreLib.dll" />
    <Analyzer Include="$(MSBuildThisFileDirectory)..\analyzers\Primavera.VisualStudio.Analyzers.General.dll" />
  </ItemGroup>
  <!-- CodeAnalysisRuleSet -->
  <PropertyGroup>
<CodeAnalysisRuleSet>$(MSBuildThisFileDirectory)Primavera.VisualStudio.Analyzers.ruleset</CodeAnalysisRuleSet>
  </PropertyGroup>
</Project>
  • A primeira secção indica os analyzers que devem ser utilizados. A condição assinalada garante que serão apenas executados quando a build estiver em Release.
  • A última referencia o ficheiro ruleset.

Por último, o ficheiro Primavera.VisualStudio.Analyzers.ruleset define as regras aplicáveis e o seu estado.

Ativação do Code Analysis nas Soluções e Projetos

Para que os projetos de uma determinada solução tenham o code analysis ativo bastará que referenciem o package Primavera.VisualStudio.Analyzers:

<?xml version="1.0" encoding="utf-8"?>
<packages>
  (...)
  <package id="Primavera.VisualStudio.Analyzers" version="3.0000.16106.01" targetFramework="net451" developmentDependency="true" />
  (...)
</packages>
A instalação do package faz as seguintes alterações no csproj no início do ficheiro:
<?xml version="1.0" encoding="utf-8"?>
<Project ToolsVersion="12.0" DefaultTargets="Build" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
  <Import Project="..\..\..\..\..\_packages\Primavera.VisualStudio.Analyzers.3.0000.16106.01\build\Primavera.VisualStudio.Analyzers.props" Condition="Exists('..\..\..\..\..\..\_packages\Primavera.VisualStudio.Analyzers.3.0000.16106.01\build\Primavera.VisualStudio.Analyzers.props')" />
(...)

E no fim do projeto:

(...)
  <Target Name="EnsureNuGetPackageBuildImports" BeforeTargets="PrepareForBuild">
    <PropertyGroup>
      <ErrorText>This project references NuGet package(s) that are missing on this computer. Use NuGet Package Restore to download them.  For more information, see http://go.microsoft.com/fwlink/?LinkID=322105. The missing file is {0}.</ErrorText>
    </PropertyGroup>
    <Error Condition="!Exists('..\..\..\..\..\..\_packages\Primavera.VisualStudio.Analyzers.3.0000.16106.01\build\Primavera.VisualStudio.Analyzers.props')" Text="$([System.String]::Format('$(ErrorText)', '..\..\..\..\..\..\_packages\Primavera.VisualStudio.Analyzers.3.0000.16106.01\build\Primavera.VisualStudio.Analyzers.props'))" />
    <Error Condition="!Exists('..\..\..\..\..\..\_packages\Primavera.VisualStudio.Analyzers.3.0000.16106.01\build\Primavera.VisualStudio.Analyzers.targets')" Text="$([System.String]::Format('$(ErrorText)', '..\..\..\..\..\..\_packages\Primavera.VisualStudio.Analyzers.3.0000.16106.01\build\Primavera.VisualStudio.Analyzers.targets'))" />
  </Target>
  <Import Project="..\..\..\..\..\_packages\Primavera.VisualStudio.Analyzers.3.0000.16106.01\build\Primavera.VisualStudio.Analyzers.targets" Condition="Exists('..\..\..\..\..\..\_packages\Primavera.VisualStudio.Analyzers.3.0000.16106.01\build\Primavera.VisualStudio.Analyzers.targets')" />
</Project>

Quando a solução estiver em Debug o code analysis estará inativo e a lista de analyzers de cada projeto estará vazia:

Em release, essa lista incluirá todos os analyzers e as respetivas regras (as do FxCop não são visíveis aqui mas podem ser consultadas visualizado o ficheiro ruleset):

Diagnostic Analysers PRIMAVERA

Estão implementados vários diagnostic analyzers específicos para a PRIMAVERA nas seguintes assemblies:

  • Primavera.VisualStudio.Analyzers.Async.dll
  • Primavera.VisualStudio.Analyzers.Core.dll
  • Primavera.VisualStudio.Analyzers.CoreLib.dll
  • Primavera.VisualStudio.Analyzers.General.dll

Os vários analyzers definem regras nas seguintes categorias:

  • DesignRules
  • GlobalizationRules
  • MaintainabilityRules
  • NamingRules
  • ReliabilityRules
  • UsageRules

Todas as regras PRIMAVERA provocam erros por defeito (exceto quando ainda estão em desenvolvimento ou quando ainda não é possível aplica-las aos projetos da Plataforma). Isto porque não são implementados analyzers para questões que só possam ser considerados como warnings.

As regras implementadas são a seguintes:

DesignRules

  • PRD1000 - Application service contract should be an interface
  • PRD1001 - Application service contract should be public
  • PRD1002 - Application service contract name should end with 'Service'
  • PRD1003 - Application service provider contract should be an interface
  • PRD1004 - Application service provider contract should be public
  • PRD1005 - Application service provider contract name should end with 'Provider' (and not 'ServiceProvider').
  • PRD1006 - Application service implementation should be a class
  • PRD1007 - Application service implementation should be internal
  • PRD1008 - Application service implementation name should end with 'Service'
  • PRD1009 - Application service implementation should implement the application service contract
  • PRD1010 - Application service implementation attribute should reference the application service contract
  • PRD1011 - Application service provider implementation should be a class
  • PRD1012 - Application service provider implementation should be internal
  • PRD1013 - Application service provider implementation name should end with 'Provider' (and not 'ServiceProvider')
  • PRD1014 - Application service provider implementation should implement the application service provider contract
  • PRD1015 - Application service provider implementation attribute should reference the application service provider contract
  • PRD1016 - Application service implementation should be marked with the [ApplicationServiceImplementation] attribute
  • PRD1017 - Application service provider implementation should be marked with the [ApplicationServiceProviderImplementation] attribute
  • PRD1018 - Component bootstrapper should be a class
  • PRD1019 - Component bootstrapper should be internal
  • PRD1020 - Component bootstrapper should derive from 'ComponentBootstrapperBase'
  • PRD1021 - Component bootstrapper name should end with 'Bootstrapper'
  • PRD1022 - Module bootstrapper should be a class
  • PRD1023 - Module bootstrapper should be internal
  • PRD1024 - Module bootstrapper should derive from 'ModuleBootstrapperBase'
  • PRD1025 - Module bootstrapper name should end with 'Bootstrapper'
  • PRD1026 - Component bootstrapper should be marked with the [ComponentBootstrapper] attribute
  • PRD1027 - Module bootstrapper should be marked with the [ModuleBootstrapper] attribute
  • PRD1028 - Assembly should be marked with the [ModuleProject] attribute

GlobalizationRules

  • PRG1000 - Do not pass literals as parameters of known types constructors
  • PRG1001 - Do not invoke known extension methods on string literals
  • PRG1002 - Do not pass literals as parameters of known extension methods

MaintainabilityRules

  • PRM1000 - Assembly should be marked with the [ReferencesValidationSpec] attribute
  • PRM1001 - Invalid reference between assemblies
  • PRM1002 - Invalid reference between different module assemblies

NamingRules

  • PRN1000 - Use Async suffix
  • PRN1001 - Avoid Async suffix

ReliabilityRules

  • PRR1000 - Avoid async void
  • PRR1001 - Property getter returns itself
  • PRR1002 - Property setter sets itself
  • PRR1003 - Assignment to itself
  • PRR1004 - Call is not awaited

UsageRules

  • PRU1000 - Use ConfigurationService
  • PRU1001 - Use SmartGuard

Outros Diagnostic Analysers

Outros analyzers são packages externos:

  • StyleCop.Analyzers.dll – analyzers StyleCop.

Questões em Aberto

  • Parte dos analyzers implementados poderiam ser acompanhados de code fixers.
  • A organização em categorias precisa de ser melhorada.

FAQ

  • P: Posso fazer suppress de um erro de code analysis?
    • R: Sim mas apenas se for um “falso positivo” ou a correção do erro for impossível.
  • P: A análise de um projeto não está a ser executada. Porquê?
    • R: A análise só corre se a compilação estiver em Release. Se não executa pode ser porque a referência para o targets está errada (a Factory inclui uma ferramenta para verificar essa definição) ou porque os ficheiros em $Common estão desatualizados (a Factory também tem uma funcionalidade para provocar o get latest desses ficheiros quando são atualizados).