Resumo
Extrair Método (Extract Method/Function) é a refatoração de pegar um trecho de código dentro de uma função maior e movê-lo para uma função separada com um nome descritivo. É simples, mecânica, e provavelmente a refatoração mais poderosa do catálogo de Fowler.
Um bom nome de função elimina comentários, documenta intenção e permite que o código seja lido como uma narrativa.
O problema
Funções longas são difíceis de entender porque misturam níveis de abstração e responsabilidades:
async function finalizarCheckout(carrinho: Carrinho, usuario: Usuario) {
// validar itens
if (carrinho.itens.length === 0) throw new Error("Carrinho vazio");
for (const item of carrinho.itens) {
if (item.quantidade <= 0) throw new Error(`Quantidade inválida: ${item.nome}`);
if (item.preco <= 0) throw new Error(`Preço inválido: ${item.nome}`);
}
// calcular total
let subtotal = 0;
for (const item of carrinho.itens) {
subtotal += item.preco * item.quantidade;
}
const desconto = usuario.isPremium() ? subtotal * 0.1 : 0;
const total = subtotal - desconto;
// criar pedido
const pedido = await pedidoRepository.criar({
usuarioId: usuario.id,
itens: carrinho.itens,
total,
});
// notificar
await emailService.enviar({
para: usuario.email,
assunto: "Pedido confirmado",
corpo: `Seu pedido #${pedido.id} foi criado. Total: R$ ${total}`,
});
return pedido;
}Antes e depois
function calcularPrecoFinal(produto: Produto, usuario: Usuario) {
let preco = produto.preco;
if (new Date() >= produto.promocaoInicio &&
new Date() <= produto.promocaoFim) {
preco = preco * (1 - produto.desconto);
}
if (usuario.plano === "premium" || usuario.totalCompras > 10000) {
preco = preco * 0.95;
}
if (preco < 0) preco = 0;
return Math.round(preco * 100) / 100;
}function calcularPrecoFinal(produto: Produto, usuario: Usuario) {
const precoComPromocao = aplicarPromocao(produto);
const precoComDesconto = aplicarDescontoFidelidade(precoComPromocao, usuario);
return arredondar(Math.max(0, precoComDesconto));
}
function aplicarPromocao(produto: Produto) {
const agora = new Date();
const emPromocao = agora >= produto.promocaoInicio &&
agora <= produto.promocaoFim;
return emPromocao ? produto.preco * (1 - produto.desconto) : produto.preco;
}
function aplicarDescontoFidelidade(preco: number, usuario: Usuario) {
const DESCONTO_FIDELIDADE = 0.05;
return usuario.elegívelParaFidelidade()
? preco * (1 - DESCONTO_FIDELIDADE)
: preco;
}
function arredondar(valor: number) {
return Math.round(valor * 100) / 100;
}Além de mais legível, o segundo exemplo é testável por partes. Você pode testar aplicarPromocao sem precisar criar um usuário — e testar aplicarDescontoFidelidade sem precisar de um produto com promoção.
Como aplicar
O processo é mecânico:
- Identifique o bloco a extrair — geralmente um grupo de linhas com um propósito único
- Crie uma nova função com um nome que descreve o que o bloco faz (não como)
- Mova o código para a nova função
- Identifique os parâmetros — tudo que o bloco ler de fora se torna parâmetro
- Identifique o retorno — tudo que o bloco escrever para fora se torna valor de retorno
- Substitua o bloco original pela chamada à nova função
- Teste que o comportamento não mudou
Voltando ao exemplo do checkout:
// Após extrair métodos:
async function finalizarCheckout(carrinho: Carrinho, usuario: Usuario) {
validarCarrinho(carrinho);
const total = calcularTotal(carrinho.itens, usuario);
const pedido = await criarPedido(carrinho, usuario, total);
await notificarUsuario(usuario, pedido, total);
return pedido;
}Agora a função lê como um índice — você entende o fluxo geral sem precisar ler os detalhes.
Quando usar
Extraia um método quando:
- Um bloco de código precisa de um comentário para ser entendido
- Um trecho pode ser reutilizado em outro lugar
- Uma função tem mais de um nível de abstração (mistura "o quê" com "como")
- A função está ficando longa e difícil de ler de uma vez
Erros comuns
Extrair por tamanho, não por propósito: Uma função de 5 linhas com três responsabilidades ainda é um problema. Extraia por coesão de propósito, não por contagem de linhas.
Parâmetros demais na função extraída: Se a função extraída precisa de muitos parâmetros, é sinal de que o bloco extraído ainda é complexo demais ou que a função original está acoplada a muitos dados.
Extrair e deixar o nome ruim:
// Extraído, mas o nome não ajuda
function processar(itens: Item[]) { ... }
// Com nome que documenta a intenção
function calcularSubtotalComImposto(itens: Item[]) { ... }Conceitos relacionados
- Code Smells — "função longa" é o smell mais comum que indica necessidade de extrair método
- Refatoração Segura — como garantir que extrair método não quebra comportamento existente
- Separação de Responsabilidades — extrair método é uma das implementações práticas desse princípio