Repository navigation
Qualidade e Convencoes
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.
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.
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 |
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 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.
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 é 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.
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.
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.
| 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 |
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?".
-
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 F1Os 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 nadeveloperpor pull request; amainrecebe 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"
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 |
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:
-
build: parte da imagem do SDK (grande, com compilador), copia o código e compila. -
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).
| 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.
- Troque
namespace PolicyDesk.Domain.Entities;pornamespace PolicyDesk.Domain.Entities { ... }em um arquivo e rodedotnet build. Leia o erro. - Em qualquer método, declare uma variável que não é usada e rode o build.
- Abra o
Directory.Packages.propse procure a versão do EF Core. Agora procure em um.csproj: não há versão.
- 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.
- 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.
PolicyDesk · Distribuído sob a licença MIT · Repositório
Comece aqui
Como o sistema é feito
A API
Engenharia
Prática
Consulta