Patrones Anti-Patrones en PHP

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!

¿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.


Ejercicios Resueltos

  • Ejercicio 1

    Identifica los anti-patrones en el siguiente código PHP y los problemas que generan:

    <?php
    // Identifica los anti-patrones en el siguiente código PHP y los problemas que generan:
    class Usuarios {
    
        public $db;
      
        public function __construct() {
          $this->db = new DB();
        }
      
        public function obtenerListado() {
      
          $query = "SELECT * FROM usuarios";
          $result = $this->db->query($query);
      
          $listado = [];
          while($row = $result->fetch()) {
            $listado[] = $row;
          }
      
          return $listado;
      
        }
      
        public function crearUsuario($nombre, $email) {
      
      // Validaciones
          if(empty($nombre)) {
            throw new Exception("El nombre es obligatorio");
          }
      
          if(!filter_var($email, FILTER_VALIDATE_EMAIL)) {
            throw new Exception("El email no es válido");
          }
      
      // Insertar usuario
          $query = "INSERT INTO usuarios VALUES ($nombre, $email)";
          $this->db->execute($query);
      
        }
      
      }
      

    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?

    <?php
    class Reporte {
    
        public function generaReporte($datos) {
      
      // ... lógica para recopilar datos del reporte
      
          $pdf = new TCPDF();
          $pdf->addPage();
          $pdf->setTextColor(0,0,0);
          $pdf->setFont('helvetica');
          $pdf->write(0, "REPORTE DE VENTAS ANUAL");
          $pdf->ln(20);
      
          foreach($datos as $item) {
            $pdf->write(0, $item);
            $pdf->ln(10);
          }
      
          $pdf->output("reporteventas.pdf");
      
        }
      
      }
      

    Aplicar patrón Strategy:

    • - Crear interfaz GeneradorPDF

    • - Implementar con la clase TCPDF

    • - Inyectar esta dependencia en Reporte

    <?php
    interface GeneradorPDF {
        public function generarPDF($datos);
      }
      
      class Reporte {
      
        private $pdf;
      
        public function __construct(GeneradorPDF $pdf) {
          $this->pdf = $pdf;
        }
      
        public function genera() {
      //...
          $this->pdf->generarPDF($datos);
        }
      
      }
      
      class TCPDF implements GeneradorPDF {
      // implementación
      }
      

    Así se desacopla la generación de PDFs y puede cambiarse fácilmente.

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

Deja un Comentario

¡Bienvenido a nuestro sitio web!

Este sitio utiliza cookies para mejorar tu experiencia de navegación y proporcionar funciones adicionales relacionadas con el análisis de tráfico y el rendimiento del sitio. Política de cookies.