Home / Desenvolvimento / O Clean Code que destruiu a produtividade do seu time

O Clean Code que destruiu a produtividade do seu time

Capa cinematográfica: desenvolvedor frustrado com a cabeça apoiada na mão em frente ao monitor, livro Clean Code aberto na mesa, planta murcha, atmosfera noir em tons teal e âmbar.

Refatorar 40 linhas em 14 funções pode dobrar tempo de onboarding e travar o time. O dogma do Clean Code tem ressalvas que checklists apagaram.

A história da review com o júnior

Recebi um PR de um engenheiro júnior. Quarenta linhas de regra de negócio, legíveis, com três ramificações, dois comentários honestos sobre “caso especial” e um teste cobrindo o caminho feliz.

Devolvi pedindo refatoração. “Uma função por responsabilidade, por favor.” O júnior voltou três dias depois com 14 funções espalhadas por 3 arquivos, 2 classes auxiliares, 1 interface que ainda não tinha implementação, e uma pasta utils/ nova. A complexidade ciclomática caiu. As linhas de cobertura, não. O tempo de onboarding do próximo dev, que até ali era “abre o arquivo e lê 40 linhas”, virou “abre 3 arquivos, segue 14 chamadas e descobre qual tem a regra de negócio de verdade”.

Foi nesse momento que parei de tratar Clean Code como checklist.

O que Clean Code defende de verdade (e o que virou dogma)

O capítulo 3 do Clean Code, do Uncle Bob, tem ressalvas. Ele diz: “funções devem ser curtas” e em seguida exemplifica limites com código real, mostrando que a regra é heurística, não mandamento. A frase de cabeçalho virou meme. As ressalvas não viralizaram.

O que pegou foi o checklist: “função menor que 20 linhas, uma responsabilidade por função, máximo de três parâmetros, zero efeitos colaterais”. Aplicado de forma religiosa, o checklist transforma código simples em arquitetura que parece mais sólida que o problema que ela resolve.

Defender o livro aqui seria injusto. O texto tem nuance que a cultura engoliu inteira e digeriu mal.

O caso: antes e depois lado a lado

A tabela que abro abaixo descreve exatamente o PR que recebi. Os números de tempo de onboarding são estimativas com três devs do time, cronometrados informalmente, mas o padrão é consistente: dobrou.

MétricaAntes (1 função, 40 linhas)Depois (14 funções, 3 arquivos)
Linhas totais de código40112 (com boilerplate)
Complexidade ciclomática média62
Arquivos a abrir pra entender o fluxo13
Funções chamadas pra executar o caso feliz16 indiretas
Tempo estimado de onboarding (3 devs)~5 min~12 min
Cobertura de teste1 caminho feliz1 caminho feliz, 2 ramos extras

Olha o que aconteceu. A métrica de “qualidade interna” melhorou. A métrica de “velocidade de entender o sistema” piorou. E nenhuma ferramenta automática avisa a segunda, porque ela não cabe em número de classe.

Voltei a abrir o chat com ele três meses depois. O sistema tinha virado monolito de utilidades. Cada novo caso de uso ia pra um arquivo novo com sufixo _service, _handler ou _processor. A pasta utils/ que ele criou no PR original tinha crescido pra 40 arquivos. O lead time do time tinha subido de 2 dias pra 9. E ninguém queria mexer no módulo porque “é cheio de regras escondidas”.

O que começou como 40 linhas honestas virou um sistema que precisa de diagrama pra ser explicado. Isso é dívida técnica de verdade. Não a que o SonarQube aponta. A que você sente quando tenta entender o PR da feature seguinte.

A citação que não viralizou

O capítulo 3 do Clean Code abre com a frase que virou meme — “a primeira regra de funções é que elas devem ser pequenas. A segunda é que elas devem ser menores que isso” — e em seguida dedica os parágrafos seguintes a qualificar. Uncle Bob argumenta que funções longas são sintoma de código mal estruturado, mas também que “a função ideal tem entre 4 e 20 linhas, embora funções de 100 linhas em código puramente declarativo não sejam absurdas”. A frase de 4 a 20 linhas virou cartão de visita. A ressalva de 100 linhas em código declarativo foi pro lixo da cultura.

Não é má-fé de quem leu. É o modo como a Internet funciona: manchete vende, contexto não. Você cita o número, ganha retweet. Você cita a ressalva, perde audiência. Depois de dez anos, sobra só a manchete.

Métrica alternativa: dor de cabeça

Se você só mede coesão, acoplamento, complexidade ciclomática, lines of code por função e número de métodos por classe, você está medindo a facilidade de mexer no código. Não a facilidade de entregar com o código.

As métricas que pagam a conta são outras:

  • Lead time — do commit ao deploy em produção.
  • PR-to-merge — quanto tempo o PR fica aberto esperando review ou retrabalho.
  • Taxa de bugs pós-deploy — especialmente bugs recorrentes na mesma região do código.
  • Tempo de onboarding — quanto um dev novo leva pra fazer o primeiro PR com confiança no módulo.

Coesão e acoplamento são diagnóstico, não meta. Eles te dizem onde mexer quando o lead time explode. Não te dizem que mexer.

Código que vale: quando quebrar a regra é a coisa certa

Um exemplo real, simplificado, em Python. O lado “limpo” aplica o checklist de forma religiosa. O lado “feio” faz a mesma coisa em metade do espaço, sem abstração vazia.

O lado “limpo” (40 linhas, 6 funções)

class OrderValidator:
    def __init__(self, rules): self.rules = rules
    def validate(self, order): return self._check_all(order, self.rules)
    def _check_all(self, order, rules):
        for rule in rules:
            if not self._apply_rule(order, rule): return False, rule.name
        return True, None
    def _apply_rule(self, order, rule): return rule.matches(order)
    def _log_failure(self, rule_name, order):
        logger.warning(f"Order {order.id} failed rule {rule_name}")
        return False

def process_order(order, validator, repo, notifier):
    ok, failed = validator.validate(order)
    if not ok: return validator._log_failure(failed, order)
    repo.save(order)
    notifier.send_confirmation(order)
    return True

O lado “feio” (12 linhas, 1 função)

def process_order(order, validator, repo, notifier):
    for rule in validator.rules:
        if not rule.matches(order):
            logger.warning(f"Order {order.id} failed rule {rule.name}")
            return False
    repo.save(order)
    notifier.send_confirmation(order)
    return True

O lado “limpo” tem mais métodos, mais arquivos mentalmente, mais indireção. Pra entender o que process_order faz, você pula por 5 funções. O lado “feio” mostra o que faz, na ordem que faz, sem te fazer pular. São 12 linhas. Qual você prefere debugar às 23h em produção?

Esse é o ponto. O checklist de complexidade ciclomática vai te dizer que o lado “limpo” é melhor. A pergunta útil é: pra quem vai ler isso daqui a seis meses? Pra quem está sendo pago pra entregar, ou pra quem está sendo pago pra impressionar o SonarQube?

Quando refatorar, então?

Três critérios pragmáticos, nessa ordem. Se nenhum dos três bate, deixa o código quieto.

  1. O mesmo trecho já foi tocado três vezes em seis meses. Não é “eu acho que vai dar problema”. É histórico. Se você refatora sem histórico, está apostando.
  2. O bug recorrente vive ali. Mesmo trecho, mesmo tipo de erro, terceiro PR de correção. Refatorar para eliminar a categoria de bug vale o investimento.
  3. A função tem ramificações suficientes pra travar o teste mental. Não “passou de 20 linhas” — isso é vaidade. “Eu não consigo ler e prever o resultado sem rodar” — isso é limite cognitivo real.

Fora disso, código feio que funciona é melhor que código bonito que ninguém entende.

O caso Posseidom: 14 meses de refatoração contínua

Na Posseidom, sistema interno de automação comercial que mantenho, herdei em 2024 um módulo de conciliação financeira escrito “à moda antiga”: 1 arquivo, 280 linhas, função única, três ramificações grandes, zero testes. O score de qualidade estática era 35/100. O número de bugs em produção por mês era zero.

Novo engenheiro entrou no time em janeiro. Leu o módulo em 4 horas. Propôs refatorar pra 11 funções seguindo o checklist. Pedi pra ele esperar. Esperei seis meses. Os bugs em produção continuaram zero. O time não tocou o módulo. Em julho, entrou a primeira feature nova que dependia daquela lógica. O engenheiro escreveu a feature em 2 dias, sem mexer no módulo original. Adicionou 40 linhas novas, dois testes. Sumiu.

No acumulado: 14 meses, zero bugs, uma feature adicionada, zero horas gastas com refatoração preventiva. Se eu tivesse aceitado a refatoração em janeiro, o engenheiro teria gasto duas semanas, gerado 300 linhas novas de código, e o módulo estaria agora com 580 linhas espalhadas por 4 arquivos pra fazer o que 280 linhas faziam. E a feature de julho? Teria levado 6 dias, não 2, porque o engenheiro teria que reaprender a regra de negócio agora codificada em 11 funções.

Isso é o que o dogma custa. Não custa em linhas. Custa em horas de engenheiro que foram gastas lendo código que não precisava existir.

Por que esse dogma pegou

Três forças sociais, nenhuma técnica.

  • Cultura de PR-approval. Review virou gate. Parecer rigoroso na review é mais importante do que aprovar o que funciona. Função grande = reprovação fácil.
  • Ferramentas de análise estática que viraram nota. SonarQube, CodeClimate, e similares dão score. Score virou KPI. KPI virou carreira. Engenheiro que sobe o score ganha visibilidade; engenheiro que entrega rápido sem mexer no score não.
  • Ansiedade de júnior em parecer rigoroso. O júnior do meu exemplo queria aprender. Adotou o checklist que parecia “profissional” e reproduziu a forma sem entender o trade-off. Isso não é falha do júnior. É falha do código cultural que ele copiou.

O problema é social, não técnico. Tratar como técnico (mais checklist, mais ferramenta, mais regra) só piora a dinâmica.

O que fazer com a review hoje, se você é lead

Três ações práticas pra reverter a dinâmica sem virar o “anti-checklist” (que seria só mais um dogma):

  1. Elimine o veto por estética. Tire do template de PR review os comentários de “função grande demais”, “nome não descritivo”, “extraia pra variável”. Troque por: “essa mudança é fácil de desfazer se precisar?” Se a resposta for sim, aprova. A reversibilidade é a única qualidade que importa em código novo.
  2. Recompense lead time, não score. Reconhecimento público do time deve ir pra quem entregou feature, não pra quem subiu nota de análise estática. Score de ferramenta é diagnóstico, não desempenho. A diferença parece sutil, mas muda o que o time prioriza.
  3. Faça o ônus da refatoração ser explícito. Antes de aprovar um PR que refatora, pergunte: “qual métrica de entrega isso vai mover?” Se a resposta for “vai diminuir complexidade ciclomática”, recuse. Complexidade ciclomática não é métrica de entrega. Se a resposta for “vai eliminar a categoria de bug que apareceu 3 vezes esse trimestre”, aprove e meça o resultado por 3 meses.

Não é “pare de refatorar”. É “refatore por motivo, não por checklist”. A diferença cabe em uma frase, mas muda a cultura inteira do time.

Conclusão

Clean Code é referência canônica. Não é lei. O capítulo 3 tem ressalvas que a cultura apagou, e o custo de aplicar o checklist sem as ressalvas é código bem organizado que ninguém consegue entregar.

A pergunta certa não é “este código está limpo o suficiente?”. É “este código está resolvendo o problema com menos dor de cabeça?”. E a segunda pergunta não tem checklist que responda.

Como você mede dor de cabeça no seu time?

Marcado:

Um comentário

Deixe um Comentário

O seu endereço de e-mail não será publicado. Campos obrigatórios são marcados com *