Kaique Mitsuo Silva Yamamoto
Arquitetura softwareFrontend webTypeScriptAnti-patterns & Code Smells

Refactoring em TypeScript

Té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

Referências

On this page