Guía Completa sobre los Patrones Anti-Patrones en PHP
Bienvenidos a esta clase sobre patrones y anti-patrones de diseño en PHP. En programación, los patrones de diseño son soluciones reutilizables para problemas comunes en el desarrollo de software. Nos ayudan a crear sistemas más flexibles, escalables y fáciles de mantener.
Por otro lado, los anti-patrones son formas inadecuadas de escribir código que generan sistemas frágiles, difíciles de extender y propensos a errores. Identificar y evitar anti-patrones es clave para crear aplicaciones PHP de alta calidad.
Al finalizar, estarás capacitado para crear sistemas PHP escalables y de fácil mantenimiento, aplicando patrones de diseño y evitando anti-patrones. ¡Comencemos!
Índice
¿Qué son los Anti-Patrones de Diseño?
Los anti-patrones son formas comunes pero inapropiadas de escribir código que genera sistemas poco flexibles, difíciles de mantener y propensos a errores. Son como "patrones de malas prácticas".
Algunas características de los anti-patrones son:
- Código desorganizado y difícil de entender: mucho código duplicado, nombres de variables poco descriptivos, falta de comentarios, etc.
- Diseño poco flexible: difícil de extender con nuevos requisitos, alta acoplamiento entre clases.
- Baja cohesión: clases y métodos con multiples responsabilidades.
- Ineficiencias: operaciones costosas dentro de bucles, queries sin paginación, etc.
Los anti-patrones surgen debido a negligencia, prisas o falta de experiencia. Pueden comenzar como soluciones rápidas a problemas puntuales, pero terminan convirtiéndose en código difícil de mantener.
Identificación de Anti-Patrones en Código PHP
Aprender a identificar anti-patrones es clave para mejorar la calidad de nuestro código y prevenir futuros dolores de cabeza. Veamos como detectar algunos de los anti-patrones más comunes.
Long Method & Long Class
Métodos o clases excesivamente largos y complejos son síntoma de que resuelven diferentes responsabilidades y deberían dividirse.
Solución: Aplicar el principio de responsabilidad única dividiendo en métodos/clases más pequeños y enfocados.
Duplicated Code
Fragmentos de código idénticos o muy similares en diferentes partes del sistema. Violan el principio DRY (Don't Repeat Yourself).
Solución: Extraer a funciones/métodos reutilizables.
Spaghetti Code
Código sin estructura, con variables globales, difícil de entender y seguir. Suele tener nombres poco descriptivos.
Solución: Reorganizar en clases y métodos cohesivos. Usar nombres descriptivos
Shotgun Surgery
Un cambio simple requiere modificar diferentes partes no relacionadas del código. Indica alto acoplamiento.
Solución: Reducir dependencias y aumentar cohesión de clases.
God Class
Clase con cientos o miles de líneas controlando gran parte de la lógica del sistema.
Solución: Extraer responsabilidades en nuevas clases enfocadas.
Law of Demeter
Método accede directamente a métodos/atributos de objetos internos de otro objeto. Alto acoplamiento.
Solución: Usar getters/setters para acceder a objetos internos.
Complex Conditionals
Múltiples anidamientos de condicionales if/else o switch muy complejos.
Solución: Simplificar con polimorfismo, strategy o state.
Cada vez que encontremos estas "banderas rojas" en nuestro código, debemos considerar aplicar refactorizaciones o patrones de diseño para dejarlo más limpio y mantenible.
Cómo Evitar Anti-Patrones en Proyectos PHP
Prevenir anti-patrones desde el inicio nos ahorrará dolores de cabeza. Algunas recomendaciones:
Planifica la Arquitectura
Dedica tiempo al diseño previo de la arquitectura y comunicación entre módulos. Esto previene alto acoplamiento y facilita el mantenimiento.
Aplica Principios SOLID
Los principios SOLID de POO como responsabilidad única y segregación de interfaces previenen anti-patrones.
Usa Patrones de Diseño
Los patrones de diseño como MVC, ORM o Builder generan código flexible y mantenible.
Refactoriza Continuamente
Refactorizar el código para mejorar su estructura previene que se deteriore con el tiempo.
Revisa el Código
Revisiones de código ayudan a detectar anti-patrones antes de que se propaguen.
Escribe Pruebas Unitarias
Las pruebas unitarias obligan a mantener el código descentralizado y con bajo acoplamiento.
Usa Estándares de Codificación
Adoptar estándares como PSR reduce inconsistencias y mejora la legibilidad.
Ejemplos Prácticos de Anti-Patrones y Soluciones
Veamos ahora algunos ejemplos de código de anti-patrones comunes y como resolverlos aplicando buenas prácticas y patrones de diseño.
Ejemplo Práctico: Duplicated Code
<?php
// PROCESAR ORDEN DE COMPRA
// Validaciones
function validar_cliente($cliente)
{
// validaciones...
}
function validar_productos($productos)
{
// validaciones...
}
function validar_dir_envio($direccion)
{
// validaciones...
}
// Envío email
function enviar_email_compra($cliente, $productos)
{
// lógica de envío de email...
}
function enviar_email_factura($cliente, $productos)
{
// misma lógica de envío de email...
}
En este código hay mucha duplicación entre las funciones de validación y envío de emails.
Solución
<?php
// Validaciones
function validar_datos($datos)
{
// lógica de validacion reutilizable
}
// Envio de emails
function enviar_email($cliente, $productos, $tipo_email)
{
// lógica reutilizable
}
Se extrajo la lógica común a funciones reutilizables.
Ejemplo Práctico: God Class
<?php
class OrdenCompra{
public function __construct()
{
// constructor
}
// Logica de orden de compra
public function generarNumeroOrden(){return true;}
public function calcularTotal(){return true;}
public function aplicarDescuento(){return true;}
public function verificarStock(){return true;}
// Logica de validacion
public function validarCliente(){return true;}
public function validarDireccion(){return true;}
// Logica de envio
public function enviarEmailConfirmacion(){return true;}
public function enviarEmailFactura(){return true;}
public function programarEnvio(){return true;}
// Logica de persintencia
public function guardarEnBD(){return true;}
public function leerDeBD(){return true;}
public function borrarDeBD(){return true;}
// etc etc
}
La clase OrdenCompra tiene más de 1000 líneas realizando diferentes responsabilidades.
Solución
Aplicar SRP dividiendo en varias clases más enfocadas:
<?php
// Creamos 3 objetos
// - OrdenCompra
// - ValidadorOrdenCompra
// - EmailOrdenCompra
// - PersistenciaOrdenCompra
Cada clase se encarga de una responsabilidad específica.
Ejemplo Práctico: Shotgun Surgery
<?php
// CLASE PARA GENERAR REPORTE DE VENTAS
class ReporteVentas
{
public function generar()
{
// incluir libreria de graficos
require 'libChart.php';
// obtener ventas del mes
$ventasMes = $db->query("SELECT ...");
// obtener ventas del año anterior
$ventasAñoAnt = $db->query("SELECT ...");
// generar graficos
$chart = new Chart();
$chart->pieChart($ventasMes);
$chart->barChart($ventasAñoAnt);
// exportar a PDF
$pdf = new PDF();
$pdf->addChart($chart->pieChart());
$pdf->addChart($chart->barChart());
$pdf->export("reporteventas.pdf");
}
}
Un cambio en la librería de gráficos requiere cambios en múltiples partes. Acoplamiento.
Solución: Aplicar patrón Strategy
<?php
interface Grafico
{
public function generaGrafico();
}
class GraficoBarras implements Grafico
{
/* Código */
public function generaGrafico() {
return true;
}
}
class GraficoPie implements Grafico
{
public function generaGrafico() {
return true;
}
}
class ReporteVentas
{
private $grafico;
public function __construct(Grafico $grafico)
{
$this->grafico = $grafico;
}
public function generar()
{
$this->grafico->generaGrafico();
}
}
Desacopla la generación de gráficos del reporte permitiendo variar fácilmente.
Ejemplo Práctico: Spaghetti Code
<?php
require 'core.php';
$db = conectarDB();
if(isset($_POST['btn_submit'])){
$nombre = $_POST['nombre'];
$correo = $_POST['email'];
if(validarEmail($email)){
$consulta = $db->query("SELECT ...");
while($row = $result->fetch()){
enviarEmail($row);
}
}else{
mostrarMsg("Email invalido");
}
}else{
mostrarFormulario();
}
function mostrarFormulario(){
// html formulario...
}
function validarEmail(){
// validacion
}
function enviarEmail(){
// enviar email
}
Código desorganizado con mezcla de php, html y queries. Dificil de entender y mantener.
Solución:
- Usar MVC para separar capas
- ORM para abstraer la BD
- Plantillas para el HTML
Quedaría algo así:
<?php
// TODO ESTO SE DIVIDE EN VARIOS ARCHIVOS SIGUIENDO EL PATROÓN MVC, ESTO ES SOLO UNA MUESTRA
// CONTROLADOR
$datos = $form->getValues();
if ($validador->validar($datos)) {
$reporte->generar(); // Genera y envia emails
return view('exito');
} else {
return view('form');
}
// VISTA
// html con plantilla
// MODELO
class Reporte
{
public function generar()
{
// lógica envio emails
}
}
CódigoPHP separado de la vista y la capa de datos. Más organizado y mantenible.
Conclusión
En esta clase hemos visto la importancia de identificar y prevenir anti-patrones en nuestro código PHP.
Aplicar buenas prácticas de diseño y principios SOLID nos permitirá crear sistemas flexibles, mantenibles y preparados para evolucionar. Los patrones de diseño son otra herramienta fundamental para evitar anti-patrones.
Al detectar "malas prácticas" en nuestro código mediante las técnicas vistas, es clave realizar refactorizaciones o introducir patrones para dejarlo limpio.
Incorporando estas técnicas en nuestro día a día como desarrolladores PHP podremos prevenir mucho dolores de cabeza y crear aplicaciones de alta calidad, escalables y fáciles de mantener a lo largo del tiempo.
Preguntas Frecuentes
Algunos de los anti-patrones más típicos en PHP son: spaghetti code, clases god, programación copy-paste, acoplamiento excesivo, queries en bucles, nombres poco descriptivos, clases y métodos muy largos, falta de comentarios, etc.
Lo recomendable es ir mejorando el código legacy de forma progresiva. Puedes comenzar refactorizando sectores puntuales cuando se necesite agregar nueva funcionalidad. También agregar tests unitarios para ir dando más flexibilidad al sistema. Eventualmente podrías reemplazar partes grandes del sistema por nuevos módulos mieux diseñados. Lo importante es ir mejorando el código de a partes.
En general no se recomienda utilizar anti-patrones deliberadamente en ningún caso. Si bien pueden servir como soluciones rápidas
Algunas formas de prevenir anti-patrones son: Planificar bien la arquitectura inicial Seguir principios SOLID y patrones de diseño Refactorizar continuamente el código Mantener clases y métodos pequeños y enfocados Usar nombres
Ejercicios Resueltos
Ejercicio 1
Identifica los anti-patrones en el siguiente código PHP y los problemas que generan:
Solución
- Global $db: Uso de variable global para BD en vez de inyección de dependencia.
- God Class: La clase Usuarios tiene diferentes responsabilidades: obtener, crear, validar usuarios.
- Duplicated Code: Validaciones duplicadas en distintos métodos.
- SQL Injection: Interpolación directa de variables en querys.
Mejoras:
- Inyectar BD en constructor
- Separar en clases más pequeñas y enfocadas
- Centralizar validaciones en una clase Validator
- Usar consultas preparadas para prevenir inyección SQL
Ejercicio 2
Tenemos una aplicación que genera reportes de ventas. El código utiliza la librería TCPDF para generar archivos PDF. ¿Cómo podríamos desacoplar la generación de PDFs de la lógica de reportes para facilitar el cambio de librería en el futuro?
Aplicar patrón Strategy:
- Crear interfaz GeneradorPDF
- Implementar con la clase TCPDF
- Inyectar esta dependencia en Reporte
Así se desacopla la generación de PDFs y puede cambiarse fácilmente.