Pular para o conteúdo
10 min de leitura

Clean Code na prática: escrevendo código que a próxima pessoa consegue ler

Por Equipe Tech do Sonne ·

Nomes, funções, comentários e níveis de abstração: um guia prático de clean code para escrever código legível, testável e barato de manter no longo prazo.

Neste artigo

Código é lido muito mais do que é escrito#

A estatística mais citada sobre manutenção de software resume o ofício: passamos a maior parte do tempo lendo código, não escrevendo. Cada linha que você digita hoje será lida dezenas de vezes nos próximos anos — por você mesmo em uma madrugada de plantão, por um colega que entrou na equipe semana passada, por quem herdar o sistema quando você tiver mudado de time. Escrever para essa audiência futura é o núcleo do que se convencionou chamar de clean code.

O termo carrega uma bagagem quase religiosa em alguns círculos, e vale começar desarmando o mal-entendido: clean code não é sobre elegância estética nem sobre seguir um catálogo de regras à risca. É sobre reduzir o custo cognitivo de entender o que o programa faz. Um trecho está limpo quando alguém competente, sem contexto prévio, consegue prever seu comportamento apenas lendo-o. Quando isso não acontece, o código está sujo, por mais que passe nos testes e rode em produção.

Este artigo percorre as decisões diárias que mais afetam essa legibilidade: nomes, funções, comentários, tratamento de erro e a disciplina de manter um único nível de abstração por vez. Nenhuma delas é sofisticada. Todas são fáceis de ignorar sob pressão de prazo, e é justamente por isso que compõem a diferença entre uma base de código que envelhece bem e outra que apodrece.

Nomes: a documentação que nunca fica desatualizada#

Nomear é a habilidade mais subestimada da programação. Um bom nome elimina a necessidade de comentário, de leitura do corpo da função e, muitas vezes, de perguntar ao autor. Um nome ruim mente, confunde e obriga o leitor a manter contexto extra na cabeça.

A regra prática mais útil é: o nome deve revelar a intenção, não a implementação. d não diz nada. dias diz um pouco. diasDesdeUltimoLogin diz tudo que o leitor precisa, e o corpo da função vira apenas confirmação.

```javascript // Ruim: o leitor precisa decifrar function calc(l, t) { return l.filter(x => x.s === 1 && x.t > t); }

// Bom: o nome carrega a intenção function pedidosAtivosApos(pedidos, dataCorte) { return pedidos.filter( pedido => pedido.status === STATUS_ATIVO && pedido.criadoEm > dataCorte ); } ```

Alguns princípios que sustentam bons nomes na prática:

  • Comprimento proporcional ao escopo. Um índice de loop de três linhas pode ser i. Uma variável que vive por trinta linhas e cruza várias condições merece um nome descritivo. Quanto maior a distância entre a declaração e o uso, mais o nome precisa se defender sozinho.
  • Evite desinformação. Não chame de lista algo que é um Set. Não use contas para uma variável que guarda uma única conta. O plural mente, e mentiras pequenas se acumulam.
  • Faça distinções significativas. dado, dados, info, dadosInfo no mesmo módulo não distinguem nada — são ruído. Se você precisa de dois nomes parecidos, provavelmente precisa entender melhor a diferença conceitual entre as duas coisas.
  • Use o vocabulário do domínio. Se o negócio fala em "apólice", "sinistro" e "prêmio", o código deve falar a mesma língua. Traduzir termos do domínio para abstrações genéricas (objeto, item, registro) apaga a informação mais valiosa que o código poderia carregar.

O teste final de um nome é a pronúncia mental: se você consegue explicar em voz alta o que a variável guarda usando exatamente o nome dela, ele está bom. Se precisa dizer "isso aqui é na verdade...", refatore.

Funções pequenas que fazem uma coisa#

A segunda alavanca de legibilidade são as funções. A orientação clássica — funções devem ser pequenas, e depois menores ainda — soa exagerada até você conviver com uma função de trezentas linhas que ninguém ousa tocar. O tamanho não é o objetivo em si; é um efeito colateral de duas propriedades mais fundamentais: responsabilidade única e nível de abstração consistente.

Uma função faz uma coisa quando você não consegue extrair outra função com um nome que não seja apenas uma repetição da anterior. Se dá para tirar validarEntrada, calcularImposto e formatarRecibo de dentro de processarPedido, então processarPedido estava fazendo quatro coisas, não uma.

O sintoma mais comum de função sobrecarregada é a mistura de níveis de abstração. Observe:

``python def gerar_relatorio(usuario): dados = buscar_dados(usuario) # abstração alta total = 0 for linha in dados: # abstração baixa if linha["tipo"] == "credito": total += linha["valor"] else: total -= linha["valor"] enviar_email(usuario, formatar(total)) # abstração alta ``

O laço no meio opera em um nível de detalhe muito mais baixo que as chamadas ao redor. O leitor precisa descer ao chão de fábrica no meio de uma narrativa de alto nível. Extrair o cálculo resolve:

```python def gerar_relatorio(usuario): dados = buscar_dados(usuario) total = calcular_saldo(dados) enviar_email(usuario, formatar(total))

def calcular_saldo(linhas): return sum( linha["valor"] if linha["tipo"] == "credito" else -linha["valor"] for linha in linhas ) ```

Agora gerar_relatorio se lê como um resumo: busca, calcula, envia. Quem quiser saber como o saldo é calculado desce um nível. Quem só quer entender o fluxo geral não precisa. Essa é a essência de manter um único nível de abstração por função — cada uma conta uma história completa na sua altura, e delega os detalhes para funções nomeadas abaixo.

Um cuidado com o número de parâmetros: funções com muitos argumentos são difíceis de chamar e de testar, porque a quantidade de combinações explode. Três já é motivo para desconfiar. Acima disso, quase sempre há um objeto de valor implícito esperando para nascer — um IntervaloDeDatas no lugar de inicio, fim, um Endereco no lugar de cinco strings soltas.

Comentários: o último recurso, não o primeiro#

Existe um mal-entendido persistente de que código bem documentado é código cheio de comentários. Na prática, a maioria dos comentários é uma tentativa de compensar código que não se explica — e o remédio quase sempre é melhorar o código, não adicionar prosa ao redor dele.

Comentários têm um defeito estrutural: eles não são executados, então nada garante que continuem verdadeiros. Toda mudança no código é uma oportunidade para o comentário adjacente virar mentira. Um comentário desatualizado é pior que nenhum, porque desinforma com aparência de autoridade.

A regra é reservar comentários para aquilo que o código não consegue dizer:

  • O porquê, não o quê. // incrementa i é ruído. // pulamos o primeiro registro porque o CSV do fornecedor sempre traz um cabeçalho sem marcação é ouro — explica uma decisão que o código sozinho jamais revelaria.
  • Avisos e armadilhas. "Não paralelize este laço: a API do parceiro serializa por sessão e responde 429." Isso protege o próximo desenvolvedor de um erro caro.
  • Contexto externo. Um link para o ticket, para a especificação, para o bug do fornecedor que motivou o workaround.

Fora disso, prefira código autoexplicativo. Se você sente vontade de escrever um comentário explicando o que um bloco faz, extraia o bloco para uma função cujo nome seja aquele comentário. A função nomeada é um comentário que o compilador verifica.

Tratamento de erro que não engole o problema#

Código limpo é honesto sobre o que pode dar errado. O anti-padrão mais destrutivo aqui é o erro silenciado — o catch vazio, o except: pass, o valor de retorno ignorado. Ele transforma uma falha ruidosa e rastreável em um bug fantasma que só aparece três camadas adiante, sem pista de origem.

```javascript // Crime: o erro desaparece e o programa segue com estado inválido try { saldo = await buscarSaldo(conta); } catch (e) {}

// Honesto: falha explícita, com contexto try { saldo = await buscarSaldo(conta); } catch (erro) { throw new Error(falha ao buscar saldo da conta ${conta.id}, { cause: erro }); } ```

Trate o caminho de erro como parte do design, não como um apêndice. Separe o fluxo feliz do tratamento de falha para que a lógica principal permaneça legível, e prefira lançar exceções ou retornar tipos de resultado explícitos a devolver null e códigos mágicos que o chamador é obrigado a lembrar de checar. Um erro que se propaga com contexto é depurável em minutos; um erro engolido custa dias.

Um nível de abstração por vez, contando uma história#

Vale insistir num ponto que costura nomes e funções: dentro de qualquer bloco de código, mantenha um único nível de abstração. O leitor deveria conseguir ler uma função de cima a baixo como uma narrativa coerente, em que cada passo está na mesma altura conceitual dos demais. Quando um trecho mistura "validar o pedido" (alto nível) com "iterar caractere por caractere verificando se é dígito" (baixo nível), o leitor é obrigado a subir e descer uma escada mental a cada linha, e o esforço de acompanhar cresce desproporcionalmente.

Uma heurística prática ajuda a manter essa disciplina: se você consegue descrever o que uma função faz sem usar a palavra "e", ela provavelmente faz uma coisa só. "Valida a entrada e calcula o imposto e formata o recibo" denuncia três responsabilidades. "Processa o pedido" — cujo corpo apenas orquestra três chamadas nomeadas — descreve uma única responsabilidade de coordenação, com os detalhes delegados um nível abaixo. Essa estrutura em camadas, em que cada função conta uma história completa na sua altura e delega os detalhes para funções nomeadas logo abaixo, é o que permite que alguém entenda o fluxo geral sem mergulhar em cada detalhe, e mergulhe apenas onde precisa.

O ganho não é estético. Código organizado por níveis de abstração é código onde você consegue responder "o que este módulo faz?" lendo só o topo, e "como ele faz X?" descendo só no ramo relevante. É a diferença entre um livro com sumário e capítulos e um livro que é um único parágrafo de cem páginas.

A regra do escoteiro e a dívida que não aparece no balanço#

Nenhuma dessas práticas exige um grande refactor de fim de semana. Clean code é construído em incrementos: a regra do escoteiro — deixe o acampamento mais limpo do que encontrou — aplicada a cada commit. Renomeie a variável confusa que você teve que decifrar. Extraia a função que você acabou de entender. Adicione o comentário de porquê que faltava. Cada melhoria é pequena; o efeito composto ao longo de meses é uma base de código que fica mais fácil de mudar em vez de mais difícil.

O inimigo silencioso é a dívida técnica que não aparece em nenhum relatório: a fricção acumulada de mil pequenas confusões não resolvidas. Ela não quebra nada de imediato. Só torna cada mudança um pouco mais lenta, cada estimativa um pouco mais imprecisa, cada novo integrante um pouco mais perdido — até que a equipe inteira passa a ter medo de tocar em certos arquivos.

Vale lembrar também que clean code e desempenho não são inimigos. A crença de que código legível é necessariamente lento raramente se sustenta: a esmagadora maioria das funções nunca é gargalo, e otimizar antes de medir troca clareza garantida por ganho imaginário. Escreva claro primeiro; meça; otimize o punhado de pontos quentes que os dados apontarem, e comente por que aquele trecho específico foge do padrão.

Por fim, clean code não termina no arquivo isolado — ele se materializa também no ritual coletivo de revisão. Um bom processo de code review é onde a equipe negocia e mantém esses padrões vivos, transformando preferências individuais em convenções compartilhadas. Nomes, funções e tratamento de erro são decisões de design; e as melhores decisões de design são aquelas que a próxima pessoa consegue entender sem precisar perguntar nada a você.

Leituras relacionadas

Nenhum comentário ainda

Seja o primeiro a comentar.

Deixe seu comentário

Entre com sua conta Canverly para comentar. Você pode usar a mesma conta em qualquer site da rede.

Entrar com Canverly