Limpo e rápido ou Clean™ e lento

A essa altura do чемпионато, todo mundo já leu o livro que define esse conceito. Ele se tornou obrigatório para qualquer desenvolvedor, porque algum dia você vai receber um review dizendo que não seguiu o princípio X do Clean Code™ e deveria refatorar aquela solução que estava há semanas parada no backlog.

Infelizmente, discutir sobre o tema é complexo: afinal, ninguém quer um repositório com código que não seja "limpo", e as discussões parecem rodar em torno de conceitos abstratos do que é limpo, e não do que de fato faz parte da metodologia Clean Code™. O livro abrange dezenas de tópicos: SOLID, formatação, classes, concorrência, design emergente, entre outros. Para esta análise, esses tópicos foram agrupados nos princípios gerais que os representam. Por exemplo, "Funções Pequenas e Focadas" engloba o Princípio da Responsabilidade Única (SRP); "Polimorfismo" engloba as discussões sobre herança, interfaces e inversão de dependência; "Testes Limpos" cobre tanto a escrita de testes quanto o design orientado a testabilidade (TDD). Os princípios utilizados são:

  • Nomes Significativos
  • Funções Pequenas e Focadas
  • Comentários Mínimos e Precisos
  • Tratamento de Erros
  • Testes Limpos
  • Polimorfismo

Esses agrupamentos são simplificações deliberadas para viabilizar a análise. O SRP, por exemplo, é tratado dentro de "Funções Pequenas e Focadas" porque, na prática do livro, ambos convergem para a mesma recomendação: cada unidade de código deve fazer uma única coisa. Da mesma forma, herança, interfaces e inversão de dependência são agrupados sob "Polimorfismo" porque compartilham o mecanismo central de delegar comportamento via abstração. Reconheço que puristas de SOLID podem discordar; o objetivo aqui não é redefinir esses conceitos, mas organizá-los em categorias analisáveis.

Diante de um framework sem validação empírica clara, a forma mais direta de avaliar o trade-off é medir o que ele custa na prática. Se o Clean Code™ cobra um preço em performance, qual é o tamanho dessa conta? Para tornar essa discussão concreta, vale reproduzir um experimento que coloca os números na mesa.


Dois grupos, dois propósitos

Podemos categorizar esses princípios em dois grandes grupos.

O primeiro grupo é dedicado exclusivamente à manutenibilidade do código: nomes significativos, comentários mínimos e precisos e testes limpos. Nenhuma dessas categorias roda em produção; elas existem para facilitar a vida do desenvolvedor. O segundo grupo reúne o que de fato impacta a aplicação em execução: funções pequenas e focadas, tratamento de erros e polimorfismo.

No fundo, a ideia do Clean Code™ é colocar o desenvolvedor como foco do processo de desenvolvimento de software, facilitando e aumentando a produtividade do time.

Grupo 1: Manutenibilidade

O primeiro grupo realmente segue esse ethos. São guias gerais para melhorar a vida de quem desenvolve, conceitos que não afetam diretamente o código que roda a aplicação; apenas ajudam a ler, compreender e iterar sobre um repositório. Dado isso, o único trade-off ao adotar essa parte do framework é a adaptabilidade dos desenvolvedores e o quanto esses princípios de fato aumentam a velocidade de produção dentro de um time.

Grupo 2: Design e estrutura do código

O segundo grupo apresenta escolhas que determinam um padrão de desenvolvimento com impacto direto na aplicação. Ao optar por polimorfismo, estilo de funções e tratamento de erros específicos, você está condicionando um trade-off: a velocidade de produção passa a ter mais peso do que a performance da aplicação.

Essa é uma escolha totalmente razoável, e frequentemente necessária ao se produzir software. Porém, como toda decisão baseada em dados, é preciso ver os números para entender qual seria o limite: o quanto se está disposto a perder de performance para aumentar a produtividade dos desenvolvedores.

E aqui está o ponto: se o Clean Code™ fosse um framework com resultados comprovados, haveria dados sólidos para sustentá-lo. Mas não existe uma definição clara para o que é "código de qualidade"; o que se encontrará são ideias que nem os próprios desenvolvedores concordam entre si. Você pode tentar usar definições como legibilidade e estrutura, mas esses são conceitos vagos, que mudam de significado dependendo da equipe, da linguagem e do domínio. Pior: as métricas tradicionais que deveriam capturar essa qualidade (complexidade ciclomática, acoplamento, linhas de código) simplesmente não refletem o que desenvolvedores percebem como melhoria. O Clean Code™ propõe resolver um problema que a comunidade acadêmica ainda não conseguiu nem definir direito.


O experimento

Essa troca já foi testada anteriormente por Casey Muratori em um setup de C++, com código extraído do próprio livro, e ele encontrou uma diferença de 10 a 25 vezes mais rápida ao ignorar alguns dos conceitos do livro.

Fazendo uma adaptação desse processo para Python, utilizei o seguinte setup para a mesma avaliação.

Clean Code™ vs. Performance: Benchmark de Cálculo de Área

O debate proposto por Casey questiona se os padrões do Clean Code™, especialmente o polimorfismo, carregam um custo de performance desnecessário. Este experimento reproduz a ideia central em Python, comparando três abordagens para calcular a área total de uma coleção de formas geométricas.

As abordagens

  1. OOP (Polimorfismo): cada forma é uma classe com seu próprio método area(). O loop chama shape.area() e o runtime resolve qual método executar.
  2. Procedural (If/Elif): todas as formas vivem numa struct achatada com tipo, largura e altura. Uma função usa if/elif para escolher a fórmula correta.
  3. Tabela de Pesquisa: todas as fórmulas compartilham a mesma estrutura, coeficiente × largura × altura. Uma tabela de coeficientes [1.0, 1.0, 0.5, π] elimina todo branching.

Setup

Instância GCP e2-standard-2 (2 vCPUs, 8 GB RAM), Ubuntu 22.04 LTS. Processo fixado em um core (taskset -c 0). 10 milhões de formas geradas aleatoriamente (distribuição uniforme entre os 4 tipos, dimensões entre 0 e 1). Seed fixa (42) para reprodutibilidade. 20 execuções por abordagem, 2 warmups descartados. Medição com time.perf_counter().

Código do Benchmark
import math
import time
import random
import statistics

def bench(fn, runs=20):
    fn(); fn()
    times = []
    for _ in range(runs):
        t0 = time.perf_counter()
        fn()
        times.append(time.perf_counter() - t0)
    return times

def print_results(results, baseline_key):
    baseline = statistics.mean(results[baseline_key])
    print(f"\n{'Approach':<35} {'Mean (s)':>10} {'StdDev':>10} {'Min (s)':>10} {'vs base':>8}")
    print("-" * 75)
    for name, times in results.items():
        avg = statistics.mean(times)
        std = statistics.stdev(times) if len(times) > 1 else 0
        mn = min(times)
        speedup = baseline / avg
        print(f"{name:<35} {avg:>10.5f} {std:>10.5f} {mn:>10.5f} {speedup:>7.2f}x")

NUM_SHAPES = 10_000_000
NUM_RUNS = 20

random.seed(42)

types_list, widths_list, heights_list = [], [], []

for _ in range(NUM_SHAPES):
    t = random.randint(0, 3)
    types_list.append(t)
    if t in (0, 3):
        v = random.random()
        widths_list.append(v); heights_list.append(v)
    else:
        widths_list.append(random.random()); heights_list.append(random.random())

class ShapeBase:
    def area(self): pass

class Square(ShapeBase):
    __slots__ = ('side',)
    def __init__(self, s): self.side = s
    def area(self): return self.side * self.side

class Rectangle(ShapeBase):
    __slots__ = ('width', 'height')
    def __init__(self, w, h): self.width = w; self.height = h
    def area(self): return self.width * self.height

class Triangle(ShapeBase):
    __slots__ = ('base', 'height')
    def __init__(self, b, h): self.base = b; self.height = h
    def area(self): return 0.5 * self.base * self.height

class Circle(ShapeBase):
    __slots__ = ('radius',)
    def __init__(self, r): self.radius = r
    def area(self): return math.pi * self.radius * self.radius

class ShapeUnion:
    __slots__ = ('type', 'width', 'height')
    def __init__(self, t, w, h): self.type = t; self.width = w; self.height = h

COEFF = [1.0, 1.0, 0.5, math.pi]

clean_shapes, union_shapes = [], []
for i in range(NUM_SHAPES):
    t, w, h = types_list[i], widths_list[i], heights_list[i]
    if t == 0:   clean_shapes.append(Square(w))
    elif t == 1: clean_shapes.append(Rectangle(w, h))
    elif t == 2: clean_shapes.append(Triangle(w, h))
    elif t == 3: clean_shapes.append(Circle(w))
    union_shapes.append(ShapeUnion(t, w, h))

def py_oop():
    accum = 0.0
    for s in clean_shapes: accum += s.area()
    return accum

def py_switch():
    accum = 0.0
    for s in union_shapes:
        t = s.type
        if t == 0:   accum += s.width * s.width
        elif t == 1: accum += s.width * s.height
        elif t == 2: accum += 0.5 * s.width * s.height
        elif t == 3: accum += math.pi * s.width * s.width
    return accum

def py_table():
    accum = 0.0
    c = COEFF
    for s in union_shapes: accum += c[s.type] * s.width * s.height
    return accum

def main():
    print(f"{'='*60}")
    print(f"  Benchmark: {NUM_SHAPES:,} shapes, {NUM_RUNS} runs each")
    print(f"{'='*60}")

    results = {}
    results["OOP (polymorphism)"]   = bench(py_oop, NUM_RUNS)
    results["Procedural (if/elif)"] = bench(py_switch, NUM_RUNS)
    results["Lookup Table"]         = bench(py_table, NUM_RUNS)

    print_results(results, "OOP (polymorphism)")
    print()

if __name__ == "__main__":
    main()
============================================================ Benchmark: 10,000,000 shapes, 20 runs each ============================================================ Approach Mean (s) StdDev Min (s) vs base --------------------------------------------------------------------------- OOP (polymorphism) 1.48864 0.01663 1.47013 1.00x Procedural (if/elif) 1.21787 0.01012 1.20604 1.22x Lookup Table 0.72776 0.01013 0.71919 2.05x

É importante destacar que Python é uma linguagem interpretada, onde o overhead do interpretador domina o tempo de execução; otimizações de compilação como inlining ou eliminação de branches simplesmente não existem. Mesmo nesse cenário, onde performance não é o objetivo primário da linguagem, a abordagem por tabela de pesquisa foi 2x mais rápida que o polimorfismo. Se em um ambiente que naturalmente nivela tudo por baixo já se observa esse ganho, em linguagens compiladas (onde o compilador poderia otimizar, mas o polimorfismo impede) a diferença tende a ser ainda maior, como demonstrado por Casey Muratori em C++ (10–25x), explorando outros pontos para além do custo do dispatch polimórfico, como o impacto de funções excessivamente pequenas.


O fator IA na equação

Uma premissa central do Clean Code é que código deve ser otimizado para leitura humana. Em 2008, isso fazia sentido irrestrito: humanos eram os únicos leitores e escritores de código. Em 2025, o cenário mudou. Ferramentas como GitHub Copilot, Claude Code e Cursor atuam como coautores e revisores, e LLMs conseguem navegar código complexo com facilidade comparável a código "limpo". Isso não invalida a legibilidade como valor, mas muda o cálculo: se uma IA consegue compreender e refatorar uma função procedural de 50 linhas tão bem quanto seis funções polimórficas de 8 linhas, parte do benefício do Clean Code passa a ser redundante. O trade-off de performance discutido neste artigo ganha peso quando o principal argumento do outro lado, legibilidade para humanos, tem agora um substituto parcial.

Para ilustrar, considere as duas implementações do cálculo de área usadas neste artigo. A versão Clean Code™ distribui a lógica em quatro classes com métodos area() individuais; a versão procedural concentra tudo em uma única função com if/elif. Se você pedir a uma LLM como o Claude para "explicar o que esse código faz", ambas as versões produzem respostas igualmente precisas. Se pedir para "adicionar suporte a um hexágono", a LLM gera a nova classe polimórfica ou o novo branch procedural com a mesma facilidade. A legibilidade humana era o argumento central para justificar a indireção do polimorfismo; quando uma IA consegue navegar essa indireção instantaneamente, o benefício que restava ao lado Clean da balança perde peso, e o custo de performance do outro lado permanece intacto.


Mas nem tudo precisa ser rápido

Na prática, a maioria dos sistemas em produção (APIs web, CRUDs, pipelines de dados) tem seus gargalos em I/O, rede e banco de dados, não no dispatch de métodos. O cenário deste benchmark (um hot loop iterando sobre 10 milhões de objetos em memória) é real, mas restrito: processamento de sinais, engines de jogos, simulações numéricas. Para o restante, o custo do polimorfismo raramente será o fator limitante.

O benefício da escolha pelo Clean Code™ seria uma maior capacidade de entregar features e tornar a compreensão geral de um repositório melhor. Entretanto, essa compreensão ainda não é uma avaliação científica do estado do uso do Clean Code™. Enquanto temos pesquisas que mostram que a maioria dos profissionais reconhece os princípios do Clean Code™ como boas práticas, poucos os consideram essenciais no dia a dia; muitos relatam que preferem iterar rapidamente com código "sujo" e refatorar depois, quando (e se) necessário. Quando analisado o impacto da refatoração em métricas de qualidade de código (modificabilidade e analisabilidade), não se encontra uma diferença estatisticamente significativa após a aplicação de técnicas de refatoração alinhadas ao Clean Code™. É importante notar que ambos os estudos têm escopo limitado (amostras pequenas e contextos específicos), mas apontam na mesma direção: a eficácia prática do Clean Code™ como framework de produtividade ainda carece de validação empírica.

E talvez o problema mais fundamental seja anterior ao debate sobre performance: ninguém concorda sobre o que "código de qualidade" realmente significa. Quando pesquisadores perguntam a desenvolvedores experientes o que define código bom, as respostas gravitam em torno de "legibilidade" e "estrutura", termos que cada equipe interpreta de um jeito. Na prática, já vi times gastarem sprints inteiras refatorando código funcional para "ficar Clean", sem redução de bugs, sem ganho de velocidade de entrega, apenas para satisfazer uma convenção que ninguém conseguia justificar com dados. O Clean Code™ se apresenta como resposta universal, mas o que é "limpo" para um time de backend em Go não é o mesmo que para um time de data science em Python. Quando o framework não reconhece isso, ele não padroniza: ele engessa. A padronização que funciona nasce do contexto da equipe, da linguagem e do domínio, não de um livro escrito em 2008 para Java.

Este artigo mostrou três coisas. Primeiro, que os princípios do Clean Code™ se dividem em dois grupos com naturezas diferentes: os de manutenibilidade (que não custam performance) e os de design (que custam). Segundo, que o custo de performance é mensurável: mesmo em Python, a abordagem por tabela de pesquisa foi 2x mais rápida que o polimorfismo, e em linguagens compiladas essa diferença se amplifica. Terceiro, que o benefício prometido do outro lado da balança, a melhora na qualidade e produtividade, ainda carece de validação empírica.

Buscar padronizar um repositório, tornando-o reprodutível e compreensível, é um desafio diário na vida de desenvolvedores. Essa busca por deixar o código limpo não pode ser confundida com deixar o código Clean, porque o segundo carrega trade-offs claros: é um conceito amplamente conhecido que deixa a aplicação mais lenta e não possui embasamento científico na melhora dos atributos que afirma melhorar. O trade-off de uma nova opção seria desconhecido, e nem sempre o desconhecido é melhor. Mas a questão permanece: código limpo não precisa ser Clean Code™.

Referências

  1. MURATORI, Casey. Clean Code, Horrible Performance. Computer Enhance, 2023. Disponível em: https://www.computerenhance.com/p/clean-code-horrible-performance
  2. MURATORI, Casey. misc. GitHub, 2023. Disponível em: https://github.com/cmuratori/misc/tree/main
  3. LJUNG, K.; GONZALEZ-HUERTA, J. "To Clean Code or Not to Clean Code": A Survey Among Practitioners. In: INTERNATIONAL CONFERENCE ON PRODUCT-FOCUSED SOFTWARE PROCESS IMPROVEMENT (PROFES), 23., 2022, Jyväskylä. Lecture Notes in Computer Science, v. 13709, p. 298–315. Springer, 2022. DOI: 10.1007/978-3-031-21388-5_21
  4. KANNANGARA, S. H.; WIJAYANAYAKE, W. M. J. I. An Empirical Evaluation of Impact of Refactoring on Internal and External Measures of Code Quality. International Journal of Software Engineering & Applications (IJSEA), v. 6, n. 1, p. 51–67, jan. 2015. DOI: 10.5121/IJSEA.2015.6105
  5. KOLLER, H. G. Effects of Clean Code on Understandability: An Experiment and Analysis. 2016. Dissertação (Mestrado em Informática) — Department of Informatics, University of Oslo, Oslo, 2016.
  6. RACHOW, P.; SCHRÖDER, S.; RIEBISCH, M. Missing Clean Code Acceptance and Support in Practice — An Empirical Study. In: AUSTRALASIAN SOFTWARE ENGINEERING CONFERENCE (ASWEC), 25., 2018, Adelaide. Proceedings [...]. IEEE, 2018. p. 131–140. DOI: 10.1109/ASWEC.2018.00026
  7. BÖRSTLER, J. et al. Developers talking about code quality. Empirical Software Engineering, v. 28, n. 6, art. 128, p. 1–31, 2023. DOI: 10.1007/s10664-023-10381-0

← Voltar para todos os posts