La optimización de código es un arte que va más allá de hacer que funcione. Para desarrolladores intermedios y avanzados, aquí exploramos técnicas que pueden transformar código funcional en código excepcional.
🚀 OPTIMIZACIÓN DE PERFORMANCE
1. Lazy Loading y Evaluación Diferida
Carga recursos solo cuando sean necesarios:
// JavaScript - Dynamic imports
const loadModule = async () => {
const { heavyFunction } = await import('./heavyModule.js');
return heavyFunction();
};
// Python - Lazy property evaluation
class DataProcessor:
@property
def expensive_calculation(self):
if not hasattr(self, '_expensive_result'):
self._expensive_result = self._calculate_heavy_data()
return self._expensive_result
2. Memoización para Cálculos Repetitivos
Evita recalcular resultados costosos:
// JavaScript - Memoización con Map
const memoize = (fn) => {
const cache = new Map();
return (...args) => {
const key = JSON.stringify(args);
if (cache.has(key)) return cache.get(key);
const result = fn(...args);
cache.set(key, result);
return result;
};
};
// Python - Decorador para memoización
from functools import lru_cache
@lru_cache(maxsize=128)
def fibonacci(n):
if n < 2:
return n
return fibonacci(n-1) + fibonacci(n-2)
3. Batch Processing para Operaciones I/O
Agrupa operaciones para reducir overhead:
// JavaScript - Batch database operations
class BatchProcessor {
constructor(batchSize = 100) {
this.batch = [];
this.batchSize = batchSize;
}
async add(operation) {
this.batch.push(operation);
if (this.batch.length >= this.batchSize) {
await this.flush();
}
}
async flush() {
if (this.batch.length > 0) {
await db.transaction(this.batch);
this.batch = [];
}
}
}
🏗️ PATRONES DE DISEÑO AVANZADOS
1. Command Pattern para Undo/Redo
Encapsula acciones como objetos para máxima flexibilidad:
// TypeScript - Command pattern implementation
interface Command {
execute(): void;
undo(): void;
}
class TextEditor {
private history: Command[] = [];
private currentPosition = -1;
executeCommand(command: Command) {
// Remove any commands after current position
this.history = this.history.slice(0, this.currentPosition + 1);
command.execute();
this.history.push(command);
this.currentPosition++;
}
undo() {
if (this.currentPosition >= 0) {
this.history[this.currentPosition].undo();
this.currentPosition--;
}
}
}
2. Strategy Pattern con Functional Programming
Combina patrones clásicos con enfoques funcionales:
// JavaScript - Strategy pattern funcional
const sortingStrategies = {
byDate: (a, b) => new Date(a.date) - new Date(b.date),
byPriority: (a, b) => a.priority - b.priority,
byName: (a, b) => a.name.localeCompare(b.name)
};
const sortData = (data, strategy) => [...data].sort(sortingStrategies[strategy]);
// Uso más avanzado con composition
const createSortComposer = (...strategies) => (data) =>
strategies.reduce((sorted, strategy) =>
sorted.sort(sortingStrategies[strategy]), [...data]);
🔧 TÉCNICAS DE OPTIMIZACIÓN ESPECÍFICAS
1. Object Pooling para Reducir GC Pressure
Reutiliza objetos costosos de crear:
// JavaScript - Object pool implementation
class ObjectPool {
constructor(createFn, resetFn, initialSize = 10) {
this.createFn = createFn;
this.resetFn = resetFn;
this.pool = Array.from({ length: initialSize }, () => createFn());
this.used = new Set();
}
acquire() {
if (this.pool.length === 0) {
return this.createFn();
}
const obj = this.pool.pop();
this.used.add(obj);
return obj;
}
release(obj) {
if (this.used.delete(obj)) {
this.resetFn(obj);
this.pool.push(obj);
}
}
}
2. Bitwise Operations para Flags
Usa operaciones bit a bit para estados múltiples eficientes:
// JavaScript - Bitwise flags
const PERMISSIONS = {
READ: 1, // 001
WRITE: 2, // 010
EXECUTE: 4, // 100
ADMIN: 8 // 1000
};
class User {
constructor(permissions = 0) {
this.permissions = permissions;
}
hasPermission(permission) {
return (this.permissions & permission) !== 0;
}
addPermission(permission) {
this.permissions |= permission;
}
removePermission(permission) {
this.permissions &= ~permission;
}
}
3. Trabajo con Streams para Grandes Datasets
Procesa datos grandes sin cargar todo en memoria:
// Node.js - Stream processing
const { Transform } = require('stream');
const fs = require('fs');
const processLargeFile = () => {
const transformStream = new Transform({
objectMode: true,
transform(chunk, encoding, callback) {
// Procesa cada línea sin cargar el archivo completo
const processed = chunk.toString()
.split('\n')
.filter(line => line.includes('ERROR'))
.map(line => ({ timestamp: Date.now(), log: line }));
callback(null, JSON.stringify(processed));
}
});
fs.createReadStream('huge-log-file.txt')
.pipe(transformStream)
.pipe(fs.createWriteStream('errors-only.json'));
};
⚡ MICRO-OPTIMIZACIONES IMPACTANTES
1. Loop Optimizations
// Evitar búsquedas repetitivas de propiedades
// ❌ Ineficiente
for (let i = 0; i < array.length; i++) {
if (array[i].property.nestedProperty > threshold) {
// proceso
}
}
// ✅ Optimizado
const length = array.length;
for (let i = 0; i < length; i++) {
const nested = array[i].property.nestedProperty;
if (nested > threshold) {
// proceso
}
}
2. Short-Circuit Evaluation
// Usar evaluación de cortocircuito estratégicamente
const isValid = user?.isActive &&
user.permissions?.includes('read') &&
expensiveValidationCheck(user);
// Condicionales con early return
const processUser = (user) => {
if (!user?.isActive) return null;
if (!user.permissions?.length) return null;
return performExpensiveOperation(user);
};
Consideraciones importantes: La optimización prematura puede ser contraproducente. Siempre medir performance antes y después de optimizar, y enfocarse en los cuellos de botella reales identificados mediante profiling.
¿Qué técnicas de optimización han resultado más efectivas en sus proyectos? ¿Algún patrón específico que hayan implementado recientemente?