desenvolvedor estudando código legado com repetição espaçada em Python

Spaced Repetition para Codebases: O Sistema que Te Faz Dominar Qualquer Projeto Legado em 7 Dias (Python + SQLite)

Você já herdou um projeto com 47 mil linhas de código, zero documentação, e o prazo de “entender tudo” era ontem? Eu já. E falhei miseravelmente nas primeiras três tentativas.

O erro não era falta de inteligência. Era falta de método. Eu tentava ler código como quem lê um romance — do início ao fim, esperando que a narrativa fizesse sentido. Spoiler: não faz. Codebases não são histórias lineares. São grafos semânticos com dependências cruzadas, e seu cérebro não foi projetado para absorver isso de uma vez.

A solução que mudou tudo? Spaced repetition — a mesma técnica que estudantes de medicina usam para memorizar anatomia, adaptada para dominar código legado. Não é mágica. É neurociência aplicada com Python e SQLite.

O Problema: Por Que Você Esquece 80% do Código que Lê

A curva do esquecimento de Ebbinghaus é implacável. Sem revisão, você perde 80% do que aprendeu em 48 horas. Isso vale para vocabulário alemão, fórmulas de química e — adivinhe — a arquitetura daquele microsserviço que você debugou na terça-feira.

Quando você abre um projeto legado pela primeira vez, seu cérebro faz o seguinte:

  1. Scan superficial: identifica padrões visuais (nomes de arquivos, estrutura de pastas)
  2. Busca de âncoras: tenta conectar o que vê com conhecimento prévio
  3. Sobrecarga cognitiva: desiste quando a quantidade de informação nova excede a capacidade de processamento

Resultado: você fecha o editor, abre o Twitter, e volta no dia seguinte tendo esquecido 90% do que “entendeu”.

A Ciência: Como Spaced Repetition Hackeia Sua Memória

Spaced repetition funciona porque respeita o timing biológico da consolidação de memória. Cada vez que você revisa informação no momento certo — logo antes de esquecê-la — a conexão sináptica se fortalece exponencialmente.

O algoritmo SM-2 (SuperMemo 2), criado por Piotr Woźniak em 1987, calcula intervalos ótimos de revisão baseado em:

  • Qualidade da resposta: quão facilmente você lembrou (0-5)
  • Ease factor: dificuldade intrínseca do item
  • Intervalo anterior: tempo desde a última revisão

A fórmula é elegante:

def calculate_next_interval(quality, ease_factor, previous_interval):
    if quality >= 3:
        if previous_interval == 0:
            next_interval = 1
        elif previous_interval == 1:
            next_interval = 6
        else:
            next_interval = round(previous_interval * ease_factor)
        ease_factor = max(1.3, ease_factor + (0.1 - (5 - quality) * (0.08 + (5 - quality) * 0.02)))
    else:
        next_interval = 1
        ease_factor = max(1.3, ease_factor - 0.2)
    return next_interval, ease_factor
código Python sendo revisado com técnica de repetição espaçada para dominar codebases legadas
Revisão de código Python: o sistema te mostra trechos no momento exato para consolidação máxima.

Adaptando SM-2 para Codebases: O Que Você Precisa Memorizar

Aqui está o pulo do gato: você não vai memorizar código linha por linha. Isso é inútil. O objetivo é construir um mapa mental funcional do projeto — entender responsabilidades, dependências e fluxos críticos.

Os “cards” do seu sistema de spaced repetition devem cobrir:

1. Módulos e Responsabilidades

card = {
    "question": "Qual a responsabilidade do módulo invoice_reconciliation.py?",
    "answer": "Compara invoices recebidas do Stripe com transações locais, detecta discrepancies e gera report diário.",
    "file_path": "services/payments/invoice_reconciliation.py",
    "dependencies": ["stripe_client", "transaction_logger", "report_generator"]
}

2. Fluxos Críticos

card = {
    "question": "Trace o fluxo completo de um pedido desde o checkout até a fulfillment queue.",
    "answer": "checkout_handler -> order_validator -> payment_processor -> inventory_reservation -> fulfillment_queue",
    "entry_point": "routes/checkout.py:45",
    "exit_point": "workers/fulfillment_consumer.py:12"
}

3. Decisões de Design

card = {
    "question": "Por que o sistema usa event sourcing no módulo de inventory em vez de CRUD tradicional?",
    "answer": "Precisa de audit trail completo para compliance SOX. Cada mudança de estoque gera evento imutável. Estado atual é projeção derivada.",
    "related_files": ["events/inventory_events.py", "projections/stock_level.py"]
}

4. APIs e Contratos

card = {
    "question": "Qual o contrato da API interna /internal/user/permissions?",
    "answer": "POST {user_id, resource, action} -> {allowed: bool, reason: str?}. Rate limit: 100 req/min. Auth: service-to-service JWT.",
    "defined_in": "api/internal/permissions.py:78"
}

5. Gotchas e Armadilhas

card = {
    "question": "Por que NUNCA chamar cache.invalidate_all() em produção?",
    "answer": "Faz flush síncrono de 2GB de cache Redis, causando thundering herd nos databases. Usa cache.invalidate_pattern() com TTL staggered.",
    "incident_history": "INC-2022-0847, 3h downtime"
}

Implementação: Python + SQLite em 150 Linhas

Chega de teoria. Vamos ao código. O sistema completo cabe num arquivo Python e usa SQLite como storage — zero dependências externas.

import sqlite3
import json
from datetime import datetime, timedelta
from pathlib import Path

class CodebaseSpacedRepetition:
    def __init__(self, db_path="codebase_sr.db"):
        self.db_path = db_path
        self._init_db()

    def _init_db(self):
        with sqlite3.connect(self.db_path) as conn:
            conn.execute("""
                CREATE TABLE IF NOT EXISTS cards (
                    id INTEGER PRIMARY KEY,
                    question TEXT NOT NULL,
                    answer TEXT NOT NULL,
                    file_path TEXT,
                    metadata TEXT,
                    ease_factor REAL DEFAULT 2.5,
                    interval_days INTEGER DEFAULT 0,
                    repetitions INTEGER DEFAULT 0,
                    next_review TEXT NOT NULL,
                    created_at TEXT NOT NULL
                )
            """)

    def add_card(self, question, answer, file_path=None, metadata=None):
        now = datetime.now()
        with sqlite3.connect(self.db_path) as conn:
            cursor = conn.execute(
                "INSERT INTO cards (question, answer, file_path, metadata, next_review, created_at) VALUES (?, ?, ?, ?, ?, ?)",
                (question, answer, file_path, json.dumps(metadata or {}), now.isoformat(), now.isoformat())
            )
            return cursor.lastrowid

    def get_due_cards(self, limit=20):
        now = datetime.now()
        with sqlite3.connect(self.db_path) as conn:
            conn.row_factory = sqlite3.Row
            rows = conn.execute(
                "SELECT * FROM cards WHERE next_review <= ? ORDER BY next_review ASC LIMIT ?",
                (now.isoformat(), limit)
            ).fetchall()
            return [dict(row) for row in rows]

    def review_card(self, card_id, quality):
        with sqlite3.connect(self.db_path) as conn:
            card = conn.execute("SELECT * FROM cards WHERE id = ?", (card_id,)).fetchone()
            ease = card[5]
            interval = card[6]
            reps = card[7]
            if quality >= 3:
                if reps == 0:
                    interval = 1
                elif reps == 1:
                    interval = 6
                else:
                    interval = round(interval * ease)
                ease = max(1.3, ease + (0.1 - (5 - quality) * (0.08 + (5 - quality) * 0.02)))
                reps += 1
            else:
                interval = 1
                ease = max(1.3, ease - 0.2)
                reps = 0
            next_review = datetime.now() + timedelta(days=interval)
            conn.execute(
                "UPDATE cards SET ease_factor=?, interval_days=?, repetitions=?, next_review=? WHERE id=?",
                (ease, interval, reps, next_review.isoformat(), card_id)
            )
codebase colorida no editor representando projeto legado complexo para estudo incremental
Projetos legados parecem caóticos até você aplicar um método sistemático de absorção incremental.

Workflow Diário: 20 Minutos que Mudam Tudo

Aqui está como usar o sistema na prática:

Dia 1-3: Extração de Cards

Abra o projeto e comece a criar cards. Não tente entender tudo. Foque em:

  • Arquivos que aparecem em múltiplos imports
  • Funções com nomes misteriosos (process_data_v2_final.py)
  • Comentários com “TODO” ou “HACK” ou “FIX LATER”
  • Configurações que parecem arbitrárias

Meta: 30-50 cards por dia. Leva 2-3 horas.

Dia 4-7: Revisão Intensiva

Agora o sistema entra em ação. Todo dia:

  1. Abra o script e rode get_due_cards(limit=30)
  2. Para cada card, tente responder em voz alta antes de ver a resposta
  3. Avalie honestamente: 0-2 (errei), 3 (acertei mas foi difícil), 4 (bom), 5 (fácil demais)
  4. Se errou, abra o arquivo relacionado e leia o contexto por 5 minutos

Tempo total: 20-30 minutos por dia.

# Script de revisão diária
from codebase_sr import CodebaseSpacedRepetition

sr = CodebaseSpacedRepetition()
due = sr.get_due_cards(limit=30)

print(f"Cards para revisar hoje: {len(due)}")

for i, card in enumerate(due, 1):
    print(f"Card {i}/{len(due)}")
    print(f"Pergunta: {card['question']}")
    input("Pressione ENTER para ver a resposta...")
    print(f"Resposta: {card['answer']}")
    if card['file_path']:
        print(f"Arquivo: {card['file_path']}")
    quality = int(input("Qualidade (0-5): "))
    sr.review_card(card['id'], quality)

print("Revisão completa!")

Automatizando a Extração: Parse Seu Projeto

Criar cards manualmente é tedioso. Vamos automatizar a extração de informações estruturais:

import ast
from pathlib import Path

def extract_module_cards(project_path):
    cards = []
    for py_file in Path(project_path).rglob("*.py"):
        if "test" in str(py_file) or "__pycache__" in str(py_file):
            continue
        try:
            with open(py_file) as f:
                tree = ast.parse(f.read())
            module_doc = ast.get_docstring(tree)
            if module_doc and len(module_doc) > 20:
                cards.append({
                    "question": f"Qual a responsabilidade de {py_file.name}?",
                    "answer": module_doc,
                    "file_path": str(py_file),
                    "metadata": {"auto_generated": True}
                })
            for node in ast.walk(tree):
                if isinstance(node, (ast.ClassDef, ast.FunctionDef)):
                    if not node.name.startswith("_"):
                        doc = ast.get_docstring(node)
                        if doc:
                            cards.append({
                                "question": f"O que faz {node.name} em {py_file.name}?",
                                "answer": doc,
                                "file_path": str(py_file),
                                "metadata": {"auto_generated": True}
                            })
        except SyntaxError:
            continue
    return cards

cards = extract_module_cards("/path/to/legacy/project")
print(f"Extraídos {len(cards)} cards automaticamente")

Métricas: Como Saber se Está Funcionando

Depois de 7 dias, você deve conseguir:

  1. Explicar a arquitetura em 2 minutos: desenhe o diagrama de componentes principais sem consultar código
  2. Responder “onde está X?” em 10 segundos: se alguém perguntar onde está a lógica de autenticação, você sabe o caminho
  3. Identificar 3 gotchas críticos: armadilhas que causariam bugs se você não soubesse
  4. Navegar sem grep: abrir arquivos diretamente pelo nome, sem buscar

Se não conseguir isso, ajuste o volume de cards ou a qualidade das perguntas. O sistema funciona — se você alimentar com informação certa.

Integração com Seu Workflow Existente

Não precisa abandonar suas ferramentas. Integre:

Git Hook: Revisão Automática Após Merge

#!/bin/bash
# .git/hooks/post-merge
python3 -c "
from codebase_sr import CodebaseSpacedRepetition
sr = CodebaseSpacedRepetition()
due = sr.get_due_cards(limit=5)
if due:
    print(f'Você tem {len(due)} cards para revisar')
"

Slack Reminder

import requests
from codebase_sr import CodebaseSpacedRepetition

sr = CodebaseSpacedRepetition()
due_count = len(sr.get_due_cards(limit=100))

if due_count > 0:
    requests.post(webhook_url, json={
        "text": f"Hora de revisar a codebase! {due_count} cards esperando."
    })

Quando NÃO Usar Spaced Repetition

Seja honesto: essa técnica não serve para tudo.

Não use quando:

  • O projeto será descartado em menos de 2 semanas
  • Você só precisa fazer uma correção pontual e sair
  • A codebase tem menos de 500 linhas (leia direto)
  • Você tem acesso ao autor original e pode fazer pair programming

Use quando:

  • Você será responsável pelo projeto por 1+ mês
  • Precisa fazer onboarding de outros devs depois
  • O projeto tem múltiplas camadas e dependências cruzadas
  • Documentação é inexistente ou desatualizada

Próximos Passos: Sua Codebase Espera

Aqui está o que você vai fazer agora:

  1. Baixe o código completo deste artigo
  2. Escolha um projeto legado que te assombra
  3. Passe 2 horas criando os primeiros 40 cards
  4. Revise por 7 dias consecutivos (20 min/dia)
  5. No dia 8, tente explicar a arquitetura para alguém

Se conseguir explicar sem gaguejar, você venceu. Se não, ajuste e continue.

Spaced repetition não é sexy. Não tem hype no Twitter. Não vai te dar estrela no GitHub. Mas vai te dar algo que 99% dos devs não têm: domínio real sobre código complexo, sem depender de sorte ou talento natural.

A melhor hora para começar a entender aquela codebase foi há 7 dias. A segunda melhor hora é agora.


Qual projeto legado você vai dominar primeiro? Me conta nos comentários — e se quiser ver um post sobre como gerar cards automaticamente a partir de AST de outras linguagens (TypeScript, Go, Rust), avisa aí que eu monto o parser.

Se curtiu essa abordagem de aprendizado acelerado, dá uma olhada na categoria Mente Binária — tem mais conteúdo sobre como hackear sua cognição com código.

Posts Similares

Deixe um comentário

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