Clean Code de verdade: um guia prático para escrever código legível, previsível e fácil de manter
Quando alguém fala em Clean Code, a conversa costuma convergir para o mesmo lugar:
- use nomes melhores;
- faça funções pequenas;
- evite duplicação;
- organize o código.
Tudo certo. O problema é que quase sempre a conversa para aí.
E o dev continua com as mesmas dúvidas de sempre:
- quantas linhas um arquivo pode ter antes de começar a ficar ruim?
- quando uma função está grande demais?
- quando uma classe virou um problema?
- quando extrair um helper ajuda, e quando só espalha a complexidade?
- como saber se estou escrevendo código limpo ou só código "bonitinho"?
Clean Code só fica realmente útil quando sai da filosofia e vira critério operacional — quando você consegue olhar para um arquivo e responder com clareza se ele está saudável, no limite ou estruturalmente problemático.
Esse texto é um guia prático sobre exatamente isso.
O que é código limpo, de forma direta
Código limpo não é código curto, nem sofisticado, nem cheio de padrões de projeto.
Código limpo é código fácil de entender, fácil de modificar e difícil de interpretar errado.
Isso muda a forma de avaliar qualidade. O critério deixa de ser:
"esse código ficou elegante?"
E passa a ser:
"esse código continua claro depois da próxima alteração?"
Se a resposta for não, ele já começou a perder qualidade.
O primeiro sinal de qualidade: entender o que o código faz sem ler tudo
Quando você abre um arquivo, deveria perceber rapidamente:
- qual é a responsabilidade principal dele;
- quais dependências ele usa;
- onde está o fluxo principal;
- o que ele expõe para o resto do sistema.
Se você precisa ler o arquivo inteiro para descobrir isso, alguma coisa já está errada.
Um arquivo bem escrito tem um centro de gravidade claro: um caso de uso, um serviço, um componente, uma regra de negócio, um adaptador de integração. Quando ele começa a concentrar muitas intenções ao mesmo tempo, perde identidade — e começa a acumular problemas silenciosamente.
Nome não serve para identificar. Serve para explicar.
Esse é um dos pontos mais subvalorizados do Clean Code.
Muita gente ainda nomeia coisas só para "dar um nome", quando o nome deveria comunicar:
- o que aquilo é;
- o que aquilo faz;
- em que contexto aquilo existe.
Exemplo ruim:
const data = getData(items)
const result = process(data)
Exemplo melhor:
const normalizedOrderItems = normalizeOrderItems(items)
const orderTotal = calculateOrderTotal(normalizedOrderItems)
No segundo caso, o leitor não precisa adivinhar quase nada. O nome carrega contexto.
Algumas boas práticas que funcionam consistentemente:
- variáveis: nomes com significado real, não genéricos;
- funções: verbo + contexto (
calculateInvoiceTotal, nãoprocess); - classes: papel ou conceito claro (
PaymentGateway, nãoManager); - booleanos: nomes que leem como verdade (
isSubscriptionActive).
Nomes como data, info, value, handle e manager são armadilhas. Se o nome é genérico, o entendimento vira esforço manual do leitor.
Função boa não é a menor. É a mais clara.
A discussão sobre "função pequena" fica simplista quando foca só no tamanho.
O problema não é uma função ter 25 linhas em vez de 12. O problema é quando ela começa a misturar responsabilidades: validação, regra de negócio, persistência, transformação, logging, tratamento de erro — tudo junto.
Quando isso acontece, ela deixa de ter uma responsabilidade principal.
Uma boa forma de testar: você consegue resumir o que a função faz em uma frase simples?
Se a resposta for algo como:
"essa função valida, transforma, persiste e ainda monta a resposta"
então ela provavelmente está fazendo demais.
Faixas de referência para funções
| Métrica | Ideal | Tolerável | Máximo | Crítico |
|---|---|---|---|---|
| Linhas por função | 5–20 | 21–35 | 36–50 | >50 |
| Parâmetros | 0–2 | 3 | 4 | 5+ |
Isso não é lei. É uma forma de detectar que a função talvez esteja sobrecarregada.
Arquivo grande nem sempre é o problema. Arquivo sem foco é.
Nem todo arquivo com muitas linhas é ruim. Existem exceções legítimas: arquivos declarativos, schemas, migrations, tabelas estáticas, código gerado.
Mas na maioria dos casos, arquivo grande é sintoma de acúmulo de responsabilidade. Quando um arquivo cresce além do esperado, vale investigar:
- está fazendo coisas demais?
- virou depósito de helpers?
- mistura domínio com infraestrutura?
- está exportando coisas demais para coisas demais?
Faixas de referência para arquivos
| Métrica | Ideal | Tolerável | Máximo | Crítico |
|---|---|---|---|---|
| Linhas por arquivo | até 150 | 151–250 | 251–400 | >400 |
| Caracteres por linha | até 88 | 89–100 | 101–120 | >120 |
O problema quase nunca é o número isolado. O problema é o que esse número costuma revelar.
Imports dizem muito sobre a saúde do arquivo
A lista de imports é uma das formas mais rápidas de sentir se um arquivo está coeso.
Quando um arquivo importa dependências demais, pode ser sinal de acoplamento excessivo, mistura de camadas ou responsabilidade inflada.
Uma organização simples e eficiente:
- bibliotecas nativas ou padrão da linguagem;
- dependências externas;
- infraestrutura compartilhada do projeto;
- módulos internos de domínio e aplicação;
- tipos e contratos;
- dependências locais.
| Métrica | Ideal | Tolerável | Máximo | Crítico |
|---|---|---|---|---|
| Imports por arquivo | 3–12 | 13–20 | 21–30 | >30 |
Se um arquivo precisa importar muita coisa, talvez esteja tentando ser sistema demais ao mesmo tempo.
Classe grande raramente é sinal de maturidade
Classe enorme costuma passar uma falsa impressão de sofisticação. Na prática, quase sempre ela só acumulou estado demais, métodos demais, dependências demais e regras demais.
Quando uma classe cresce além do razoável, a melhor saída geralmente não é "organizar melhor por dentro" — é dividir papéis.
Faixas de referência para classes
| Métrica | Ideal | Tolerável | Máximo | Crítico |
|---|---|---|---|---|
| Linhas por classe | até 120 | 121–200 | 201–300 | >300 |
| Métodos por classe | 1–7 | 8–12 | 13–20 | >20 |
| Propriedades por classe | até 5 | 6–8 | 9–12 | >12 |
Side effect escondido é um dos piores problemas de legibilidade
Função que parece só calcular, mas por dentro grava em banco, chama API, publica evento ou altera cache — vira uma armadilha silenciosa.
Exemplo ruim:
function buildCustomerSummary(customerId: string) {
const customer = repository.save(loadCustomer(customerId))
audit.log(customer)
return formatCustomerSummary(customer)
}
O nome sugere montagem de um resumo. Mas a função também persiste e registra auditoria. Quem chama esse código não espera isso — e esse é exatamente o problema.
Código limpo exige que o leitor não seja surpreendido pelo efeito colateral.
Aninhamento profundo aumenta o custo mental
Quanto mais if, else, try, for e switch encaixados uns dentro dos outros, maior o esforço para entender o fluxo principal.
| Métrica | Ideal | Tolerável | Máximo | Crítico |
|---|---|---|---|---|
| Níveis de aninhamento | 0–2 | 3 | 4 | 5+ |
Quando o aninhamento cresce, vale usar guard clauses, retornos antecipados, extração de decisão ou tabelas de decisão. O objetivo é simples: deixar o caminho principal visível sem precisar percorrer mentalmente todas as ramificações.
Nem toda repetição é ruim
Esse é um ponto onde muita gente exagera na aplicação do DRY.
Repetição pequena de sintaxe quase nunca merece abstração. O que realmente prejudica é repetir regra de negócio, validação importante, transformação semântica ou decisão crítica.
Em outras palavras: não tente eliminar qualquer repetição. Tente eliminar repetição de significado.
Às vezes duas linhas parecidas em lugares diferentes são melhores do que uma abstração genérica que ninguém entende — e que vira um ponto único de falha escondido.
Comentário bom explica contexto. Comentário ruim narra o óbvio.
Comentário não é inimigo. Comentário ruim é que é problema.
Comentário útil:
- explica uma limitação externa;
- documenta uma decisão incomum;
- registra um workaround;
- justifica uma escolha não evidente.
Comentário ruim:
- narra o que o código já mostra;
- compensa um nome ruim;
- tenta salvar uma estrutura problemática.
Exemplo ruim:
// incrementa o contador em 1
count += 1
Exemplo de uso legítimo:
// This provider may return duplicated events during retry windows.
// We deduplicate here to keep the downstream workflow idempotent.
Contrato previsível é parte de código limpo
Uma função, classe ou módulo precisa ser previsível no que recebe e no que devolve.
Problemas comuns de contrato imprevisível:
- ora retorna objeto, ora
null; - ora lança exceção, ora retorna
false; - muda comportamento sem deixar isso explícito.
Código limpo pede entrada clara, saída clara, nulidade controlada e consistência de retorno. Se o contrato é imprevisível, o código pode até funcionar — mas vai ser difícil de confiar.
Clareza vale mais do que esperteza
Tem código que impressiona na primeira leitura, mas cansa em todas as seguintes: encadeamento excessivo, abstração prematura, helpers genéricos demais, truques sintáticos, lógica comprimida além da conta.
Código limpo não é competição de elegância. Se o leitor precisa parar para decifrar a "inteligência" da solução, a clareza foi sacrificada — e isso tem custo real toda vez que alguém precisar alterar aquele trecho.
Um jeito prático de medir a saúde de um código
Se você quiser sair da subjetividade, esta tabela serve como referência operacional:
| Métrica | Ideal | Tolerável | Máximo | Crítico |
|---|---|---|---|---|
| Linhas por arquivo | até 150 | 151–250 | 251–400 | >400 |
| Caracteres por linha | até 88 | 89–100 | 101–120 | >120 |
| Imports por arquivo | 3–12 | 13–20 | 21–30 | >30 |
| Funções por arquivo | 1–7 | 8–12 | 13–20 | >20 |
| Linhas por função | 5–20 | 21–35 | 36–50 | >50 |
| Parâmetros por função | 0–2 | 3 | 4 | 5+ |
| Aninhamento | 0–2 | 3 | 4 | 5+ |
| Métodos por classe | 1–7 | 8–12 | 13–20 | >20 |
Isso não substitui julgamento. Mas evita que qualidade vire só debate subjetivo.
Um checklist mental para avaliar um trecho de código
Quando quero saber se um trecho está limpo, costumo passar por estas perguntas:
- o nome explica ou só identifica?
- esse arquivo tem uma responsabilidade central?
- essa função faz uma coisa ou várias?
- os imports fazem sentido para a camada?
- o efeito colateral está explícito?
- o contrato é previsível?
- o fluxo principal aparece rápido?
- a última alteração deixou o trecho mais claro ou mais confuso?
- alguém novo no projeto entenderia isso sem esforço desnecessário?
Se várias respostas forem "não", o problema já está instalado.
Conclusão
Clean Code só fica realmente útil quando deixa de ser discurso genérico e vira prática observável.
No fim, código limpo não é o que parece bonito no print. É o que continua compreensível quando outra pessoa pega, quando você revisita meses depois, quando o projeto cresce, quando a regra muda ou quando o sistema precisa ser refatorado sob pressão.
Então, mais do que perguntar "isso está clean?", talvez valha perguntar:
"isso está claro, previsível e fácil de evoluir?"
Se sim, você está no caminho certo. Se não, provavelmente não falta opinião — falta critério.
E você? Tem algum indicador ou heurística que usa no dia a dia para avaliar a saúde de um código antes de mexer nele? Troca nos comentários.