Curso – Claude

Catálogo de refactorización

Catálogo comisariado de técnicas de refactorización de Martin Fowler Refactorización (2a Edición). Cada refactorización incluye motivación, mecánica paso a paso y ejemplos.

"Una refactorización se define por su mecánica —la secuencia precisa de pasos que sigues para llevar a cabo el cambio." — Martin Fowler


Cómo usar este catálogo

  1. Identificar el olor utilizando la referencia de olores de código
  2. Encontrar la refactorización correspondiente en este catálogo
  3. Sigue a los mecánicos paso a paso
  4. Prueba después de cada paso para asegurar que se preserve el comportamiento

Regla de oro: Si cualquier paso toma más de 10 minutos, dividirlo en pasos más pequeños.


Refactorización más frecuente

Método de extracción

Cuándo usar: método largo, código duplicado, necesidad de nombrar un concepto

Motivación: Convertir un fragmento de código en un método cuyo nombre explica el propósito.

Mecánica2. Copiar el fragmento de código en el nuevo método 3. Escanear las variables locales utilizadas en el fragmento 4. Pasar las variables locales como parámetros (o declarar en el método) 5. Manipular los valores de retorno apropiadamente 6. Reemplazar el fragmento original con una llamada al nuevo método 7. Prueba

Antes:

function printOwing(invoice) {
  let outstanding = 0;

  console.log("***********************");
  console.log("**** Customer Owes ****");
  console.log("***********************");

  // Calculate outstanding
  for (const order of invoice.orders) {
    outstanding += order.amount;
  }

  // Print details
  console.log(`name: ${invoice.customer}`);
  console.log(`amount: ${outstanding}`);
}

Después:

function printOwing(invoice) {
  printBanner();
  const outstanding = calculateOutstanding(invoice);
  printDetails(invoice, outstanding);
}

function printBanner() {
  console.log("***********************");
  console.log("**** Customer Owes ****");
  console.log("***********************");
}

function calculateOutstanding(invoice) {
  return invoice.orders.reduce((sum, order) => sum + order.amount, 0);
}

function printDetails(invoice, outstanding) {
  console.log(`name: ${invoice.customer}`);
  console.log(`amount: ${outstanding}`);
}

Método en línea

Cuándo usar: El órgano del método es tan claro como su nombre, delegación excesiva

Motivación: Eliminar la indirecta innecesaria cuando el método no añade valor.

Mecánica: 1. Compruebe que el método no es polimórfico 2. Encuentre todas las llamadas al método 3. Reemplace cada llamada con el cuerpo del método 4. Prueba después de cada reemplazo 5. Quite la definición del método

Antes:

function getRating(driver) {
  return moreThanFiveLateDeliveries(driver) ? 2 : 1;
}

function moreThanFiveLateDeliveries(driver) {
  return driver.numberOfLateDeliveries > 5;
}

Después:

function getRating(driver) {
  return driver.numberOfLateDeliveries > 5 ? 2 : 1;
}

Variable de extracción

Cuándo usar: Expresión compleja que es difícil de entender

Motivación: Dar un nombre a una pieza de una expresión compleja.

Mecánica: 1. Asegúrese de que la expresión no tiene efectos secundarios 2. Declare una variable inmutable 3. Configure el resultado de la expresión (o parte) 4. Reemplace la expresión original con la variable 5. Test

Antes:

return order.quantity * order.itemPrice -
  Math.max(0, order.quantity - 500) * order.itemPrice * 0.05 +
  Math.min(order.quantity * order.itemPrice * 0.1, 100);

Después:

const basePrice = order.quantity * order.itemPrice;
const quantityDiscount = Math.max(0, order.quantity - 500) * order.itemPrice * 0.05;
const shipping = Math.min(basePrice * 0.1, 100);
return basePrice - quantityDiscount + shipping;

Variable en línea

Cuándo usar: Nombre variable no comunica más que la expresión

Motivación: Eliminar la indirecta innecesaria.

Mecánica1. Compruebe que el lado derecho no tiene efectos secundarios 2. Si la variable no es inmutable, hágalo así y pruebe 3. Encuentre la primera referencia y reemplácela con la expresión 4. Prueba 5. Repita para todas las referencias 6. Retire la declaración y asignación 7. Prueba 7.


Cambiar el nombre de la variable

Cuándo usar: El nombre no comunica claramente el propósito

Motivación: Los buenos nombres son cruciales para un código limpio.

Mecánica1. Si la variable es ampliamente utilizada, considere encapsular 2. Encuentre todas las referencias 3. Cambie cada referencia 4. Prueba

Consejos: - Usar nombres de intención-revelación - Evitar abreviaturas - Usar terminología de dominio

// Bad
const d = 30;
const x = users.filter(u => u.a);

// Good
const daysSinceLastLogin = 30;
const activeUsers = users.filter(user => user.isActive);

Cambiar declaración de función

Cuándo usar: El nombre de la función no explica el propósito, los parámetros necesitan cambiar

Motivación: Los nombres de buenas funciones hacen que el código se autodocumente.

Mecánica (Simple): 1. Quitar parámetros no necesarios 2. Cambiar el nombre 3. Añadir parámetros necesarios 4. Prueba

Mecánica (Migración - para cambios complejos)Si quitar parámetro, asegúrese de que no se utiliza 2. Crear nueva función con la declaración deseada 3. Tener función antigua llamada nueva función 4. Prueba 5. Cambiar llamadas para utilizar nueva función 6. Prueba después de cada 7. Eliminar función antigua

Antes:

function circum(radius) {
  return 2 * Math.PI * radius;
}

Después:

function circumference(radius) {
  return 2 * Math.PI * radius;
}

Encapsular variable

Cuándo usar: Acceso directo a los datos de múltiples lugares

Motivación: Proporcionar un punto de acceso claro para la manipulación de datos.

Mecánica2. Buscar todas las referencias 3. Reemplazar lee con getter 4. Reemplazar escribe con setter 5. Prueba después de cada cambio 6. Restringir la visibilidad de la variable

Antes:

let defaultOwner = { firstName: "Martin", lastName: "Fowler" };

// Used in many places
spaceship.owner = defaultOwner;

Después:

let defaultOwnerData = { firstName: "Martin", lastName: "Fowler" };

function defaultOwner() { return defaultOwnerData; }
function setDefaultOwner(arg) { defaultOwnerData = arg; }

spaceship.owner = defaultOwner();

Introducir el objeto del parámetro

Cuándo usar: Varios parámetros que frecuentemente van juntos

Motivación: Datos de grupo que naturalmente pertenecen a la misma.

Mecánica1. Crear una nueva clase/estructura para los parámetros agrupados 2. Prueba 3. Usar la declaración de función de cambio para añadir el nuevo objeto 4. Prueba 5. Para cada parámetro del grupo, quitarlo de la función y utilizar el nuevo objeto 6. Prueba después de cada uno

Antes:

function amountInvoiced(startDate, endDate) { ... }
function amountReceived(startDate, endDate) { ... }
function amountOverdue(startDate, endDate) { ... }

Después:

class DateRange {
  constructor(start, end) {
    this.start = start;
    this.end = end;
  }
}

function amountInvoiced(dateRange) { ... }
function amountReceived(dateRange) { ... }
function amountOverdue(dateRange) { ... }

Combinar funciones en clase

Cuándo usar: Varias funciones funcionan con los mismos datos

Motivación: Funciona en grupo con los datos en los que operan.

Mecánica2. Mueva cada función a la clase 3. Prueba después de cada movimiento 4. Reemplace los argumentos de datos con los usos de los campos de clase

Antes:

function base(reading) { ... }
function taxableCharge(reading) { ... }
function calculateBaseCharge(reading) { ... }

Después:

class Reading {
  constructor(data) { this._data = data; }

  get base() { ... }
  get taxableCharge() { ... }
  get calculateBaseCharge() { ... }
}

Fase de división

Cuándo usar: Código trata de dos cosas diferentes

Motivación: Separar el código en fases distintas con límites claros.

Mecánica1. Crear una segunda función para la segunda fase 2. Prueba 3. Introducir una estructura de datos intermedia entre fases 4. Prueba 5. Extraer la primera fase en su propia función 6. Prueba

Antes:

function priceOrder(product, quantity, shippingMethod) {
  const basePrice = product.basePrice * quantity;
  const discount = Math.max(quantity - product.discountThreshold, 0)
    * product.basePrice * product.discountRate;
  const shippingPerCase = (basePrice > shippingMethod.discountThreshold)
    ? shippingMethod.discountedFee : shippingMethod.feePerCase;
  const shippingCost = quantity * shippingPerCase;
  return basePrice - discount + shippingCost;
}

Después:

function priceOrder(product, quantity, shippingMethod) {
  const priceData = calculatePricingData(product, quantity);
  return applyShipping(priceData, shippingMethod);
}

function calculatePricingData(product, quantity) {
  const basePrice = product.basePrice * quantity;
  const discount = Math.max(quantity - product.discountThreshold, 0)
    * product.basePrice * product.discountRate;
  return { basePrice, quantity, discount };
}

function applyShipping(priceData, shippingMethod) {
  const shippingPerCase = (priceData.basePrice > shippingMethod.discountThreshold)
    ? shippingMethod.discountedFee : shippingMethod.feePerCase;
  const shippingCost = priceData.quantity * shippingPerCase;
  return priceData.basePrice - priceData.discount + shippingCost;
}

Características de movimiento

Mover método

Cuándo usar: Método utiliza más características de otra clase que su propia

Motivación: Colocar funciones con los datos que más utilizan.

Mecánica: 1. Examine todos los elementos de programa utilizados por método en su clase 2. Compruebe si el método es polimórfico 3. Copiar el método a la clase de destino 4. Ajustar para el nuevo contexto 5. Hacer que el método original delegado a la meta 6. Prueba 7. Considerar la eliminación del método original


Mover campo

Cuándo usar: El campo es usado más por otra clase

Motivación: Mantenga los datos con las funciones que lo utilizan.

Mecánica1. Encapsular el campo si no ya 2. Prueba 3. Crear campo en el objetivo 4. Actualizar las referencias para usar el campo de destino 5. Prueba 6. Eliminar el campo original


Mover las declaraciones a la función

Cuándo usar: El mismo código siempre aparece con una llamada de función

Motivación: Eliminar la duplicación moviendo código repetido en la función.

Mecánica: 1. Extraiga el código repetido en una función si no ya 2. Mueva las instrucciones a esa función 3. Prueba 4. Si los llamantes ya no necesitan declaraciones independientes, quítelas


Mover las declaraciones a los que llaman

Cuándo usar: El comportamiento común varía entre los que llaman

Motivación: Cuando el comportamiento necesita diferir, sacarlo de la función.

Mecánica: 1. Utilice el método de extracción en el código para mover 2. Use el método en línea en la función original 3. Retire la llamada ahora en línea 4. Mueva el código extraído a cada llamante 5. Prueba


Organización de los datos

Reemplazar Primitivo con Objeto

Cuándo usar: El elemento de datos necesita más comportamiento que valor simple

Motivación: Encapsular los datos con su comportamiento.

Mecánica1. Aplicar Variable de Encapsulado 2. Crear una clase de valor simple 3. Cambiar el set para crear una nueva instancia 4. Cambiar el getter para devolver el valor 5. Prueba 6. Agregar comportamiento más rico a la nueva clase.

Antes:

class Order {
  constructor(data) {
    this.priority = data.priority; // string: "high", "rush", etc.
  }
}

// Usage
if (order.priority === "high" || order.priority === "rush") { ... }

Después:

class Priority {
  constructor(value) {
    if (!Priority.legalValues().includes(value))
      throw new Error(`Invalid priority: ${value}`);
    this._value = value;
  }

  static legalValues() { return ['low', 'normal', 'high', 'rush']; }
  get value() { return this._value; }

  higherThan(other) {
    return Priority.legalValues().indexOf(this._value) >
           Priority.legalValues().indexOf(other._value);
  }
}

// Usage
if (order.priority.higherThan(new Priority("normal"))) { ... }

Reemplazar Temp con la consulta

Cuándo usar: Variable temporal sostiene el resultado de una expresión

Motivación: Haga el código más claro al extraer la expresión en una función.

Mecánica: 1. Compruebe que la variable se asigna sólo una vez 2. Extraiga el lado derecho de la asignación en un método 3. Reemplace las referencias a la temperatura con la llamada al método 4. Prueba 5. Retire la declaración y la asignación temporales

Antes:

const basePrice = this._quantity * this._itemPrice;
if (basePrice > 1000) {
  return basePrice * 0.95;
} else {
  return basePrice * 0.98;
}

Después:

get basePrice() {
  return this._quantity * this._itemPrice;
}

// In the method
if (this.basePrice > 1000) {
  return this.basePrice * 0.95;
} else {
  return this.basePrice * 0.98;
}

Simplificación de la lógica condicional

Descomponer condicional

Cuándo usar: Declaración condicional compleja (si-entonces-else)

Motivación: Deja clara la intención extrayendo las condiciones y acciones.

Mecánica1. Aplicar Método de Extracto en la condición 2. Aplicar Método de Extracto en la rama 3. Aplicar Método de Extracto en la rama (si está presente)

Antes:

if (!aDate.isBefore(plan.summerStart) && !aDate.isAfter(plan.summerEnd)) {
  charge = quantity * plan.summerRate;
} else {
  charge = quantity * plan.regularRate + plan.regularServiceCharge;
}

Después:

if (isSummer(aDate, plan)) {
  charge = summerCharge(quantity, plan);
} else {
  charge = regularCharge(quantity, plan);
}

function isSummer(date, plan) {
  return !date.isBefore(plan.summerStart) && !date.isAfter(plan.summerEnd);
}

function summerCharge(quantity, plan) {
  return quantity * plan.summerRate;
}

function regularCharge(quantity, plan) {
  return quantity * plan.regularRate + plan.regularServiceCharge;
}

Consolidar la expresión condicional

Cuándo usar: Múltiples condiciones con el mismo resultado

Motivación: Deja claro que las condiciones son un solo cheque.

Mecánica: 1. Verificar ningún efecto secundario en condiciones 2. Combinar las condiciones utilizando and o or 3. Considere el método de extracción en la condición combinada

Antes:

if (employee.seniority < 2) return 0;
if (employee.monthsDisabled > 12) return 0;
if (employee.isPartTime) return 0;

Después:

if (isNotEligibleForDisability(employee)) return 0;

function isNotEligibleForDisability(employee) {
  return employee.seniority < 2 ||
         employee.monthsDisabled > 12 ||
         employee.isPartTime;
}

Reemplazar condicional anidado por cláusulas de protección

Cuándo usar: Condiciones profundamente anidadas que hacen que fluyan difíciles de seguir

Motivación: Utilice cláusulas de protección para casos especiales, manteniendo el flujo normal despejado.

Mecánica: 1. Encontrar las condiciones especiales de caso 2. Reemplazarlos con cláusulas de guardia que regresen temprano 3. Prueba después de cada cambio

Antes:

function payAmount(employee) {
  let result;
  if (employee.isSeparated) {
    result = { amount: 0, reasonCode: "SEP" };
  } else {
    if (employee.isRetired) {
      result = { amount: 0, reasonCode: "RET" };
    } else {
      result = calculateNormalPay(employee);
    }
  }
  return result;
}

Después:

function payAmount(employee) {
  if (employee.isSeparated) return { amount: 0, reasonCode: "SEP" };
  if (employee.isRetired) return { amount: 0, reasonCode: "RET" };
  return calculateNormalPay(employee);
}

Reemplazar condicional con polimorfismo

Cuándo usar: Conmutador/caso basado en el tipo, lógica condicional que varía por tipo

Motivación: Deja que los objetos manejen su propio comportamiento.

Mecánica2. Use la función Factory para crear objetos 3. Mueva la lógica condicional al método de superclase 4. Cree el método de subclase para cada caso 5. Quite el condicional original

Antes:

function plumages(birds) {
  return birds.map(b => plumage(b));
}

function plumage(bird) {
  switch (bird.type) {
    case 'EuropeanSwallow':
      return "average";
    case 'AfricanSwallow':
      return (bird.numberOfCoconuts > 2) ? "tired" : "average";
    case 'NorwegianBlueParrot':
      return (bird.voltage > 100) ? "scorched" : "beautiful";
    default:
      return "unknown";
  }
}

Después:

class Bird {
  get plumage() { return "unknown"; }
}

class EuropeanSwallow extends Bird {
  get plumage() { return "average"; }
}

class AfricanSwallow extends Bird {
  get plumage() {
    return (this.numberOfCoconuts > 2) ? "tired" : "average";
  }
}

class NorwegianBlueParrot extends Bird {
  get plumage() {
    return (this.voltage > 100) ? "scorched" : "beautiful";
  }
}

function createBird(data) {
  switch (data.type) {
    case 'EuropeanSwallow': return new EuropeanSwallow(data);
    case 'AfricanSwallow': return new AfricanSwallow(data);
    case 'NorwegianBlueParrot': return new NorwegianBlueParrot(data);
    default: return new Bird(data);
  }
}

Introducir caso especial (objeto nulo)

Cuándo usar: Comprobaciones de nulidad reiteradas para casos especiales

Motivación: Devuelve un objeto especial que maneja el caso especial.

Mecánica1. Crear clase de caso especial con interfaz esperada 2. Agregar isSpecialCase check 3. Introducir método de fábrica 4. Reemplazar las comprobaciones nulas con el uso especial de objetos caso 5. Prueba

Antes:

const customer = site.customer;
// ... many places checking
if (customer === "unknown") {
  customerName = "occupant";
} else {
  customerName = customer.name;
}

Después:

class UnknownCustomer {
  get name() { return "occupant"; }
  get billingPlan() { return registry.defaultPlan; }
}

// Factory method
function customer(site) {
  return site.customer === "unknown"
    ? new UnknownCustomer()
    : site.customer;
}

// Usage - no null checks needed
const customerName = customer.name;

Refactorización de APIs

Consulta separada del modificador

Cuándo usar: La función devuelve un valor y tiene efectos secundarios

Motivación: Deje claro qué operaciones tienen efectos secundarios.

Mecánica1. Crear una nueva función de consulta 2. Copiar la lógica de retorno de la función original 3. Modificar el original para devolver vacío 4. Reemplazar llamadas que utilizan valor de retorno 5. Prueba

Antes:

function alertForMiscreant(people) {
  for (const p of people) {
    if (p === "Don") {
      setOffAlarms();
      return "Don";
    }
    if (p === "John") {
      setOffAlarms();
      return "John";
    }
  }
  return "";
}

Después:

function findMiscreant(people) {
  for (const p of people) {
    if (p === "Don") return "Don";
    if (p === "John") return "John";
  }
  return "";
}

function alertForMiscreant(people) {
  if (findMiscreant(people) !== "") setOffAlarms();
}

Función de parametrización

Cuándo usar: Varias funciones haciendo cosas similares con diferentes valores

Motivación: Eliminar la duplicación añadiendo un parámetro.

Mecánica: 1. Seleccione una función 2. Añadir parámetro para el literal variable 3. Cambiar cuerpo para utilizar el parámetro 4. Prueba 5. Cambiar llamadas para usar la versión parametrizada 6. Eliminar funciones ahora no utilizadas.

Antes:

function tenPercentRaise(person) {
  person.salary = person.salary * 1.10;
}

function fivePercentRaise(person) {
  person.salary = person.salary * 1.05;
}

Después:

function raise(person, factor) {
  person.salary = person.salary * (1 + factor);
}

// Usage
raise(person, 0.10);
raise(person, 0.05);

Eliminar el argumento de la bandera

Cuándo usar: Parámetro booleano que cambia el comportamiento de la función

Motivación: Haga el comportamiento explícito a través de funciones separadas.

Mecánica1. Crear función explícita para cada valor de bandera 2. Reemplazar cada llamada con la función nueva 3. Prueba después de cada cambio 4. Eliminar función original

Antes:

function bookConcert(customer, isPremium) {
  if (isPremium) {
    // premium booking logic
  } else {
    // regular booking logic
  }
}

bookConcert(customer, true);
bookConcert(customer, false);

Después:

function bookPremiumConcert(customer) {
  // premium booking logic
}

function bookRegularConcert(customer) {
  // regular booking logic
}

bookPremiumConcert(customer);
bookRegularConcert(customer);

Tratar con la herencia

Tirar hacia arriba método

Cuándo usar: Mismo método en varias subclases

Motivación: Eliminar la duplicación en la jerarquía de clases.

Mecánica: 1. Inspeccione los métodos para asegurarse de que son idénticos 2. Compruebe las firmas son el mismo 3. Crear nuevo método en la superclase 4. Copiar cuerpo de una subclase 5. Eliminar un método de subclase, prueba 6. Eliminar otros métodos de subclase, probar cada uno


Método de empuje hacia abajo

Cuándo usar: Comportamiento relevante sólo para un subconjunto de subclases

Motivación: Poner el método donde se utiliza.

Mecánica: 1. Copiar el método a cada subclase que lo necesite 2. Quitar el método de la superclase 3. Prueba 4. Retirar de las subclase que no lo necesiten 5. Prueba


Reemplazar Subclase con Delegado

Cuándo usar: La herencia se está utilizando incorrectamente, necesita más flexibilidad

Motivación: Prefiere la composición sobre la herencia cuando sea apropiado.

Mecánica1. Crear clase vacía para el delegado 2. Agregar campo a la clase de acogida delegado 3. Crear constructor para delegado, llamado desde el anfitrión 4. Mover características a delegar 5. Prueba después de cada movimiento 6. Reemplazar herencia con delegación


Clase de extracción

Cuándo usar: Clase grande con múltiples responsabilidades

Motivación: Dividir clase para mantener una sola responsabilidad.

Mecánica2. Crear nueva clase 3. Mueva el campo de la clase original a la nueva clase 4. Prueba 5. Mueva los métodos de la clase original a la nueva clase 6. Prueba después de cada movimiento 7. Revisar y cambiar el nombre de ambas clases 8. Decidir cómo exponer nueva clase.

Antes:

class Person {
  get name() { return this._name; }
  set name(arg) { this._name = arg; }
  get officeAreaCode() { return this._officeAreaCode; }
  set officeAreaCode(arg) { this._officeAreaCode = arg; }
  get officeNumber() { return this._officeNumber; }
  set officeNumber(arg) { this._officeNumber = arg; }

  get telephoneNumber() {
    return `(${this._officeAreaCode}) ${this._officeNumber}`;
  }
}

Después:

class Person {
  constructor() {
    this._telephoneNumber = new TelephoneNumber();
  }
  get name() { return this._name; }
  set name(arg) { this._name = arg; }
  get telephoneNumber() { return this._telephoneNumber.toString(); }
  get officeAreaCode() { return this._telephoneNumber.areaCode; }
  set officeAreaCode(arg) { this._telephoneNumber.areaCode = arg; }
}

class TelephoneNumber {
  get areaCode() { return this._areaCode; }
  set areaCode(arg) { this._areaCode = arg; }
  get number() { return this._number; }
  set number(arg) { this._number = arg; }
  toString() { return `(${this._areaCode}) ${this._number}`; }
}

Referencia rápida: Olor a la Refactorización

Olor al código Refactorización primaria Alternativa
Método largo Método de extracción Reemplazar Temp con la consulta
Código duplicado Método de extracción Tirar hacia arriba método
Clase grande Clase de extracción Extraer subclase
Lista de parámetros largos Introducir el objeto del parámetro Preservar todo el objeto
Envidia de la característica Mover método Método de extracción + movimiento
Clumps de datos Clase de extracción Introducir el objeto del parámetro
Obsesión primitiva Reemplazar Primitivo con Objeto Reemplazar el código de tipo
Cambiar las declaraciones Reemplazar condicional con polimorfismo Reemplazar el código de tipo
Campo temporal Clase de extracción Introduzca el objeto nulo
Cadenas de mensajes Ocultar Delegado Método de extracción
Hombre de medio Eliminar el hombre medio Método en línea
Cambio divergente Clase de extracción Fase de división
Cirugía de escopeta Mover método Clase en línea
Código muerto Eliminar código muerto -
Generalidad especulativa Contraer jerarquía Clase en línea

Lectura adicional

  • Fowler, M. (2018). Refactorización: mejorar el diseño del código existente (2a edición)
  • Catálogo en línea: https://refactoring.com/catalog/

Última actualización: 4 de agosto de 2026 Versión de Claude Code: 2.1.220 Fuentes: - https://code.claude.com/docs/en/skills Modelos compatibles: Claude Fable 5, Claude Opus 5, Claude Sonnet 5, Claude Sonnet 4.6, Claude Opus 4.8, Claude Haiku 4.5

Versión en español de Cris Severin. Basado en Claude How To de luongnv89, bajo licencia MIT. Proyecto original