Vamos ser sinceros: a maioria de nós tem uma relação de amor e ódio com code reviews. Em um dia bom, são uma colaboração genial que pega bugs e compartilha conhecimento. Em um dia ruim, parecem um gargalo burocrático onde opiniões pessoais e picuinhas de estilo travam todo o progresso.
A diferença não é mágica. É uma questão de ter um sistema. Um bom code review c# não é só sobre achar erros, é um multiplicador de força para a qualidade, consistência e velocidade do seu time. É a atividade de maior impacto que você pode fazer para melhorar sua codebase, exceto por uma reescrita completa (e ninguém quer isso).
Então, como ter mais dias bons?
Começa concordando sobre o que estamos procurando e como falamos sobre isso.
O Contrato Social de um Bom PR
Antes mesmo de entrar no código, vamos falar do fator humano. Um pull request é uma conversa, e toda boa conversa tem algumas regras básicas. Sem elas, vira caos.
Para o Autor: Facilite a Vida do seu Revisor
Você não pediria para alguém revisar um livro sem contar a história. Não jogue um PR de 50 arquivos para o seu time com o título “bug fixes” e espere uma revisão de alta qualidade.
Escreva uma boa descrição para o PR.
Qual é o “porquê” por trás da mudança? Que problema você está resolvendo? Link para o ticket. Inclua screenshots de mudanças na UI. Se você tomou uma decisão de arquitetura específica, explique seu raciocínio. Dê ao revisor o contexto necessário para entender suas escolhas.
Revise seu próprio código primeiro.
Sério. Leia suas próprias mudanças, arquivo por arquivo. Você vai se surpreender com a quantidade de erros de digitação, console.log
s esquecidos e erros lógicos simples que você mesmo pega. Isso demonstra respeito pelo tempo de quem está revisando.
Mantenha o PR pequeno.
O maior inimigo de uma boa revisão é um PR gigante. É impossível revisar 2.000 linhas de código direito. O revisor fica cansado, passa o olho por cima e só carimba um “LGTM”. Busque fazer mudanças atômicas que resolvam um único problema.
Para o Revisor: Seja um Mentor, Não um Policial
Seu trabalho não é punir o autor pelos erros. É ajudá-lo e ao time a entregar um código melhor. Sua mentalidade é tudo.
– Faça perguntas, não exigências
Em vez de “Mude isso para um dictionary”, tente “Fiquei curioso sobre o uso de uma list aqui. Um dictionary nos daria buscas mais rápidas se essa coleção crescer?”. Isso abre um diálogo em vez de encerrá-lo.
– Revise o código, não o programador
Mantenha o feedback focado na implementação técnica. “Este método está ficando um pouco longo” é um ótimo feedback. “Por que você sempre escreve métodos longos?” não é.
– Ofereça soluções, ou pelo menos aponte a direção certa
Se encontrar um problema, sugira uma forma melhor. “Isso pode levar a uma query N+1” é bom. “Isso pode levar a uma query N+1; talvez possamos usar Include()
aqui para carregar os dados relacionados com eager-load?” é ainda melhor.
– Automatize as coisas pequenas
Não perca tempo e boa vontade com picuinhas de estilo, como posicionamento de chaves ou espaçamento. Isso é trabalho para uma máquina. Use um formatador de código e um linter. Falaremos mais sobre isso adiante.
O que realmente avaliar em um PR
Ok, o PR é pequeno e o contexto está claro. Estamos todos sendo legais uns com os outros. E agora? Quando você está olhando para o diff, onde deve focar sua atenção? Eu divido isso em algumas áreas principais.
Clareza e Manutenibilidade
Esta é a base. Se você não consegue entender o código, não pode verificar se ele está correto ou se tem boa performance. O “Você do Futuro” vai te agradecer por ser rigoroso aqui.
A intenção está clara? – Você consegue entender o que um método faz apenas pelo nome e assinatura? Os nomes das variáveis são descritivos (customer
vs. c
)? O C# nos dá tantas ferramentas de expressão, records, pattern matching, LINQ, estamos usando-as para tornar o código mais simples?
É consistente? – O código segue os padrões e convenções estabelecidos no projeto? Consistência é uma feature. Torna o codebase previsível e mais fácil de navegar.
Os comentários explicam o porquê, e não o quê? – Um comentário como // Incrementa i
é inútil. Um comentário como // Temos que iterar de trás para frente aqui porque estamos removendo itens da coleção
é ouro.
Arquitetura e Design
Aqui saímos do “linha a linha” e olhamos para o todo. Essas mudanças nos preparam para o sucesso futuro ou nos deixam encurralados?
É testável? – Isso é o canário na mina de carvão para um bom design. Se o código é difícil de testar, provavelmente está muito acoplado. Estamos usando injeção de dependência para fornecer as dependências em vez de dar new
nelas dentro da classe? Conseguimos mockar os serviços externos com os quais este código conversa?
Segue os princípios SOLID (com bom senso)? – Uma classe não deveria ter dez responsabilidades diferentes (Single Responsibility). Você deveria poder estender o comportamento sem modificar o código existente (Open/Closed). Não precisamos ser dogmáticos, mas esses princípios são guias fantásticos para construir software flexível.
Estamos introduzindo novas dependências com sabedoria? – Adicionar um novo pacote NuGet é fácil. Mas cada um aumenta nossa árvore de dependências, potenciais vulnerabilidades de segurança e o custo de manutenção. Esse novo pacote é necessário? É bem mantido?
Performance e Gerenciamento de Recursos
Em C#, é surpreendentemente fácil escrever um código que funciona, mas que no fundo é lento ou vaza recursos. Esses problemas costumam ser invisíveis até o tráfego de produção chegar.
Cuidado com as armadilhas do async/await
. – Existe algum async void
? É quase sempre uma má ideia fora de event handlers. Estamos usando ConfigureAwait(false)
em código de biblioteca quando apropriado? Estamos acidentalmente criando um deadlock ao misturar chamadas blocantes (.Result
, .Wait()
) com código async?
O descarte de recursos é crucial – Qualquer coisa que implementa IDisposable
(conexões de banco de dados, file streams, HttpClient
) deve ser descartada. A declaração using
(ou a declaração using
do C# 8+) é sua melhor amiga. Um using
faltando é um vazamento de recurso garantido.
Manuseio eficiente de dados – Existe uma query N+1 escondida em um loop que está chamando uma navigation property do Entity Framework Core? Estamos chamando .ToList()
ou .ToArray()
prematuramente, puxando muito mais dados para a memória do que o necessário? Estamos usando a estrutura de dados certa para a tarefa (ex: HashSet<T>
para buscas rápidas)?
Code review em C# focado em segurança e robustez
Aqui o objetivo é construir um código resiliente que aguenta inputs estranhos e falhas inesperadas.
Nunca confie na entrada de dados – Estamos validando os dados que vêm do mundo externo (requests de API, formulários de usuário)? Estamos usando queries parametrizadas para prevenir SQL injection, ou estamos concatenando strings para montar o SQL? (Por favor, diga que é a primeira opção).
Lide com nulos de forma elegante – Com nullable reference types ativados (e você deveria ativá-los), o compilador ajuda muito. Mas ainda precisamos ficar atentos. O que acontece se um método principal retorna null
? O código que o chama lida com isso, ou ele quebra com uma NullReferenceException
?
Tratamento de erros e logging – O código está simplesmente engolindo exceções com um bloco catch
vazio? Isso é uma bomba-relógio. Devemos tratar as exceções das quais podemos nos recuperar e deixar as não tratadas subirem para um handler global. Estamos registrando informações suficientes para diagnosticar um problema em produção?
O Checklist de Code Review C#
Aqui está um checklist prático e sem enrolação. Você não precisa marcar cada item em todo PR, mas use-o como um guia para estimular seu raciocínio.
✅ Legibilidade & Estilo
- O código é fácil de entender à primeira vista?
- Os nomes de variáveis, métodos e classes são claros e sem ambiguidades?
- Segue as convenções de código existentes do projeto?
- Os comentários explicam o porquê algo é feito, não o quê é feito?
✅ Arquitetura & Design
- Essa mudança pertence a esta classe/projeto? (Single Responsibility)
- O código é testável? As dependências são injetadas?
- Está super-engenheirado? Existe uma solução mais simples?
- Introduz novas dependências de terceiros? Se sim, elas se justificam?
✅ Performance & Gerenciamento de Recursos
- O
async/await
está sendo usado corretamente (semasync void
, sem.Result
em tasks)? - Objetos
IDisposable
são descartados corretamente com declaraçõesusing
? - Existe potencial para uma query N+1 ou busca ineficiente de dados (ex:
.ToList()
prematuro)? - As estruturas de dados apropriadas estão sendo usadas para a tarefa (ex:
List
vs.Dictionary
vs.HashSet
)?
✅ Segurança
- Toda entrada externa é validada e sanitizada?
- Queries parametrizadas são usadas em todo acesso ao banco de dados para prevenir SQLi?
- Dados sensíveis (senhas, chaves de API) são manuseados de forma segura (ex: não são logados em texto puro)?
✅ Robustez & Tratamento de Erros
- Valores de retorno ou argumentos
null
são tratados corretamente? (Especialmente importante se não estiver usando nullable reference types). - As exceções são tratadas apropriadamente? São capturadas de forma muito genérica ou silenciadas?
- Existe logging suficiente para diagnosticar problemas em produção?
- Casos extremos (edge cases) são considerados (ex: coleções vazias, valores zero, falhas de chamadas de rede)?
✅ Testes
- Existem testes para a nova lógica?
- Os testes cobrem o “caminho feliz” assim como os edge cases e cenários de falha?
- Os testes são fáceis de ler e entender?
Fomentar uma cultura de code review de alta qualidade é um dos melhores investimentos que um time de desenvolvimento pode fazer. Não se trata de adicionar atrito; trata-se de construir um entendimento compartilhado do que é um código “bom” e ajudar uns aos outros a chegar lá. Isso torna o código melhor, e também torna cada dev do time melhor.