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
- Identificar el olor utilizando la referencia de olores de código
- Encontrar la refactorización correspondiente en este catálogo
- Sigue a los mecánicos paso a paso
- 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