Arquitetura softwareFrontend webTypeScriptAnti-patterns & Code Smells
Refactoring em TypeScript
Baixar PDFTécnicas de refatoração — como transformar código problemático em código limpo, passo a passo, com exemplos full-stack.
Refactoring em TypeScript
Refactoring é alterar a estrutura interna do código sem alterar seu comportamento externo. Os testes garantem que o comportamento não muda — por isso testes vêm antes do refactoring.
Regra de ouro
"Primeiro torne o código certo. Depois torne o código rápido." — Kent Beck
1. Extract Method / Extract Function
Antes
function processOrder(order: Order) {
// Validação — misturada com lógica
if (!order.items || order.items.length === 0) {
throw new Error('Pedido sem itens');
}
if (order.total <= 0) {
throw new Error('Total inválido');
}
// Cálculo de frete — misturada com lógica
const weight = order.items.reduce((sum, item) => sum + item.weight * item.quantity, 0);
const shippingCost = weight > 30 ? 50 : weight > 10 ? 25 : 10;
// Criação do registro — misturada com lógica
const record = {
...order,
shippingCost,
grandTotal: order.total + shippingCost,
createdAt: new Date(),
status: 'confirmed',
};
// Persistência
db.orders.insert(record);
}Depois
function validateOrder(order: Order): void {
if (!order.items?.length) throw new Error('Pedido sem itens');
if (order.total <= 0) throw new Error('Total inválido');
}
function calculateShipping(items: OrderItem[]): number {
const weight = items.reduce((sum, item) => sum + item.weight * item.quantity, 0);
if (weight > 30) return 50;
if (weight > 10) return 25;
return 10;
}
function createOrderRecord(order: Order, shippingCost: number): OrderRecord {
return {
...order,
shippingCost,
grandTotal: order.total + shippingCost,
createdAt: new Date(),
status: 'confirmed',
};
}
function processOrder(order: Order): void {
validateOrder(order);
const shippingCost = calculateShipping(order.items);
const record = createOrderRecord(order, shippingCost);
db.orders.insert(record);
}2. Replace Conditional com Polymorphism
Antes
function calculatePrice(type: string, basePrice: number): number {
if (type === 'physical') {
return basePrice * 1.1; // +10% imposto
} else if (type === 'digital') {
return basePrice * 0.95; // -5% sem frete
} else if (type === 'subscription') {
return basePrice * 0.8; // -20% assinatura
}
return basePrice;
}Depois
interface PricingStrategy {
calculate(basePrice: number): number;
}
class PhysicalProductPricing implements PricingStrategy {
calculate(basePrice: number): number {
return basePrice * 1.1;
}
}
class DigitalProductPricing implements PricingStrategy {
calculate(basePrice: number): number {
return basePrice * 0.95;
}
}
class SubscriptionPricing implements PricingStrategy {
calculate(basePrice: number): number {
return basePrice * 0.8;
}
}
// Registry
const pricingStrategies: Record<string, PricingStrategy> = {
physical: new PhysicalProductPricing(),
digital: new DigitalProductPricing(),
subscription: new SubscriptionPricing(),
};
function calculatePrice(type: string, basePrice: number): number {
const strategy = pricingStrategies[type];
if (!strategy) return basePrice;
return strategy.calculate(basePrice);
}3. Inline Variable / Inline Function
Às vezes, uma variável ou função intermediária só adiciona ruído.
Antes
const basePrice = order.total;
const discount = basePrice * 0.1;
const finalPrice = basePrice - discount;
return finalPrice;Depois
return order.total * 0.9;Antes (função)
function getFullName(user: User): string {
return `${user.firstName} ${user.lastName}`;
}
const greeting = `Olá, ${getFullName(user)}`;Depois (se usada apenas uma vez)
const greeting = `Olá, ${user.firstName} ${user.lastName}`;4. Replace Magic Number com Constante
Antes
if (items.length > 10) { /* ... */ }
if (retryCount > 3) { /* ... */ }
setTimeout(() => refresh(), 300000);Depois
const MAX_ITEMS_PER_PAGE = 10;
const MAX_RETRY_COUNT = 3;
const REFRESH_INTERVAL_MS = 5 * 60 * 1000; // 5 minutos
if (items.length > MAX_ITEMS_PER_PAGE) { /* ... */ }
if (retryCount > MAX_RETRY_COUNT) { /* ... */ }
setTimeout(() => refresh(), REFRESH_INTERVAL_MS);5. Introduce Parameter Object
Antes
function createUser(
firstName: string,
lastName: string,
email: string,
phone: string,
address: string,
city: string,
state: string,
zip: string,
country: string,
) { /* ... */ }Depois
interface Address {
street: string;
city: string;
state: string;
zip: string;
country: string;
}
interface CreateUserInput {
firstName: string;
lastName: string;
email: string;
phone: string;
address: Address;
}
function createUser(input: CreateUserInput) { /* ... */ }6. Replace Callback com Async/Await
Antes
function getUser(id: string, callback: (err: Error | null, user?: User) => void) {
db.query('SELECT * FROM users WHERE id = $1', [id], (err, result) => {
if (err) return callback(err);
callback(null, result.rows[0]);
});
}
getUser('1', (err, user) => {
if (err) throw err;
getOrders(user.id, (err, orders) => {
if (err) throw err;
// ... callback hell
});
});Depois
async function getUser(id: string): Promise<User | null> {
const result = await db.query('SELECT * FROM users WHERE id = $1', [id]);
return result.rows[0] ?? null;
}
const user = await getUser('1');
if (!user) throw new NotFoundError('Usuário não encontrado');
const orders = await getOrders(user.id);7. Split Phase — Dividir em fases
Antes
async function handleCheckout(req: Request, res: Response) {
// Tudo misturado: validação, cálculo, pagamento, resposta
}Depois
async function handleCheckout(req: Request, res: Response) {
// Fase 1: Validação
const input = validateCheckoutInput(req.body);
if (!input.ok) return res.status(400).json(input.error);
// Fase 2: Cálculo
const order = await calculateOrder(input.value);
// Fase 3: Pagamento
const payment = await processPayment(order);
// Fase 4: Persistência
const saved = await saveOrder(order, payment);
// Fase 5: Resposta
return res.status(201).json(saved);
}Checklist de refactoring
Antes de começar a refatorar, verifique:
- Existem testes? Sem testes, refactoring é perigoso
- O código funciona hoje? Não refatore código quebrado — conserte primeiro
- O comportamento externo não muda? Refactoring = estrutura, não comportamento
- Estou fazendo uma mudança por vez? Não combine refactoring com feature nova
- Estou commitando após cada passo? Se algo quebrar, é fácil reverter