Como fazer o Claude escrever código de qualidade em .NET

Garantir código de qualidade com Claude em projetos .NET exige mais do que um bom prompt, porque agentes de IA escrevem C# em uma velocidade que nenhum time humano acompanha. O problema não é a velocidade, e sim o que chega até a revisão.

O código gerado por um agente frequentemente compila, passa nos testes e ainda assim carrega decisões que um revisor experiente rejeitaria. Nomes vagos, tipos de retorno permissivos, interfaces desnecessárias e métodos assíncronos mal nomeados são exemplos frequentes.

A causa é mais simples do que parece. O agente seguiu as regras que conseguia enxergar, e as suas regras nunca estiveram entre elas.

Este artigo descreve uma abordagem em duas camadas para garantir código de qualidade com Claude. A primeira camada transforma padrões de código em erros de compilação, e a segunda move as regras que nenhum compilador verifica para uma skill de revisão, carregada só quando a revisão acontece.

Todas as configurações assumem o .NET 10, a versão LTS lançada em novembro de 2025 e com suporte até novembro de 2028.

Por que a instrução perde para o compilador

Existe uma diferença de natureza entre uma regra escrita em um documento e uma regra que quebra o build.

A primeira depende de o agente lembrar dela no momento certo, entre milhares de tokens de contexto acumulado. A segunda, por outro lado, aparece na saída do dotnet build como um erro, e o agente não tem escolha a não ser corrigir.

Essa distinção organiza toda a estratégia de código de qualidade com Claude. Portanto, o que puder ser expresso como erro de compilação deve ser expresso assim, porque o custo de manutenção é zero e a taxa de cumprimento é total.

O que sobra depois disso vira o material da segunda camada.

Código de qualidade com Claude começa no Directory.Build.props

O MSBuild oferece um mecanismo de customização por pasta que elimina a duplicação de configuração entre projetos.

Conforme a documentação do MSBuild, o Microsoft.Common.props procura um arquivo Directory.Build.props na estrutura de diretórios e importa as propriedades dele. A busca sobe a partir da pasta do projeto e para no primeiro arquivo encontrado.

Um detalhe relevante: a localização do arquivo de solução não importa para esse mecanismo. Na prática, vale apenas a hierarquia de pastas a partir do .csproj.

Esta é a configuração base para código de qualidade com Claude em qualquer solução nova:

<Project>
  <PropertyGroup>
    <Nullable>enable</Nullable>
    <ImplicitUsings>enable</ImplicitUsings>
    <AnalysisLevel>latest</AnalysisLevel>
    <AnalysisMode>All</AnalysisMode>
    <TreatWarningsAsErrors>true</TreatWarningsAsErrors>
    <CodeAnalysisTreatWarningsAsErrors>true</CodeAnalysisTreatWarningsAsErrors>
    <EnforceCodeStyleInBuild>true</EnforceCodeStyleInBuild>
  </PropertyGroup>
</Project>

O AnalysisLevel define qual conjunto de analisadores roda, de acordo com uma versão do .NET. Para projetos que miram o .NET 5 ou superior, o valor padrão já é latest.

Fixar esse valor em uma versão, como 10, congela o conjunto de regras. Você continua recebendo correções nas regras existentes, mas nenhuma regra nova entra ao atualizar o SDK.

Já o AnalysisMode define a agressividade. No modo Default, apenas um número pequeno de regras vira aviso de build, enquanto no modo All todas as regras viram avisos.

Mesmo assim, algumas regras legadas ficam de fora no modo All, como CA1017, CA1045, CA1005, CA1014, CA1060, CA1021 e as regras de métrica CA1501, CA1502, CA1505, CA1506 e CA1509. Para usá-las, habilite cada uma com dotnet_diagnostic.CAxxxx.severity.

Por fim, o EnforceCodeStyleInBuild merece atenção. Por padrão, as regras de estilo com prefixo IDE ficam desligadas no build de linha de comando, então o agente, que só enxerga a saída do build, nunca as veria.

TreatWarningsAsErrors muda o comportamento do agente

Entre todas as configurações acima, o TreatWarningsAsErrors é a que altera de fato o fluxo de trabalho.

Ele converte cada violação em erro de compilação. Assim, o agente executa o build, lê o erro, corrige e recompila, tudo antes de você abrir o diff.

A documentação da Microsoft descreve uma propriedade complementar. Com o flag -warnaserror, os avisos de análise também viram erros, e quem não quiser esse efeito nas regras CA pode definir CodeAnalysisTreatWarningsAsErrors como false.

Na prática, isso permite calibrar o rigor. Você pode manter os avisos do compilador como erros e afrouxar os de análise de código, ou o contrário.

Para bases legadas, existe uma saída pragmática. A propriedade pode ser sobrescrita por projeto, o que evita que uma biblioteca antiga dispare centenas de erros no primeiro build.

Esse ciclo curto de feedback é o motor do código de qualidade com Claude, porque o agente aprende com o erro na mesma hora.

Analisadores de terceiros a favor do código de qualidade com Claude

O SDK do .NET já inclui os analisadores Roslyn, sem instalação separada. Além disso, para projetos que miram o .NET 5 ou superior, a análise de código vem habilitada por padrão.

Ainda assim, pacotes de terceiros reforçam o código de qualidade com Claude, porque cada um cobre um território diferente.

<ItemGroup>
  <PackageReference Include="Meziantou.Analyzer">
    <PrivateAssets>all</PrivateAssets>
    <IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
  </PackageReference>
  <PackageReference Include="SonarAnalyzer.CSharp">
    <PrivateAssets>all</PrivateAssets>
    <IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
  </PackageReference>
  <PackageReference Include="Roslynator.Analyzers">
    <PrivateAssets>all</PrivateAssets>
    <IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
  </PackageReference>
</ItemGroup>

As referências aparecem sem Version porque as versões ficam centralizadas no Directory.Packages.props, como você verá adiante.

O SonarAnalyzer.CSharp concentra-se em code smells, complexidade e vulnerabilidades. Já o Meziantou.Analyzer cobre uso incorreto de APIs, padrões de async e armadilhas de LINQ.

O Roslynator.Analyzers, por sua vez, atua sobre idiomatismo e simplificação de expressões. Para projetos de teste, o xunit.analyzers verifica erros comuns, como asserções ausentes.

Os metadados PrivateAssets e IncludeAssets mantêm esses pacotes como dependências de desenvolvimento. Dessa forma, eles atuam no build e não se propagam para quem consome o seu projeto.

O ganho fica evidente quando você pensa em custo de contexto. Esses pacotes trazem milhares de regras que o agente precisa satisfazer, e nenhuma delas ocupa uma linha do seu prompt.

O .editorconfig e a armadilha de severidade

Analisadores encontram problemas. O .editorconfig, por sua vez, decide quais problemas importam e com que gravidade.

O arquivo costuma ficar na raiz da solução e vale para qualquer IDE compatível, o que elimina divergências entre membros do time.

root = true

[*]
charset = utf-8
indent_style = space
indent_size = 4
insert_final_newline = true
trim_trailing_whitespace = true

[*.cs]
dotnet_diagnostic.CS8600.severity = error
dotnet_diagnostic.CS8602.severity = error
dotnet_diagnostic.CS8604.severity = error

dotnet_diagnostic.IDE0040.severity = warning
dotnet_diagnostic.IDE1006.severity = error

Aqui está o ponto mais delicado deste artigo, onde muita configuração publicada falha em silêncio.

É comum ver regras de nomenclatura com severidade própria, no formato dotnet_naming_rule.async_methods_end_in_async.severity = error. A intenção é fazer um método assíncrono sem o sufixo Async quebrar o build.

Porém, a documentação é explícita: a severidade dentro de uma regra de nomenclatura só vale em IDEs como o Visual Studio. Os compiladores de C# e Visual Basic não entendem essa configuração, então ela não vale durante o build.

A consequência para o código de qualidade com Claude é concreta. Se você depende apenas dessa linha, o agente compila sem erro algum e a regra nunca é aplicada.

Para verificar nomenclatura no build, configure a severidade pelo identificador. Todas as opções de nomenclatura respondem pelo IDE1006, e o valor precisa ser warning ou error.

Existe ainda outra interação que surpreende. Ao configurar a análise por propriedades do MSBuild como AnalysisMode, qualquer configuração em massa do .editorconfig passa a ser ignorada.

Ou seja, se você habilitou uma categoria inteira no .editorconfig e também definiu AnalysisMode, a configuração em massa perde efeito. As entradas individuais por regra, contudo, continuam valendo.

Central Package Management contra versões inventadas

À medida que a solução cresce, versões de pacote divergem entre projetos e as atualizações ficam trabalhosas.

O Central Package Management resolve isso movendo todas as versões para um arquivo único. Basta criar um Directory.Packages.props na raiz do repositório com ManagePackageVersionsCentrally igual a true, ou gerar o arquivo com dotnet new packagesprops.

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

  <ItemGroup>
    <PackageVersion Include="Microsoft.EntityFrameworkCore" Version="10.0.0" />
    <PackageVersion Include="Npgsql.EntityFrameworkCore.PostgreSQL" Version="10.0.0" />
    <PackageVersion Include="xunit" Version="2.9.3" />
  </ItemGroup>
</Project>

Nos projetos, a referência passa a ser declarada sem o atributo Version. Em seguida, o NuGet resolve a versão a partir da entrada correspondente no arquivo central.

Para o código de qualidade com Claude, esse efeito colateral é o mais interessante. Um agente que tenta declarar uma versão no PackageReference recebe o erro NU1008, que exige a versão em um item PackageVersion.

Com isso, toda invenção de versão fica concentrada em um único arquivo, visível em uma linha do diff.

Duas extensões merecem menção. O GlobalPackageReference, colocado no Directory.Packages.props, declara pacotes usados por todos os projetos, como analisadores, sem propagá-los para dependências downstream.

Já o CentralPackageTransitivePinningEnabled promove uma dependência transitiva a dependência de primeiro nível quando necessário. No entanto, downgrades não são permitidos e resultam no erro NU1109.

Se você usa o .NET 10, o artigo sobre NuGet package pruning complementa esse tema.

Onde as regras no prompt falham

Depois de todas essas camadas, o build ainda não pega tudo.

Nenhum analisador dirá que OrderHelper é um nome ruim. Da mesma forma, nenhum pipeline sinalizará que Send(order, true) esconde a intenção da chamada.

A reação natural é escrever essas regras no prompt. E isso funciona, mas só por algumas mensagens.

O motivo do desgaste é estrutural. As regras entram na primeira mensagem, e em seguida o agente lê arquivos, executa builds, interpreta falhas de teste e escreve novos arquivos.

Quando chega a hora de nomear uma classe, as suas regras estão milhares de tokens atrás. Elas continuam na janela de contexto, mas viraram uma fração pequena de um volume muito maior de texto.

A alternativa mais comum, colocar as regras no CLAUDE.md, tem outro custo. Esse arquivo acompanha toda requisição, inclusive as tarefas em que nenhuma classe é nomeada.

Em outras palavras, você paga por ele constantemente e se beneficia raramente. Esse é o limite do prompt quando o objetivo é código de qualidade com Claude.

Skills como conhecimento sob demanda

Para manter código de qualidade com Claude sem inflar o contexto, a alternativa arquitetural é a skill.

Uma skill é uma pasta com um arquivo SKILL.md, que traz frontmatter YAML e instruções em Markdown. Opcionalmente, ela carrega subpastas como scripts, references e assets.

O mecanismo que a torna viável chama-se progressive disclosure, ou divulgação progressiva, e opera em três níveis.

Primeiro, apenas o nome e a descrição de cada skill entram no início da sessão, com cerca de cem tokens por skill. Em seguida, o corpo do SKILL.md só carrega quando a descrição corresponde à tarefa.

Por último, os arquivos de referência dentro da pasta abrem apenas quando necessários.

Assim, a divisão de responsabilidade fica clara. O CLAUDE.md guarda fatos permanentes do projeto, como stack, arquitetura e estrutura de pastas.

A skill, por outro lado, guarda o procedimento de uma tarefa específica. O artigo a skill do time .NET para agentes de IA mostra um exemplo real desse formato.

Revisão de código encaixa perfeitamente nessa categoria. Ela acontece em um momento conhecido, segue sempre os mesmos passos e exige uma lista longa de regras que seria peso morto em qualquer outra conversa.

Skill de revisão para código de qualidade com Claude

Esta é a estrutura mínima de uma skill de revisão para .NET:

.claude/skills/dotnet-clean-code-review/
├── SKILL.md
└── references/
    ├── clean-code-rules.md
    └── detection-commands.md

Como a pasta vive dentro do repositório, ela é versionada. Quem clona o projeto recebe a skill, e a revisão de todo o time executa as mesmas verificações.

O frontmatter é o elemento mais crítico do arquivo, porque decide se a skill será carregada.

---
name: dotnet-clean-code-review
description: Reviews C# and .NET code for clean code issues such as
  Manager, Helper and Utils names, region blocks, boolean parameters,
  permissive return types, async method naming,
  single-implementation interfaces and deep inheritance.
  Use when the user asks to "review my changes", "do a code review",
  or wants readability feedback on C# code.
  Do not use for correctness bugs or security flaws.
---

Três características tornam essa descrição eficaz. Ela nomeia as verificações concretas, lista as frases que o usuário realmente digitaria e declara quando não deve ser usada.

O corpo da skill traz as regras de código de qualidade com Claude que o compilador não alcança. Por exemplo, métodos async void, que não podem ser aguardados e cujas exceções podem derrubar o processo.

Também entram tipos de retorno IEnumerable<T> sobre coleções já materializadas, que convidam o chamador a enumerar duas vezes.

Completam a lista os parâmetros booleanos que escondem a intenção, os nomes genéricos sem responsabilidade clara e as interfaces com implementação única criadas só para a injeção de dependência. Os princípios de clean code servem de base para essa lista.

Tornando a revisão auditável

Escrever as regras de código de qualidade com Claude é a parte fácil. Garantir que todas sejam verificadas é o que separa uma skill útil de um texto decorativo.

Pedir a qualquer modelo que revise um diff de quinhentas linhas em busca de clean code produz resultado irregular. Felizmente, três elementos corrigem isso.

Um deles é uma ordem fixa de passos no corpo da skill: definir escopo, executar a detecção, aplicar julgamento e escrever o relatório.

Outro é um comando de busca para cada regra que admita detecção mecânica, o que substitui memória por evidência.

rg -n --glob "*.cs" "\basync\s+void\b"
rg -n --glob "*.cs" "^\s*#region"
rg -n --glob "*.cs" -e "\b(class|record|struct)\s+[A-Za-z0-9_]*(Manager|Helper|Utils)\b"

Por fim, entra um formato de relatório em que toda regra recebe uma linha, tenha encontrado algo ou não. Desse modo, uma regra pulada fica visível imediatamente.

Há ainda uma prática operacional que muda o resultado: execute a revisão em uma sessão separada daquela que escreveu o código.

Afinal, um agente que revisa o próprio trabalho na mesma conversa ainda tem à frente todas as justificativas daquelas escolhas. Ele defende o código em vez de lê-lo.

Revisão em camadas para código de qualidade com Claude

Para código de qualidade com Claude, o build é o portão mais barato, mas não é o último.

Uma sequência razoável começa pela revisão automatizada em uma sessão limpa e passa por ferramentas especializadas de revisão de pull request. Depois, avança para plataformas de qualidade contínua, como SonarQube ou Qodana, e termina na revisão humana.

As plataformas de qualidade contínua importam mais do que parece quando agentes escrevem boa parte do código. Um pull request isolado pode parecer aceitável enquanto duplicação, complexidade e cobertura se deterioram semana após semana.

A revisão humana permanece no fim da cadeia. Contudo, ela passa a tratar arquitetura e regra de negócio, e não formatação ou nomenclatura.

Conclusão: código de qualidade com Claude no .NET

A abordagem descrita aqui não torna o agente um engenheiro melhor. Ela torna os seus padrões verificáveis, e é isso que sustenta código de qualidade com Claude no dia a dia.

A ordem de implementação importa. Comece pelo Directory.Build.props com TreatWarningsAsErrors, porque é um arquivo, leva poucos minutos e muda imediatamente o que o agente entrega.

Em seguida, mova para o .editorconfig tudo o que um arquivo de configuração consegue expressar. Lembre-se de configurar a severidade pelo identificador da regra quando se tratar de nomenclatura.

Depois, adote o Central Package Management para eliminar versões inventadas. Por fim, reserve a skill de revisão para o que nenhuma dessas camadas alcança.

Definir esses padrões sempre foi trabalho seu. A diferença é que agora existe uma forma de fazê-los valer sem repeti-los em toda conversa.

FAQ

Preciso usar o modo All de análise desde o início?

Não necessariamente. Em bases existentes, começar por Recommended costuma ser mais realista, porque All com TreatWarningsAsErrors pode gerar centenas de erros no primeiro build.

Por que minha regra de nomenclatura no .editorconfig não quebra o build?

Porque a severidade dentro de uma dotnet_naming_rule só vale em IDEs. Configure dotnet_diagnostic.IDE1006.severity com warning ou error para verificar no build.

O Directory.Build.props precisa estar na pasta da solução?

Não. O MSBuild sobe a árvore a partir do projeto e para no primeiro arquivo que encontra.

Posso ter mais de um Directory.Build.props no repositório?

Sim, mas o MSBuild importa apenas o primeiro que encontra. Para combinar níveis, importe manualmente o arquivo do diretório superior.

Qual a diferença prática entre CLAUDE.md e uma skill?

O CLAUDE.md carrega em toda sessão e guarda fatos permanentes do projeto. A skill carrega sob demanda e guarda procedimentos específicos.

Analisadores de terceiros vão para a aplicação publicada?

Não, desde que declarados com PrivateAssets e IncludeAssets como no exemplo. Assim, eles ficam restritos ao tempo de build.

Essa abordagem funciona apenas com o Claude?

Não. Compilador, analisadores, .editorconfig e Central Package Management independem do agente, e outros agentes têm mecanismos equivalentes às skills.