As Regras de Programação de Rob Pike Continuam Certas
Rob Pike escreveu cinco regras de programação em 1989. Hoje são mais relevantes do que nunca, especialmente as que continuamos ignorando.

Em 1989, Rob Pike — que mais tarde seria um dos criadores do Go, do UTF-8 e do Plan 9 — escreveu cinco regras de programação. Elas são curtas o suficiente para caber em um cartão de índice e profundas o bastante para que a comunidade de programação esteja debatendo sobre elas há 37 anos. A maioria dos desenvolvedores já viu pelo menos algumas delas citadas em posts de blog ou palestras. Poucos realmente as internalizaram, e isso é uma pena, porque elas economizariam muito esforço desperdiçado.
As regras são enganosamente simples. Elas não tratam de sintaxe nem de padrões de arquitetura. Tratam de onde os programadores desperdiçam tempo de forma consistente e de como parar de fazer isso.
As Cinco Regras
Vou enunciá-las de forma direta antes de dissecá-las:
- Você não consegue prever onde um programa vai gastar seu tempo. Gargalos aparecem em lugares surpreendentes, então não tente adivinhar e aplicar um hack de velocidade até ter provado que aquele é o gargalo.
- Meça. Não ajuste para ganhar velocidade até ter medido, e mesmo assim, só faça isso se uma parte do código dominar o restante.
- Algoritmos sofisticados são lentos quando n é pequeno, e n geralmente é pequeno. Algoritmos sofisticados têm constantes grandes. Enquanto você não souber que n vai ser frequentemente grande, não seja sofisticado.
- Algoritmos sofisticados têm mais bugs do que os simples, e são bem mais difíceis de implementar. Use algoritmos simples, assim como estruturas de dados simples.
- Os dados dominam. Se você escolheu as estruturas de dados certas e organizou bem as coisas, os algoritmos quase sempre serão autoevidentes. Estruturas de dados, não algoritmos, são o centro da programação.
As regras 1 e 2 tratam de otimização. As regras 3 e 4 tratam de complexidade. A regra 5 trata de design. Juntas, formam uma filosofia que é, no fundo, sobre humildade: admitir que nossas intuições sobre desempenho estão erradas, que a complexidade tem custos que subestimamos e que boas estruturas de dados importam mais do que código inteligente.
Regra 1: Você Não Sabe Onde Está o Gargalo
Esta é a regra que os desenvolvedores mais violam, e com mais confiança. "Sei que essa função é lenta porque tem um loop aninhado." "Devo usar um hash map aqui porque buscas são O(1)." "Vou pré-alocar este array porque alocação é cara." Parece razoável. Muitas vezes, está errado.
Já fiz profiling em sistemas de produção suficientes para ter uma coleção de exemplos em que o gargalo intuitivo não era o gargalo real. Um sistema em que todos achavam que o banco de dados era o problema, mas o profiling mostrou que a serialização JSON consumia 60% do tempo das requisições. Um pipeline de dados em que a "cara" multiplicação de matrizes levava 5% do tempo de execução enquanto o parsing de CSV levava 70%. Uma aplicação web em que o time passou meses otimizando consultas ao banco, enquanto o gargalo real era a resolução de DNS em cada requisição HTTP de saída.
O cérebro humano é um péssimo profiler. Superestimamos operações que parecem conceitualmente caras (consultas ao banco, chamadas de rede) e subestimamos as que parecem baratas (concatenação de strings, parsing de JSON, alocação de memória). O hardware moderno piora isso: caches de CPU, predição de desvios e execução fora de ordem fazem com que a relação entre complexidade do código e tempo de execução seja profundamente contraintuitiva.
Regra 2: Meça Primeiro, Depois Otimize
Este é o desdobramento prático da Regra 1. Não otimize com base na intuição. Faça profiling. Encontre o ponto quente real. Então otimize esse ponto, e somente ele.
A segunda metade desta regra — "não otimize a menos que uma parte do código domine o restante" — é igualmente importante e menos citada. Se seu profiler mostra que o tempo de execução está distribuído igualmente entre 20 funções, cada uma levando 5%, não existe um único gargalo para otimizar. Tornar uma função 2x mais rápida economiza apenas 2,5% do tempo total. Raramente vale a pena pela complexidade adicional. Você precisa encontrar uma abordagem fundamentalmente diferente, em vez de otimizar funções isoladamente.
# Profile first. Always.
# Python
python -m cProfile -s cumulative my_script.py
# Node.js
node --prof app.js
node --prof-process isolate-*.log > profile.txt
# Go
go test -bench=. -cpuprofile=cpu.prof
go tool pprof cpu.prof
# Rust
cargo flamegraph
# General (Linux)
perf record ./my_program && perf report
# The flamegraph (brendangregg.com/flamegraphs) is the most
# informative visualization. It shows you exactly where time
# is spent, in a format that makes bottlenecks visually obvious.
Regra 3: Algoritmos Sofisticados São Lentos Quando N É Pequeno
Esta é a regra que a educação em ciência da computação ensina de trás para frente. Ensinamos que O(n log n) é melhor que O(n²), e isso é verdade assintoticamente. Mas, para n = 20, um insertion sort O(n²) bem implementado é mais rápido que um merge sort O(n log n), por causa das constantes, do comportamento do cache e do overhead.
Exemplos do mundo real são abundantes. Uma busca linear em um array ordenado de 50 elementos é mais rápida que uma busca binária, porque a busca linear tem comportamento de cache perfeito e nenhuma predição de desvio errada. Uma lista encadeada simples supera uma árvore binária balanceada para coleções com menos de ~100 elementos, porque percorrer ponteiros em uma árvore destrói a localidade de cache. Hash maps têm busca O(1) amortizada, mas a constante é alta o suficiente para que a busca linear em um array seja mais rápida em coleções com menos de ~30-50 elementos.
As bibliotecas padrão sabem disso. O sorted() do Python usa Timsort, que recorre ao insertion sort para subsequências pequenas. O std::sort do C++ muda para insertion sort abaixo de um limiar (tipicamente 16 a 32 elementos). O sort_unstable do Rust usa uma combinação de quicksort e insertion sort. O algoritmo "sofisticado" só é usado onde n é realmente grande o suficiente para que ele vença.
A lição mais ampla: conheça o seu n. Se você está escolhendo entre um algoritmo O(n²) simples e um O(n log n) complexo, pergunte-se quão grande n será na prática. Se for menor que algumas centenas, o algoritmo simples quase certamente serve, e será mais fácil de escrever, depurar e manter.
Regra 4: Simples É Melhor Que Esperto
A Regra 4 estende a Regra 3 para além do desempenho. Algoritmos sofisticados não são apenas mais lentos para n pequeno — eles têm mais bugs. Uma árvore rubro-negra tem mais casos de borda do que um array ordenado. Uma estrutura de dados concorrente lock-free tem modos de falha mais sutis do que uma protegida por mutex. Um alocador de memória customizado tem mais formas de corromper memória do que o alocador do sistema.
Já vi times gastarem semanas implementando e depurando um cache LRU customizado com operações O(1), quando um simples array limitado com remoção linear teria sido escrito em uma tarde, estaria correto na primeira tentativa e seria rápido o suficiente para a carga de trabalho deles (que tinha no máximo algumas centenas de entradas no cache).
O custo da complexidade não está apenas na implementação inicial. Está em cada desenvolvedor futuro que precisa entender, modificar e depurar o código. Um algoritmo simples que todo o time entende vale mais do que um algoritmo esperto que só o autor original consegue manter. E o autor original, seis meses depois, é efetivamente outra pessoa, que também esqueceu como aquilo funciona.
Depurar é duas vezes mais difícil do que escrever o código em primeiro lugar. Portanto, se você escrever o código da forma mais inteligente possível, por definição não será inteligente o bastante para depurá-lo. — Brian Kernighan
Regra 5: Os Dados Dominam
Esta é a regra mais importante de Pike e a mais negligenciada por desenvolvedores que focam em algoritmos e padrões de design. A afirmação: se suas estruturas de dados estiverem certas, os algoritmos vêm naturalmente. Se estiverem erradas, nenhuma quantidade de esperteza algorítmica vai salvar você.
Fred Brooks disse algo parecido: "Mostre-me seus fluxogramas e esconda suas tabelas, e continuarei perplexo. Mostre-me suas tabelas, e normalmente não precisarei dos seus fluxogramas." Linus Torvalds ecoou a ideia: "Maus programadores se preocupam com o código. Bons programadores se preocupam com as estruturas de dados e seus relacionamentos."
Esse princípio aparece constantemente na prática. Uma base de código que armazena permissões de usuário como uma lista plana de strings vai acumulando lógica de verificação complexa e propensa a erros, espalhada pelo código. Reestruture os dados como uma hierarquia de papéis, e a lógica de verificação se torna trivial. Um sistema que guarda eventos como blobs JSON vai precisar de parsing e validação complexos em cada consumidor. Estruture os eventos como registros tipados com esquemas explícitos, e os consumidores ficam dramaticamente mais simples.
# Bad data structure → complex algorithm
class OrderSystem:
def __init__(self):
self.orders = [] # flat list of all orders
def get_user_orders(self, user_id):
return [o for o in self.orders if o.user_id == user_id] # O(n)
def get_pending_orders(self):
return [o for o in self.orders if o.status == 'pending'] # O(n)
def get_user_pending_orders(self, user_id):
return [o for o in self.orders
if o.user_id == user_id and o.status == 'pending'] # O(n)
# Better data structure → algorithms become obvious
class OrderSystem:
def __init__(self):
self.orders_by_user = {} # user_id → [orders]
self.orders_by_status = {} # status → [orders]
def get_user_orders(self, user_id):
return self.orders_by_user.get(user_id, []) # O(1)
def get_pending_orders(self):
return self.orders_by_status.get('pending', []) # O(1)
# The right data structure makes the code self-evident.
# You don't need to think about algorithms at all.
Como o Go Incorpora Essas Regras
É difícil olhar para as regras de Pike sem enxergar a filosofia de design do Go em estado embrionário. O Go, que Pike ajudou a criar 20 anos depois de escrever essas regras, é uma linguagem que privilegia sistematicamente a simplicidade em vez da esperteza.
- Sem generics (inicialmente) — força estruturas de dados simples. (Generics foram adicionados no Go 1.18, mas somente depois de anos de resistência, até que se encontrasse um design simples o suficiente.)
- Sem sobrecarga de operadores — o código significa o que parece.
- Sem conversões de tipo implícitas — explicitude acima de esperteza.
- Sem exceções — trate os erros onde eles ocorrem.
- Algoritmos da biblioteca padrão reduzidos ao mínimo — use slices e maps, não estruturas de dados sofisticadas.
- Profiling integrado (
pprof) — meça, não adivinhe.
O Go é frequentemente criticado como "chato" por desenvolvedores que preferem linguagens mais expressivas. Esse é exatamente o ponto. As regras de Pike são uma receita para código chato — código simples, mensurável e construído sobre boas estruturas de dados, em vez de algoritmos inteligentes. O Go é o que acontece quando você transforma essa receita em uma linguagem.
Onde as Regras Não Se Aplicam
Nenhum conjunto de regras é universal, e as de Pike têm exceções legítimas. Sistemas críticos para desempenho (engines de jogos, internals de bancos de dados, compiladores) às vezes precisam de algoritmos sofisticados porque seu n realmente é grande. Código de infraestrutura que roda milhões de vezes por segundo justifica otimizações que o código de aplicação não justifica. E, às vezes, o algoritmo "simples" tem complexidade O(n³) que é realmente inaceitável, mesmo para n modesto.
As regras são heurísticas, não leis. Seu valor está em corrigir o viés comum: desenvolvedores tendem a otimizar cedo demais, usar algoritmos complexos demais e pensar muito no código e pouco nas estruturas de dados. As regras de Pike empurram contra essas tendências. Se você se encontrar na rara situação em que o viés oposto se aplica — em que você realmente precisa de mais complexidade, não de menos —, então vá em frente e seja sofisticado. Mas meça primeiro.
Trinta e sete anos depois de Pike as escrever, as regras continuam sendo alguns dos melhores conselhos de programação já publicados. Não porque sejam surpreendentes — a maioria dos desenvolvedores experientes, ao lê-las, pensa "sim, óbvio". O valor está em tê-las enunciadas com clareza suficiente para aplicá-las de forma consistente. Da próxima vez que você for pegar uma árvore rubro-negra, um alocador customizado ou uma "otimização" que você não mediu, lembre-se: meça primeiro, mantenha simples e acerte as estruturas de dados.


