Skip to content

Qualidade e Convencoes

rodrigosdk edited this page Nov 12, 2026 · 3 revisions

Qualidade e convenções: regras que o computador confere por você

Para quem é este artigo: para quem vai contribuir com o código e precisa conhecer as regras do projeto, e para quem quer aprender a configurar um projeto .NET com qualidade verificada automaticamente.

Pré-requisitos: nenhum. Os termos técnicos são explicados quando aparecem.

O que você vai aprender: como transformar boas práticas em regras que o build verifica, o gerenciamento central de pacotes, o .editorconfig, as convenções de nomes e visibilidade, recursos modernos do C#, convenções de Git, práticas de segurança e as técnicas do Dockerfile.


A ideia: qualidade que não depende de memória

Toda equipe tem regras: "use namespaces de arquivo", "não deixe avisos no build", "não coloque dados pessoais nos logs". O problema das regras escritas em um documento é que dependem de alguém lembrar delas, e, num dia corrido, alguém esquece.

O PolicyDesk segue um princípio: sempre que possível, uma regra vira algo que o computador verifica. Se a regra for quebrada, o build falha, e o código não entra. Esta página mostra como.


A configuração global do build

O arquivo Directory.Build.props, na raiz, vale automaticamente para todos os projetos da solução. Configurar uma vez evita que cada projeto tenha regras diferentes:

<PropertyGroup>
    <TargetFramework>net10.0</TargetFramework>
    <Nullable>enable</Nullable>
    <ImplicitUsings>enable</ImplicitUsings>
    <AnalysisLevel>latest-recommended</AnalysisLevel>
    <EnforceCodeStyleInBuild>true</EnforceCodeStyleInBuild>
    <TreatWarningsAsErrors>true</TreatWarningsAsErrors>
</PropertyGroup>
Configuração O que faz Por quê
Nullable O compilador acompanha o que pode ser null e avisa quando você usa algo que talvez não exista O erro mais comum em C#, o NullReferenceException, vira um aviso em tempo de compilação. Claim? deixa explícito que GetByIdAsync pode não encontrar nada
ImplicitUsings Importa automaticamente os namespaces mais comuns Menos using repetido em todo arquivo
AnalysisLevel = latest-recommended Liga o conjunto recomendado de analisadores do .NET (regras CA*) Os analisadores leem o código e apontam problemas de desempenho, segurança e confiabilidade
EnforceCodeStyleInBuild As regras de estilo do .editorconfig (regras IDE*) rodam no dotnet build, não só dentro da IDE Quem usa outro editor, ou o CI, também é verificado
TreatWarningsAsErrors Qualquer aviso faz o build falhar Veja abaixo

Por que tratar avisos como erros?

Em projetos que toleram avisos, eles se acumulam: 10, 50, 300. Ninguém mais os lê, e um aviso importante (um null possível, um await esquecido) se perde no meio dos irrelevantes. Com a regra "zero avisos", cada aviso novo é notado imediatamente, porque quebra o build.

Ligar os analisadores exigiu alguns ajustes, que mostram o tipo de problema que eles encontram:

Regra O que exigiu
CA1305 Informar a cultura em string.Format (CultureInfo.CurrentCulture), porque números e datas são formatados de forma diferente em cada idioma
CA1824 Declarar o idioma do arquivo de mensagens neutro (NeutralLanguage = pt-BR)

O SDK também é fixado

O global.json fixa a versão do .NET SDK (10.0.100, aceitando versões mais novas do .NET 10). Assim, a máquina de cada pessoa e o CI compilam com as mesmas regras de análise. Sem isso, um SDK mais novo poderia trazer analisadores novos, e o build passaria na máquina de uma pessoa e falharia na de outra.


Gerenciamento central de pacotes

Um projeto .NET usa pacotes NuGet, bibliotecas de terceiros. Em uma solução com 11 projetos, cada um declarando suas versões, é fácil um projeto usar o EF Core 10.0.14 e outro o 10.0.12, com comportamentos sutilmente diferentes.

O Directory.Packages.props centraliza todas as versões em um arquivo:

<PropertyGroup>
    <ManagePackageVersionsCentrally>true</ManagePackageVersionsCentrally>
</PropertyGroup>

<ItemGroup Label="Aplicação">
    <PackageVersion Include="Microsoft.EntityFrameworkCore" Version="10.0.14" />
    <!-- ... -->
</ItemGroup>

E os projetos referenciam os pacotes sem versão:

<PackageReference Include="Microsoft.EntityFrameworkCore" />

Resultado: impossível ter duas versões do mesmo pacote, e atualizar uma dependência é mudar uma linha. O Dependabot também passa a abrir um único PR por atualização (veja CI/CD e Deploy).


O .editorconfig: estilo como configuração

O .editorconfig é um arquivo padrão, reconhecido por praticamente todas as IDEs, que define regras de formatação e estilo:

root = true

[*]
charset = utf-8
end_of_line = lf
insert_final_newline = true
indent_style = space

[*.cs]
indent_size = 4
csharp_style_namespace_declarations = file_scoped:warning
dotnet_sort_system_directives_first = true
dotnet_diagnostic.CA1863.severity = none

[tests/**.cs]
dotnet_diagnostic.CA1707.severity = none

[**/Migrations/*.cs]
generated_code = true
Regra Por quê
end_of_line = lf Windows e Linux usam caracteres diferentes para quebra de linha. Fixar um evita diffs em que "todas as linhas mudaram"
file_scoped:warning Exige namespace X; em vez de namespace X { ... }. Com avisos tratados como erros, a forma antiga quebra o build. Ganho: um nível de indentação a menos em todo arquivo
CA1863 desligada A regra sugere uma otimização para string.Format. Nas mensagens de erro, que são raras, o ganho não compensa a complexidade
CA1707 desligada só em testes Permite nomes como Deve_X_Quando_Y nos testes, sem afrouxar a regra no código de produção
generated_code nas migrations O código gerado pelo EF Core não é cobrado por regras de estilo

Repare na técnica: desligar uma regra só onde ela não faz sentido, e não no projeto inteiro.


Convenções de código

Nomes

Nomes consistentes permitem adivinhar onde algo está e o que faz, sem abrir o arquivo.

Elemento Convenção Exemplo
Entidade Singular, em inglês Policyholder, Policy, Claim
Caso de uso <Verbo><Agregado>UseCase ApproveClaimUseCase
Interface I + nome IApproveClaimUseCase
Repositório I<Agregado>{ReadOnly|WriteOnly|UpdateOnly}Repository IClaimsReadOnlyRepository
Método assíncrono Sufixo Async (exceto actions de controller) ExecuteAsync, CommitAsync
Busca para alterar Sufixo ForUpdate GetByIdForUpdateAsync
Request Request<Recurso>Json RequestClaimJson
Response Response<Recurso>Json ResponseClaimJson
Propriedades do contrato Em português ValorCobertura, DataOcorrencia
Enum do contrato Em português, sem valores numéricos StatusSinistro.EmAnalise
Mapeamento EF <Entidade>Map ClaimMap
Teste Deve_<Resultado>_Quando_<Condição> Deve_AprovarEConfirmar_Quando_SinistroEstiverEmAnalise

Por que as actions dos controllers não têm o sufixo Async? Porque o ASP.NET remove o sufixo do nome da action, e o CreatedAtAction(nameof(GetById), ...) deixaria de encontrar a rota.

Visibilidade: o mínimo necessário

Em C#, public é visível para todo mundo, e internal, só dentro do mesmo projeto. A regra do PolicyDesk é expor o mínimo necessário:

Tipo Visibilidade Por quê
Interfaces de casos de uso e repositórios public São os contratos entre as camadas
Implementações de casos de uso internal Só o contêiner de injeção de dependência as cria
Repositórios, DbContext, UnitOfWork, mapeamentos internal Detalhes da infraestrutura
Repositórios sealed Não foram projetados para herança

Quanto menos coisas públicas, menos coisas alguém pode usar errado, e mais liberdade para mudar o que é interno. Os testes acessam os tipos internal com InternalsVisibleTo.

Recursos modernos de C#

Recurso Versão Exemplo no projeto O que resolve
Primary constructors C# 12 class ApproveClaimUseCase(IClaimsUpdateOnlyRepository repo, ...) Elimina o construtor que só copia parâmetros para campos
Collection expressions C# 12 [domainRuleException.Message], IgnoreQueryFilters([ActiveFilter]) Listas literais curtas
Raw string literals C# 11 O SQL do upsert entre """ Texto com aspas e várias linhas, sem escapes
File-scoped namespaces C# 10 namespace PolicyDesk.Domain.Entities; Um nível de indentação a menos
Pattern matching com propriedades C# 8+ exception is PostgresException { SqlState: ..., ConstraintName: ... } Testar tipo e propriedades em uma expressão
throw como expressão C# 7 ?? throw new NotFoundException(...) "Busque, ou falhe" em uma linha
Source generators .NET 6+ [LoggerMessage], [GeneratedRegex] Código gerado na compilação, sem custo em tempo de execução
DateOnly .NET 6+ BirthDate, StartDate, OccurredOn Datas sem horário
TimeProvider .NET 8+ O BusinessClock recebe o relógio por injeção Tempo testável

Comentários: o porquê, nunca o quê

O código já diz o que faz. Um comentário que repete isso (// incrementa o contador) só envelhece mal. Os comentários do PolicyDesk registram decisões que não são óbvias:

// Usado pelo EF Core ao carregar do banco: não reaplica as regras de criação
private Claim() { }
// Gravado no OnStarting: o tratamento de exceções limpa os cabeçalhos antes de escrever o erro
// PostConfigure: o AddControllers() registra a fábrica padrão e sobrescreveria um Configure comum.

Cada um responde a pergunta que um leitor faria: "por que não foi feito do jeito óbvio?".


Convenções de Git

  • Mensagens de commit em português, com uma linha só, no infinitivo, dizendo o que a mudança faz:

    Usar o fuso horário de negócio nas regras de data e guardar as datas como DateOnly (#16)
    Adicionar o pipeline de CI/CD com deploy no Azure App Service F1
    

    Os pull requests entram com squash (todos os commits do PR viram um), e o número do PR fica no fim da mensagem. O detalhe (o que mudou, o que exige atenção, como testar) vai na descrição do PR.

  • Branches: o trabalho acontece em feature/<nome> e entra na developer por pull request; a main recebe as versões estáveis e dispara o deploy.

gitGraph
    commit id: "main"
    branch developer
    checkout developer
    branch feature/paginacao
    commit id: "trabalho"
    checkout developer
    merge feature/paginacao id: "PR + CI"
    checkout main
    merge developer id: "deploy"
Loading

Segurança e privacidade

Segurança não é uma funcionalidade isolada; é um conjunto de pequenas práticas em todo o código:

Prática Onde Protege contra
Sem dados pessoais nos logs HTTP logging restrito, catálogos de log Vazamento de CPF, e-mail e telefone (LGPD)
Sem detalhes de exceção na resposta ExceptionFilter Expor a estrutura interna para atacantes
Segredos fora do código User Secrets, variáveis de ambiente, secrets do GitHub Senhas parando no Git
Deploy sem senha guardada Login OIDC na Azure, com permissão só sobre o Web App Roubo de credenciais de longa duração
Pacotes vulneráveis bloqueiam o CI dotnet list package --vulnerable Usar bibliotecas com falhas conhecidas
Análise estática de segurança CodeQL Falhas no próprio código
Dependências atualizadas Dependabot Ficar para trás em correções de segurança
Contêiner sem root USER $APP_UID no Dockerfile Um invasor ganhar controle total do contêiner
Banco só com TLS SSL Mode=Require em produção Senhas e dados trafegando abertos
Limite de 64 KB no corpo Kestrel Ataques com payloads gigantes
SQL parametrizado SqlQuery com string interpolada SQL injection
Objetos de entrada separados das entidades Projeto Communication Mass assignment: o cliente enviar "status": "Pago" e o campo ser aceito
Migrations fora da aplicação em produção Program.cs e o pipeline A aplicação precisar de permissões de alterar tabelas

O Dockerfile, explicado

Os arquivos do Docker ficam em docker/: o Dockerfile, o Dockerfile.dockerignore, o compose.yml (API e banco) e o compose.dev.yml (Aspire Dashboard e pgAdmin).

FROM mcr.microsoft.com/dotnet/sdk:10.0 AS build
WORKDIR /src
COPY . .
RUN dotnet publish src/Backend/PolicyDesk.Api/PolicyDesk.Api.csproj \
    --configuration Release --output /app/publish /p:UseAppHost=false

FROM mcr.microsoft.com/dotnet/aspnet:10.0 AS runtime
WORKDIR /app
COPY --from=build /app/publish .
ENV ASPNETCORE_HTTP_PORTS=8080
EXPOSE 8080
USER $APP_UID
ENTRYPOINT ["dotnet", "PolicyDesk.Api.dll"]

O arquivo tem dois FROM, ou seja, dois estágios:

  1. build: parte da imagem do SDK (grande, com compilador), copia o código e compila.
  2. runtime: parte de uma imagem só com o runtime do ASP.NET (bem menor) e copia apenas o resultado da compilação.
Técnica Benefício
Multi-stage build A imagem final não tem SDK nem código-fonte: é menor e oferece menos ferramentas para um invasor
USER $APP_UID Roda com um usuário comum, já criado nas imagens oficiais do .NET 8+, e não como root
Porta 8080 Portas abaixo de 1024 exigem privilégios de root
UseAppHost=false Dispensa o executável nativo; a imagem já tem o dotnet
Dockerfile.dockerignore Exclui bin/, obj/ e outros arquivos do que é enviado ao Docker. O nome <Dockerfile>.dockerignore faz o Docker usar esse arquivo só com esse Dockerfile

É a mesma imagem nos dois lugares. O docker compose usa este Dockerfile para rodar localmente, e o pipeline de CD usa o mesmo arquivo para gerar a imagem que vai para produção, publicada no GitHub Container Registry. O que roda na Azure é o que roda na sua máquina (veja CI/CD e Deploy).


Tudo isso é verificado a cada pull request

Verificação Ferramenta
Build sem avisos, com analisadores e estilo dotnet build -c Release
Todos os testes, inclusive com PostgreSQL real dotnet test + Testcontainers
Pacotes com vulnerabilidade conhecida dotnet list package --vulnerable --include-transitive
Vulnerabilidades no código CodeQL (security-and-quality)

Detalhes em CI/CD e Deploy.


Experimente

  1. Troque namespace PolicyDesk.Domain.Entities; por namespace PolicyDesk.Domain.Entities { ... } em um arquivo e rode dotnet build. Leia o erro.
  2. Em qualquer método, declare uma variável que não é usada e rode o build.
  3. Abra o Directory.Packages.props e procure a versão do EF Core. Agora procure em um .csproj: não há versão.

Resumo

  • Regras viram verificações automáticas: analisadores, estilo no build e avisos como erros.
  • Versões de pacotes e do SDK ficam centralizadas e fixadas.
  • Nomes e visibilidade seguem convenções que tornam o código previsível.
  • Comentários explicam o porquê.
  • Segurança é um conjunto de práticas pequenas, aplicadas em todas as camadas.

Para aprofundar

  • Documentação do .NET: "Code analysis in .NET" e "Central Package Management".
  • editorconfig.org.
  • Clean Code, Robert C. Martin, capítulos sobre nomes e comentários.
  • Próximo artigo sugerido: CI/CD e Deploy.

Clone this wiki locally