Saltar al contenido principal
Engineering craft beyond tooling — design patterns, refactoring, code review, advanced testing strategy, and reading code you did not write.

Software Engineering Craft

Engineering craft beyond tooling — design patterns, refactoring, code review, advanced testing strategy, and reading code you did not write.

Patrones de diseño y refactorización idiomática

Un patrón de diseño es una solución nombrada y reusable a un problema de diseño recurrente. El libro del Gang of Four catalogó veintitrés en 1994, pero el valor hoy no es el catálogo — es el vocabulario compartido que permite que un ingeniero diga “esto es un Strategy” y otro imagine inmediatamente la forma del código.

Los patrones se vuelven dogmáticos solo cuando se aplican sin contexto. La visión moderna: aprende los pocos que aparecen en todo codebase, reconoce sus costos y recurra a ellos cuando el problema realmente encaje — nunca por defecto.

Los cuatro que te encuentras en todos lados

PatrónProblema que resuelveForma moderna
StrategyElegir un algoritmo en runtime sin escaleras de if/elseUna función pasada como argumento; una interfaz “callable”
ObserverNotificar a N suscriptores cuando algo cambiaEvent emitters, streams reactivos, callbacks subscribe
DecoratorEnvolver un objeto para añadir comportamiento sin subclasearCadenas de middleware (Express, Rack), sintaxis de decorador (Python)
FactoryDesacoplar qué se construye de cómo se construyeUna función builder; contenedores de inyección de dependencias

Cuatro casos cubren el noventa por ciento de las referencias a patrones en código de producción. Interioriza estos y la mayoría de conversaciones sobre “patrones” fluyen; las entradas restantes del GoF (Visitor, Command, Iterator) son útiles pero más raras.

Composición vs herencia

El único principio que ha desplazado más uso de patrones es composición sobre herencia. Una clase que tiene-un helper puede intercambiar ese helper en runtime; una clase que es-un padre está soldada a la interfaz del padre de por vida.

Herencia                     Composición
  class Duck extends Bird      class Duck { flight: Flyer; quack: Quacker }
  — vuelo horneado            — vuelo intercambiable (FlyWithWings → NoFly)
  — graznido integrado         — graznido intercambiable por separado

La herencia sigue siendo la herramienta correcta cuando las subclases realmente sustituyen al padre (la regla de Liskov). La composición gana cuando el comportamiento es ortogonal al tipo.

Alternativas funcionales

Varios patrones “clásicos” se reducen a una única función en un lenguaje con funciones de primera clase:

Patrón clásicoReemplazo funcional
StrategyPasa una función de orden superior
Template MethodPasa callbacks a una función genérica
CommandUn literal { run, undo } — o simplemente la función
IteratorCualquier iterable / generator (yield)
ObserverUna lista de callbacks; subscribe añade uno

Si el lenguaje soporta closures y genéricos, recurrir a un Strategy de interfaz-y-clase es una señal de que el patrón fue cargo-culted. El objetivo es el comportamiento, no la forma — los closures son la forma más barata.

Antipatrones a evitar

  • God Object: una clase “para mantenerlo simple” que acumula 50+ métodos. Divide por responsabilidad; los nombres quedan más claros.
  • Singletons prematuros: el estado de instancia única se vuelve un global — acoplamiento invisible, no testeable, cada test hereda el estado de cada otro test.
  • Patrón solo de nombre: una “Factory” que tiene un producto, un “Strategy” con una estrategia, un “Observer” con cero suscriptores. Cada uno fue una excusa para usar un patrón; cada uno debería eliminarse.
  • Herencia muñeca rusa: A → B → C → D → E, donde cambiar B es un campo minado. La composición aplana la cadena.

Cuándo los patrones dañan

Un patrón es correcto cuando aparece la tercera instancia del problema — no la primera. La regla de tres:

  1. Primera aparición: escribe el código inline. YAGNI (You Aren’t Gonna Need It) aplica.
  2. Segunda aparición: copiar-pegar es más rápido que la abstracción equivocada.
  3. Tercera aparición: ahora la forma es real; el patrón (o su equivalente funcional) está justificado.

La abstracción prematura es más cara que la duplicación, porque las abstracciones se osifican: una vez que tres llamadores dependen de la forma equivocada, cada refactor debe satisfacer a los tres.

Trayectoria de práctica

  1. Elige cualquier codebase que conozcas; encuentra un Strategy y un Observer. Describe el problema que resuelve cada uno en una frase.
  2. Refactoriza una cadena de if/else de 4 ramas a un Map<string, Strategy> y observa cómo el sistema de tipos documenta ahora los casos.
  3. Reemplaza un Strategy basado en clase por una función de orden superior y anota qué se simplifica (sin interfaz, sin ceremonia) y qué se pierde (sin dónde poner una doc-comment de interfaz).
  4. Audita un codebase buscando “patrones solo de nombre” — factories con un producto, singletons sin estado compartido, clases abstractas con una sola subclase concreta — y elimina uno.
  5. Elige una entrada del GoF que nunca hayas usado (Visitor, Memento, Chain of Responsibility) y escribe un párrafo: qué problema resuelve, cuándo es la herramienta equivocada y una alternativa moderna.

Cuándo es la herramienta correcta

SituaciónConclusión
Tres o más casos de la misma formaUn patrón (o su forma funcional) ya está justificado
Revisando código desconocidoLos nombres de patrones son el diccionario; apréndelos para leer, no para escribir
Eligiendo un lenguajeLos closures/genéricos nativos te permiten expresar la mayoría de patrones como código plano
Diseñando la API de una libreríaPrefiere composición + una interfaz pequeña; reserva la herencia para subtipos reales

Un patrón es una herramienta para comunicar y estructurar, nunca un checklist para una promoción.