Se você já abriu um script antigo e teve medo de mexer nele, você sabe o que é código espaguete: lógica emaranhada, dependências invisíveis e a sensação de que qualquer mudança pode quebrar algo do outro lado do arquivo. Com a IA generativa acelerando a escrita de código, essa dívida técnica tende a crescer ainda mais rápido — e refatorar bem virou uma habilidade essencial, não um luxo.
Neste guia, você vai ver, passo a passo, como transformar uma função Python bagunçada em código limpo e testável, usando conceitos que valem para qualquer projeto — do script de análise de dados ao serviço em produção.
✅ O que você ganha
- Funções com responsabilidade única, fáceis de ler e de testar
- Menos bugs causados por efeitos colaterais e ordem de execução
- Testes que apontam exatamente onde está o problema
- Tipagem explícita que deixa o linter pegar erros antes da execução
- Um padrão replicável para qualquer código que “cresceu demais”
⚠️ O que você NÃO ganha
- Refatorar não corrige lógica de negócio errada — só a torna mais visível
- Não é uma bala de prata: exige disciplina e aumenta o número de funções no arquivo
- Dataclasses e tipagem adicionam um pouco de verbosidade em scripts muito pequenos
Requisitos
| Componente | Mínimo | Recomendado |
|---|---|---|
| Python | 3.8 | 3.11+ |
| Editor | Qualquer um | VS Code + extensão Python |
| Testes | assert simples | pytest |
| Conhecimento prévio | Funções e dicionários | Dataclasses e type hints |
| Tempo estimado | 30–45 minutos | |
O ponto de partida: uma função que faz tudo
Considere uma função que processa pedidos de uma loja online. Ela calcula desconto, atualiza estoque e “envia” e-mail — tudo no mesmo loop:
inventory = {"sku-1042": 18, "sku-2077": 4}
def process_order(order):
total = 0
for item in order["items"]:
price = item["unit_price"] * item["quantity"]
if order["customer_type"] == "vip":
price = price * 0.85
elif order["customer_type"] == "regular" and total > 100:
price = price * 0.95
total += price
if item["sku"] in inventory:
inventory[item["sku"]] -= item["quantity"]
else:
print(f"Warning: {item['sku']} not found in inventory")
if total > 500:
shipping = 0
else:
shipping = 12.99
total += shipping
print(f"Sending confirmation email to {order['customer_email']}")
print(f"Order total: ${total:.2f}")
return totalHá um bug escondido aí: o desconto de cliente comum verifica total > 100no meio do loop, quando o total ainda está incompleto. O resultado depende da ordem em que os itens aparecem — um erro clássico de “lógica emaranhada”.
Os sinais de alerta para identificar código espaguete no seu projeto: uma função cujo nome não descreve tudo o que ela faz, uma variável que muda de significado ao longo da função, e qualquer cálculo que dependa da ordem em que as instruções executam.
Passo 1 — Separe cada responsabilidade
Dê a cada tarefa a sua própria função, com entrada e retorno claros:
def calculate_subtotal(items):
return sum(item.unit_price * item.quantity for item in items)
def apply_discount(subtotal, customer_type):
if customer_type == "vip":
return subtotal * 0.85
if customer_type == "regular" and subtotal > 100:
return subtotal * 0.95
return subtotal
def calculate_shipping(discounted_total):
return 0.0 if discounted_total > 500 else 12.99Agora apply_discount trabalha com o subtotal finalizado, não com um total em andamento — o bug de ordem de execução desaparece como consequência direta da separação. Cada função pode ser chamada isoladamente e você sabe exatamente o que ela faz.
Passo 2 — Troque dicionários por dataclasses
Passar dicionários com chaves de string funciona, mas não garante quais campos existem nem seus tipos. Uma dataclass dá estrutura explícita:
from dataclasses import dataclass
@dataclass
class OrderItem:
sku: str
unit_price: float
quantity: int
@dataclass
class Order:
customer_email: str
customer_type: str
items: list[OrderItem]Com o formato definido, o restante do código escreve contra uma forma conhecida, em vez de adivinhar chaves de dicionário. O linter consegue pegar, por exemplo, uma chamada que passa um dict onde deveria vir um Order.
Passo 3 — Transforme a função em coordenadora
def process_order(order: Order, inventory: dict) -> float:
subtotal = calculate_subtotal(order.items)
discounted = apply_discount(subtotal, order.customer_type)
total = discounted + calculate_shipping(discounted)
update_inventory(order.items, inventory)
return totalprocess_order agora é uma coordenadora, não uma trabalhadora. Ler de cima para baixo conta a história completa: calcular, aplicar desconto, calcular frete, atualizar estoque.
Passo 4 — Levante erros em vez de imprimir avisos
A função original imprimia um aviso quando um SKU não existia e seguia em frente — ou seja, um item faltando nunca interrompia nada. A versão correta falha explicitamente no ponto em que ocorre:
def update_inventory(items, inventory):
for item in items:
if item.sku not in inventory:
raise ValueError(f"{item.sku} not found in inventory")
inventory[item.sku] -= item.quantityLançar uma exceção torna a falha explícita e impede que o pedido continue quando a atualização de estoque não foi concluída. Também facilita detectar o problema em testes e rastrear na hora de depurar.
Passo 5 — Teste cada peça isoladamente
def test_apply_discount_vip():
assert apply_discount(200, "vip") == 170.0
def test_apply_discount_regular_under_threshold():
assert apply_discount(80, "regular") == 80Com o pytest, um teste que falha aponta direto para a regra de desconto — em vez de um relatório vago de “o total do pedido veio errado” sem indicar qual das quatro responsabilidades falhou.
Casos de uso reais
- ETL de dados: separar extração, transformação e carga em funções puras facilita testar cada etapa sem rodar o pipeline inteiro
- APIs: validar entrada em uma função dedicada e devolver erros estruturados evita validação espalhada pelo handler
- Scripts de CI/CD: funções pequenas e testáveis tornam o pipeline de deploy menos frágil a mudanças
- Código legado: aplicar o padrão uma função por vez mantém o script funcionando durante toda a refatoração
Troubleshooting
- ❌ TypeError ao acessar chave de dicionário → Causa: ainda passando
dictonde deveria usar a dataclass. Solução: substitua o acesso por atributos (order.customer_type) e atualize as chamadas. - ❌ Teste falha com valor inesperado de desconto → Causa: cálculo dependendo de estado parcial. Solução: garanta que o desconto receba o subtotal final, não um total em andamento.
- ❌ ValueError de SKU não encontrado em produção → Causa: erro agora é explícito, em vez de só um print. Solução: trate a exceção no chamador ou valide o estoque antes do loop.
- ❌ Linter reclama de tipo incorreto → Causa: type hints adicionados. Solução: alinhe as assinaturas das funções com as dataclasses.
- ❌ Import circular ao extrair funções → Causa: módulos com dependência mútua. Solução: mova as funções puras para um módulo compartilhado sem dependências.
FAQ
- Preciso refatorar o código inteiro de uma vez? Não. O padrão recomendado é uma função por vez, mantendo o script funcionando a cada passo e facilitando a revisão.
- Dataclasses são obrigatórias? Não, mas ajudam quando os dados têm estrutura conhecida. Para scripts triviais, tuplas nomeadas ou dicionários simples bastam.
- Quando vale a pena usar pytest em vez de assert? Quando você quer relatórios claros, parametrização de testes e integração com CI. Para um teste rápido, assert simples resolve.
- Refatorar pode introduzir bugs? Sim, se feita sem testes. Extraia uma função, escreva o teste correspondente e só então avance para a próxima.
- Isso se aplica a código de IA? Sim — pipelines de ML, pré-processamento e agentes sofrem dos mesmos problemas de acoplamento e são candidatos naturais à refatoração.
O caminho à frente
Com ferramentas de IA escrevendo cada vez mais código, a capacidade de ler e refatorar se tornou mais valiosa que a de escrever do zero. O padrão apresentado aqui — separar responsabilidades, modelar dados explicitamente e testar cada peça — tende a ser a linha de frente da manutenção de software na era dos agentes de código. Quem domina isso consegue manter sistemas legíveis e seguros mesmo quando o volume de código cresce rápido.
Descubra mais sobre noticiAI
Assine para receber nossas notícias mais recentes por e-mail.



