Leitura de Código

Por Que Leitura de Código É a Habilidade Mais Subestimada

Engenheiros experientes passam ~70% do tempo lendo código e apenas ~30% escrevendo. Apesar disso, quase nenhum curso, bootcamp ou faculdade ensina leitura de código como disciplina. A capacidade de navegar uma codebase desconhecida de milhões de linhas, entender decisões históricas e identificar riscos em code review é o que separa engenheiros eficazes de engenheiros que apenas “sabem programar”.

Tempo gasto por engenheiros experientes:
┌──────────────────────────────────────────────┐
│ Lendo código existente          ~35%         │
│ Code review                     ~20%         │
│ Debugando/investigando          ~15%         │
│ Escrevendo código novo          ~15%         │
│ Reuniões e comunicação          ~10%         │
│ Documentação                    ~5%          │
└──────────────────────────────────────────────┘

Estratégias para Ler Codebases Grandes

1. Abordagem Top-Down (Arquitetura → Detalhes)

Comece pelo panorama geral e vá refinando. Ideal para onboarding em um projeto novo.

Passo 1: Documentação e contexto
  → README.md, ARCHITECTURE.md, ADRs, wiki
  → Qual problema este sistema resolve?
  → Quais são os componentes principais?

Passo 2: Estrutura de diretórios
  → tree -L 2 -d (visão geral das pastas)
  → Convenção: src/modules/? src/features/? src/domain/?
  → Onde ficam os testes? Colocados junto ou em pasta separada?

Passo 3: Dependências externas
  → package.json / go.mod / Cargo.toml / requirements.txt
  → Quais frameworks? (Express, NestJS, Fastify, Django)
  → Quais bancos de dados? (drivers presentes)
  → Quais serviços externos? (SDKs de AWS, Stripe, SendGrid)

Passo 4: Entrypoint e bootstrap
  → main.ts, index.ts, app.ts, server.ts
  → Como o servidor inicia? Quais middlewares são registrados?
  → Qual é a ordem de inicialização?

Passo 5: Diagrama mental dos componentes
  → API Gateway → Controllers → Services → Repositories → Database
  → Quais são os bounded contexts?
  → Onde estão as fronteiras entre módulos?

2. Abordagem Bottom-Up (Módulo → Sistema)

Comece por um módulo específico e expanda. Ideal para fix de bugs ou entender uma área específica.

Passo 1: Identifique o módulo-alvo
  → grep -r "função_que_quebrou" --include="*.ts"
  → Ou use LSP: "Go to Definition" / "Find All References"

Passo 2: Leia os testes primeiro
  → Testes são documentação executável
  → Mostram como o módulo DEVE ser usado
  → Revelam edge cases que o autor considerou

Passo 3: Leia a interface pública
  → Exports do módulo, tipos públicos, API surface
  → Não mergulhe na implementação ainda

Passo 4: Rastreie dependências
  → O que este módulo importa?
  → Quem importa este módulo? (Find All References)
  → Qual é o grafo de dependências?

Passo 5: Mergulhe na implementação
  → Agora sim, leia a lógica interna
  → Com contexto dos testes e da interface, faz muito mais sentido

3. Feature Tracing (Seguindo o Fluxo de Dados)

Siga uma feature end-to-end. Ideal para entender como as peças se conectam.

Exemplo: Rastrear "Criar Pedido" em um e-commerce

1. HTTP Request chega
   → routes/orders.ts → POST /orders
   → Middleware de autenticação → JWT validation
   → Middleware de validação → Zod/Joi schema

2. Controller recebe
   → OrderController.create(req, res)
   → Extrai dados do body, chama service

3. Service processa
   → OrderService.placeOrder(dto)
   → Valida estoque → InventoryService.checkAvailability()
   → Calcula preço → PricingService.calculate()
   → Cria order → OrderRepository.save()

4. Efeitos colaterais
   → Evento OrderCreated emitido
   → EmailConsumer envia confirmação
   → InventoryConsumer reserva estoque
   → AnalyticsConsumer registra métrica

5. Response retorna
   → 201 Created com order serializada
   → Headers de rate limiting atualizados

Ferramentas Essenciais para Leitura de Código

Ferramentas de Terminal

# Busca por padrões em toda a codebase
rg "OrderService" --type ts -l           # Arquivos que mencionam OrderService
rg "TODO|FIXME|HACK|XXX" --type ts       # Dívida técnica explícita
rg "catch\s*\(" --type ts -C 3           # Como erros são tratados?

# Estrutura de diretórios (ignorando node_modules, .git)
tree -L 3 -d -I "node_modules|.git|dist|coverage"

# Contar linhas por tipo de arquivo (dimensionar a codebase)
cloc src/ --exclude-dir=node_modules

# Encontrar os arquivos mais modificados (hotspots)
git log --format=format: --name-only --since="6 months ago" | \
  sort | uniq -c | sort -rn | head -20

# Encontrar arquivos com mais autores (complexidade organizacional)
git log --format='%aN' --since="6 months ago" -- src/modules/orders/ | \
  sort | uniq -c | sort -rn

LSP e Features de IDE

Funcionalidade          | Atalho (VS Code)      | Quando usar
------------------------|----------------------|---------------------------
Go to Definition        | F12                  | Navegar para implementação
Find All References     | Shift+F12            | Quem usa este símbolo?
Go to Type Definition   | Ctrl+Shift+F12       | Ver o tipo/interface
Peek Definition         | Alt+F12              | Ver sem trocar de arquivo
Call Hierarchy          | Shift+Alt+H          | Quem chama esta função?
Type Hierarchy          | —                    | Árvore de herança
Symbol Search           | Ctrl+T               | Buscar por nome de símbolo
Outline View            | Ctrl+Shift+O         | Estrutura do arquivo atual

ctags e Navegação Offline

# Gerar tags para navegação rápida (funciona em qualquer editor)
ctags -R --exclude=node_modules --exclude=dist src/

# No Vim: Ctrl+] para ir à definição, Ctrl+T para voltar
# Universal-ctags suporta TypeScript, Go, Rust, Python, etc.

Arqueologia de Código com Git

git log — Entendendo a Evolução

# História de um arquivo específico (quem mudou, quando, por quê)
git log --oneline --follow -- src/services/OrderService.ts

# Log com diff (ver O QUE mudou em cada commit)
git log -p -- src/services/OrderService.ts

# Log com estatísticas (quantas linhas mudaram)
git log --stat -- src/services/OrderService.ts

# Buscar commits que adicionaram/removeram uma string
git log -S "calculateDiscount" --oneline
# "Pickaxe" — encontra quando uma função foi criada ou removida

# Buscar commits com mensagem que menciona um ticket
git log --grep="JIRA-1234" --oneline

# Visualizar evolução do repositório
git log --oneline --graph --all --decorate | head -50

git blame — Quem e Por Quê

# Blame completo
git blame src/services/OrderService.ts

# Blame ignorando mudanças de formatação
git blame -w src/services/OrderService.ts

# Blame ignorando commits de refatoração (precisa de .git-blame-ignore-revs)
git blame --ignore-revs-file .git-blame-ignore-revs src/services/OrderService.ts

# Ver o commit completo de uma linha específica
git blame -L 42,42 src/services/OrderService.ts
# Copie o hash e faça: git show <hash>

git bisect — Encontrando Quando Quebrou

# Busca binária automatizada para encontrar o commit que introduziu um bug
git bisect start
git bisect bad              # Commit atual está com bug
git bisect good v2.1.0      # Esta versão estava funcionando

# Git vai fazer checkout de commits intermediários
# Teste cada um e marque:
git bisect good   # ou
git bisect bad

# Automatizar com script de teste:
git bisect run npm test -- --testPathPattern="OrderService"

Entendendo Arquitetura a Partir do Código

Sinais Arquiteturais

Sinal no código                    | O que revela
-----------------------------------|----------------------------------
Muitos imports cruzados entre      | Módulos não estão bem separados
módulos                            | (acoplamento alto)

Uma pasta com 50+ arquivos         | Módulo God — precisa ser dividido

Arquivo com 1000+ linhas           | Provavelmente viola SRP

Interface com 20+ métodos          | Fat interface — viola ISP

Muitos try/catch genéricos         | Tratamento de erro fraco
(catch (e) { console.log(e) })     |

Testes que precisam de setup       | Acoplamento alto — DIP violado
com 50+ linhas                     |

Muitas variáveis globais ou        | Estado compartilhado — race
singletons mutáveis                | conditions prováveis

Funções com 5+ parâmetros          | Responsabilidades misturadas ou
                                   | falta de objetos de domínio

Padrões Arquiteturais Comuns (Reconhecimento)

Estrutura de pastas              | Padrão provável
---------------------------------|---------------------------
src/controllers, src/services,   | MVC / Layered Architecture
src/models, src/routes           |

src/modules/orders,              | Modular Monolith
src/modules/payments,            | (boa separação por domínio)
src/modules/users                |

src/domain, src/application,     | Clean Architecture /
src/infrastructure,              | Hexagonal Architecture
src/presentation                 |

src/commands, src/queries,       | CQRS
src/events, src/projections      |

services/order-service,          | Microserviços
services/payment-service,        | (repos separados ou monorepo)
services/user-service            |

Lendo Código Open Source: Projetos de Referência

Por Onde Começar

Projeto     | O que aprender                    | Complexidade
------------|-----------------------------------|-------------
Express.js  | Middleware pattern, composição     | Baixa
Redis       | Estruturas de dados, event loop    | Média
Node.js     | Libuv, event loop, streams         | Alta
PostgreSQL  | Query planning, MVCC, WAL          | Muito alta
Linux       | Subsistemas (scheduler, VFS, net)  | Extrema

Estratégia para Ler Projetos Open Source

1. Leia o CONTRIBUTING.md e ARCHITECTURE.md (se existirem)
2. Clone e compile/rode — garanta que funciona antes de ler
3. Escolha UMA feature para rastrear (feature tracing)
4. Use git log --oneline | head -100 para entender a atividade recente
5. Leia os testes da feature escolhida
6. Siga o fluxo de dados com Go to Definition
7. Tome notas — mantenha um documento com suas descobertas
8. Contribua com algo pequeno (doc fix, teste) para solidificar

Code Review Profunda

O Que Revisar (Além do Óbvio)

Review Superficial:               Review Profunda:
─────────────────────             ───────────────────────
□ Syntax e formatação             □ Arquitetura e design
□ Nomes de variáveis              □ Edge cases e race conditions
□ "Funciona?"                     □ "E se falhar?"
□ Linting pass                    □ "Escala para 10x o tráfego atual?"
                                  □ "Outro dev entende isso em 6 meses?"
                                  □ "Tem teste para o caminho de erro?"
                                  □ "A abstração está no nível certo?"
                                  □ "Isto cria acoplamento indesejado?"
                                  □ "Há impacto em observabilidade?"

Checklist de Code Review Avançado

CORREÇÃO
  □ A mudança resolve o problema descrito no ticket?
  □ Edge cases estão cobertos? (null, vazio, overflow, unicode)
  □ Race conditions possíveis em código concorrente?
  □ Idempotência em operações que podem ser retried?

PERFORMANCE
  □ Queries N+1? (ORM gerando SELECTs em loop)
  □ Índices necessários no banco foram criados?
  □ Caching apropriado? Invalidação correta?
  □ Complexidade de tempo aceitável para o volume esperado?
  □ Memory leaks? (event listeners não removidos, closures)

SEGURANÇA
  □ Input validado e sanitizado?
  □ SQL injection, XSS, CSRF protegidos?
  □ Dados sensíveis (PII) logados ou expostos?
  □ Autenticação/autorização verificadas?
  □ Rate limiting em endpoints públicos?

RESILIÊNCIA
  □ Erros tratados graciosamente? (não só console.log)
  □ Timeouts configurados para chamadas externas?
  □ Circuit breaker para dependências instáveis?
  □ Retries com exponential backoff?
  □ Graceful degradation quando dependência falha?

OPERABILIDADE
  □ Logs estruturados nos pontos certos?
  □ Métricas de negócio emitidas?
  □ Tracing propagado corretamente?
  □ Health check atualizado se nova dependência?
  □ Feature flag para rollback sem deploy?

COMPATIBILIDADE
  □ Migration de banco é backwards compatible?
  □ API pública mantém backward compatibility?
  □ Mudanças de schema versionadas?
  □ Config nova tem valor default seguro?

TESTABILIDADE
  □ Testes unitários para lógica de negócio?
  □ Testes de integração para I/O?
  □ Testes cobrem caminhos de erro?
  □ Mocks/stubs usados corretamente?

Como Dar Feedback Construtivo

RUIM (hostil, vago):
  "Isso está errado."
  "Refatora tudo."
  "Não faça assim."

BOM (específico, educativo, com alternativa):
  "Nit: considere extrair esta lógica para um método privado
   para melhorar testabilidade. Algo como:
   `private calculateDiscount(tier: UserTier): number`"

  "Pergunta: este endpoint está sem rate limiting. É intencional?
   Se for público, pode ser vetor de DDoS."

  "Sugestão (não-bloqueante): este try/catch genérico engole erros
   silenciosamente. Considere logar com contexto:
   `logger.error('Failed to process order', { orderId, error })`"

PREFIXOS ÚTEIS:
  "Nit:" → nitpick, não bloqueia merge
  "Pergunta:" → genuinamente quer entender
  "Sugestão:" → melhoria opcional
  "Blocker:" → precisa ser resolvido antes do merge
  "FYI:" → informação para o autor, não precisa mudar nada

Cognitive Load Theory Aplicada à Leitura de Código

Os Três Tipos de Carga Cognitiva

1. Carga Intrínseca (complexidade inerente do problema)
   → Não pode ser reduzida — é a complexidade do domínio
   → Exemplo: regras de cálculo tributário brasileiro

2. Carga Extrínseca (complexidade acidental do código)
   → PODE e DEVE ser reduzida
   → Nomes ruins, abstrações confusas, indireção excessiva
   → Exemplo: 7 camadas de abstração para salvar no banco

3. Carga Relevante (effort para construir modelo mental)
   → Necessária para aprendizado
   → Exemplo: entender o padrão de eventos do sistema

Técnicas para Reduzir Carga Cognitiva ao Ler

Técnica                         | Como aplicar
--------------------------------|------------------------------------
Chunking                        | Agrupe arquivos por módulo, não
                                | tente ler tudo de uma vez

Modelo mental incremental       | Desenhe diagrama enquanto lê,
                                | atualize conforme descobre mais

Rubber duck reading             | Explique em voz alta o que cada
                                | parte faz — expõe gaps de entendimento

Limitar escopo                  | Foque em UMA feature por vez,
                                | ignore módulos irrelevantes

Notas como cache externo        | Mantenha um doc com descobertas —
                                | sua memória de trabalho é limitada

Intervalos                      | A cada 25-30min, pause e consolide
                                | o que aprendeu (técnica Pomodoro)

Pattern Recognition: Identificando Padrões Rapidamente

Com experiência, você reconhece padrões instantaneamente:

Código que você vê                    | Padrão reconhecido
--------------------------------------|---------------------------
array.map().filter().reduce()         | Pipeline de transformação
new Map() + has/get/set               | Cache/memoização manual
EventEmitter + on/emit                | Observer/Pub-Sub
middleware(req, res, next)            | Chain of Responsibility
retry(fn, { maxAttempts, backoff })   | Retry com backoff
Promise.allSettled([...])             | Fan-out resiliente
Symbol.iterator + yield              | Iterator/Generator
Proxy/Reflect                        | Metaprogramação/AOP
WeakMap + class instance key          | Private data pattern
Object.freeze(config)                 | Immutable config

Referências

- "Code Reading: The Open Source Perspective" — Diomidis Spinellis
- "Working Effectively with Legacy Code" — Michael Feathers
- "Software Design X-Rays" — Adam Tornhill (análise de hotspots)
- "Your Code as a Crime Scene" — Adam Tornhill
- "Cognitive Load Theory" — Sweller, Ayres & Kalyuga
- "A Philosophy of Software Design" — John Ousterhout