← dev-notes
REFACTORING · PARTE 2 DE 3

El Catálogo de
Técnicas

7 maniobras concretas organizadas por objetivo: componer, mover y simplificar

REFACTORING · P2
🔨 Extract Method 📦 Inline Method 🔍 Replace Temp with Query 🚚 Move Method / Field 🧩 Extract Class 🌿 Decompose Conditional 🔀 Replace Conditional with Polymorphism
A Componiendo métodos — organización interna
SMELLS: LONG FUNCTION · DUPLICATED CODE
TÉCNICA 01
🔨 Extract Method
Toma un fragmento de código que puede agruparse y muévelo a un método nuevo con un nombre que exprese su intención. Es la técnica más usada del catálogo y la base de casi todas las demás.
🤢 Cura: Long Function · Duplicated Code · Comments as Explanation
Señal para aplicar: Encuentras un comentario tipo // validar pedido o // calcular total. Ese comentario es el nombre del método que aún no existe. Cuando necesitas scroll para leer una función. Cuando el mismo bloque aparece dos veces.
processOrder() — antes❌ SMELL
void processOrder(Order order) {
  // validar
  if (order.getItems().isEmpty())
    throw new IllegalStateException("No items");
  if (order.getCustomer() == null)
    throw new IllegalStateException("No customer");
  // calcular total
  double total = 0;
  for (Item i : order.getItems())
    total += i.getPrice() * i.getQty();
  if (order.getCustomer().isPremium())
    total *= 0.9;
  order.setTotal(total);
  // notificar
  emailService.send(
    order.getCustomer().getEmail(),
    "Confirmado: $" + total);
}
processOrder() — después✅ EXTRAÍDO
void processOrder(Order order) {
  validateOrder(order);
  order.setTotal(calculateTotal(order));
  notifyCustomer(order);
}

private void validateOrder(Order o) {
  if (o.getItems().isEmpty())
    throw new IllegalStateException("No items");
  if (o.getCustomer() == null)
    throw new IllegalStateException("No customer");
}

private double calculateTotal(Order o) {
  double t = o.getItems().stream()
    .mapToDouble(i -> i.getPrice() * i.getQty())
    .sum();
  return o.getCustomer().isPremium() ? t * 0.9 : t;
}
TÉCNICA 02
📦 Inline Method
La operación inversa de Extract Method. Cuando el cuerpo del método es tan claro como su nombre, o cuando el método delega trivialmente a otro sin añadir semántica, elimínalo y pon el código directamente en el punto de llamada.
🤢 Cura: Indirection innecesaria · Cadenas de delegación que no aportan
Cuándo aplicar: El método tiene una sola línea y su nombre no aporta información adicional sobre el dominio. Un grupo de métodos mal descompuestos donde el nivel de abstracción es inconsistente. Atención: no apliques Inline si el método está en múltiples sitios o si el nombre tiene valor semántico para el dominio.
antes — doble delegación inútil❌ SMELL
boolean isEligibleForDiscount(Customer c) {
  return isPremiumLongTerm(c);
}

private boolean isPremiumLongTerm(Customer c) {
  return c.getMonthsSinceJoin() > 12
      && c.getTotalOrders() > 10;
}

// el nombre isEligibleForDiscount ya
// lo dice todo → inline isPremiumLongTerm
después — directo y legible✅ INLINE
boolean isEligibleForDiscount(Customer c) {
  return c.getMonthsSinceJoin() > 12
      && c.getTotalOrders() > 10;
}

// Una sola capa, criterio legible,
// fácil de modificar sin perder contexto.
// Si la lógica crece → Extract de nuevo.
TÉCNICA 03
🔍 Replace Temp with Query
En lugar de almacenar un resultado intermedio en una variable temporal, extrae la expresión en un método. La variable desaparece y el método puede reutilizarse en otros contextos del mismo objeto.
🤢 Cura: Primitive Obsession · Long Function · duplicación de expresiones
Cuándo aplicar: La variable temporal se calcula una vez y solo se lee. La expresión podría ser útil en otros métodos de la misma clase. Prerequisito DDD: Esta técnica es el primer paso hacia el patrón Query en CQRS — separar consulta de comando.
antes — variable temporal atada al método❌ SMELL
double getFinalPrice() {
  double basePrice = quantity * itemPrice;
  if (basePrice > 1000)
    return basePrice * 0.95;
  return basePrice;
  // basePrice solo existe aquí
  // si otra función la necesita: duplicado
}
después — query reutilizable✅ QUERY
double getFinalPrice() {
  return basePrice() > 1000
    ? basePrice() * 0.95
    : basePrice();
}

private double basePrice() {
  return quantity * itemPrice;
  // ahora cualquier método puede
  // llamar a basePrice() sin duplicar
}
B Moviendo características — distribución de responsabilidades
SMELLS: FEATURE ENVY · DATA CLUMPS · LARGE CLASS
TÉCNICA 04
🚚 Move Method / Move Field
Si un método o campo usa más datos de otra clase que los propios, muévelo allá. La heurística es simple: el código vive donde viven los datos que necesita. Esto elimina el smell Feature Envy y reduce el acoplamiento.
🤢 Cura: Feature Envy · Inappropriate Intimacy
Señal para aplicar: Un método llama a más getters de otra clase que de la propia. El acoplamiento entre clases es asimétrico — una clase "envidia" los datos de la otra. Regla: si un método usa 3+ datos de una clase B y 0-1 de la propia, pertenece a B.
antes — método envidioso❌ FEATURE ENVY
class OrderLine {
  private Product product;
  private int qty;

  double getDiscountedPrice() {
    // usa 3 datos de Product, 0 de OrderLine
    double base = product.getBasePrice();
    double tax  = product.getTaxRate();
    if (product.isOnSale())
      return (base * 0.9) * (1 + tax);
    return base * (1 + tax);
  }
}
después — método en su clase natural✅ MOVIDO
class Product {
  private double basePrice;
  private double taxRate;
  private boolean onSale;

  double getDiscountedPrice() {
    // ahora usa sus propios datos
    double base = onSale ? basePrice * 0.9 : basePrice;
    return base * (1 + taxRate);
  }
}

class OrderLine {
  double lineTotal() {
    return product.getDiscountedPrice() * qty;
  }
}
TÉCNICA 05
🧩 Extract Class
Cuando una clase hace el trabajo de dos, crea una segunda y mueve los campos y métodos que corresponden. La guía es la cohesión: todos los campos de una clase deben cambiar juntos por la misma razón.
🤢 Cura: Large Class · Data Clumps · Divergent Change · SRP violado
Señal para aplicar: Hay un subgrupo de campos que siempre se usa junto (candidato a Value Object). La clase tiene dos responsabilidades claramente distinguibles. Al documentarla sientes que necesitas la palabra "y" para describirla. Conexión DDD: Extract Class → Value Object o Entity según si tiene identidad.
antes — Customer con address embebido❌ LARGE CLASS
class Customer {
  String name;
  String street;   // ← datos de dirección
  String city;
  String zipCode;
  String country;

  String formatAddress() { /* ... */ }
  boolean isAddressValid() { /* ... */ }
  // Customer y Address mezclados → SRP roto
}
después — Address como Value Object✅ EXTRAÍDO
class Address {  // Value Object
  private final String street;
  private final String city;
  private final String zipCode;
  private final String country;

  String format() { /* ... */ }
  boolean isValid() { /* ... */ }
}

class Customer {
  String name;
  Address address;  // cohesión
}
C Simplificando condicionales — la firma de un senior
SMELLS: SWITCH STATEMENTS · DIVERGENT CHANGE · OCP ROTO
TÉCNICA 06
🌿 Decompose Conditional
Extrae la condición, la rama then y la rama else a métodos con nombre. El objetivo no es solo acortar el código — es dar nombre a la intención de cada rama para que el lector entienda el porqué sin ejecutarlo mentalmente.
🤢 Cura: Complex Conditional · Logic Buried in Conditionals
Señal para aplicar: La condición del if requiere más de una lectura para entenderse. Hay operadores lógicos compuestos (&&, ||, !) que combinan más de 2 conceptos. El cuerpo del then o else supera 3-4 líneas.
antes — condicional opaca❌ SMELL
if (date.before(SUMMER_START)
    || date.after(SUMMER_END)) {
  charge = qty * winterRate
         + winterServiceCharge;
} else {
  charge = qty * summerRate;
}
// ¿qué son SUMMER_START y END aquí?
// ¿qué significa winterServiceCharge?
// el lector debe inferir el modelo mental
después — intención nombrada✅ DESCOMPUESTO
charge = isWinter(date)
  ? winterCharge(qty)
  : summerCharge(qty);

private boolean isWinter(Date d) {
  return d.before(SUMMER_START)
      || d.after(SUMMER_END);
}
private double winterCharge(int qty) {
  return qty * winterRate + winterServiceCharge;
}
private double summerCharge(int qty) {
  return qty * summerRate;
}
TÉCNICA 07
🔀 Replace Conditional with Polymorphism
En lugar de un switch que chequea el tipo de un objeto para ejecutar lógica diferente, usa subclases e interfaces. Es la aplicación directa de OCP: agregar un tipo nuevo es agregar una clase, no modificar el switch.
🤢 Cura: Switch Statements · OCP roto · Divergent Change en el switch
Señal para aplicar: El mismo switch sobre el mismo campo aparece en dos o más métodos. Cuando aparece un nuevo tipo, hay que buscar y actualizar todos los switch. Esta es la conexión directa con GoF Strategy y el principio OCP de SOLID.
antes — switch que viola OCP❌ OCP ROTO
double calculateShipping(Order order) {
  switch (order.getShippingType()) {
    case EXPRESS:
      return order.getTotal() * 0.15;
    case STANDARD:
      return order.getTotal() * 0.05;
    case ECONOMY:
      return 2.99;
    default:
      throw new IllegalArgumentException();
  }
  // nuevo tipo → modificar aquí siempre
}
después — polimorfismo OCP-compliant✅ POLIMORFISMO
interface ShippingStrategy {
  double calculate(Order order);
}
class ExpressShipping implements ShippingStrategy {
  public double calculate(Order o) {
    return o.getTotal() * 0.15;
  }
}
class EconomyShipping implements ShippingStrategy {
  public double calculate(Order o) {
    return 2.99;
  }
}
// nuevo tipo = nueva clase, sin tocar nada
🔗
Conexión con GoF Strategy y SOLID OCP
Esta técnica materializa dos conceptos: el patrón Strategy (GoF) como mecanismo de implementación, y el principio Open-Closed (SOLID) como objetivo. El switch es literalmente OCP roto: cerrado a la extensión, abierto a la modificación.
Mapa rápido — las 7 técnicas de un vistazo
Técnica
Grupo
Smell que cura
Conexión SOLID/GoF
Extract Method
Componiendo
Long Function, Duplicated Code
SRP
Inline Method
Componiendo
Indirection innecesaria
Legibilidad
Replace Temp with Query
Componiendo
Duplicación de expresiones
DDD Query
Move Method / Field
Moviendo
Feature Envy
SRP · DIP
Extract Class
Moviendo
Large Class, Data Clumps
SRP Value Object
Decompose Conditional
Simplificando
Complex Conditional
Legibilidad
Replace Cond. with Polymorphism
Simplificando
Switch Statements
OCP Strategy
📚
Refactoring to Patterns — Joshua Kerievsky
Kerievsky lleva estas técnicas un paso más allá: a veces el destino del refactoring no es solo código más limpio, sino la emergencia de un patrón GoF completo. Replace Conditional with Polymorphism evoluciona al patrón Strategy. Extract Class puede terminar siendo un Decorator o un Composite. El catálogo de Fowler es el camino; los patrones GoF son algunos de los destinos.