En contra de la programación orientada a objetos (bueno, no, o sí)
Llevo un tiempo pensando en escribir una entrada así pero la fantástica entrada que Pablo López escribió hace un par de meses me animó entonces a ponerme con ello. Como suele ser habitual, tardo mucho en escribir pero al fin aquí está la entrada.
Antes de nada quiero dejar claro y especialmente a Pablo que no tengo nada en contra de su entrada, faltaría más, pero me parece sano abrir el melón de la programación orientada a objetos de blog a blog y exponer mi punto de vista que ha ido cambiando a lo largo de los años.
¿Qué dices? ¿Que estás en contra de la OOP?
NO, lo que pasa es que con el paso de los años me he ido alejando de ese paradigma cada vez más. Pienso que los desarrolladores tendemos a complicarnos mucho a la hora de abstraer y acabamos pensando “demasiado” para algo que puede llegar a ser muy simple. Si no ponemos cuidado, es fácil acabar en una espiral de abstracciones y capas sobre capas que complican el acceso y la legibilidad del código y como consecuencia nos topamos con un código más difícil de mantener o extender.
Todavía no he visto desarrolladores que consigan una abstracción perfecta, con unas dependencias finas, patrones ideales y todo lo que se le pide a una buena arquitectura SOLID y por otra parte, mi backend es básicamente WordPress y éste tiene algunas características que en la mayoría de los casos, el uso de objetos y exceso de abstracción creo que podría desaconsejarse.
Esto de aquí es algo típico que veo en ciertos plugins y es algo que he hecho durante años:
class MyPlugin {
static $instance;
public static function get_instance() {
if ( ! self::$instance ) {
self::$instance = new self();
}
return self::$instance;
}
}
Esto es un Singleton de toda la vida y representa la clase principal de un plugin. Si en WordPress los plugins se cargan una sola vez ¿Para qué necesitamos un singleton en su clase principal? Si bien tampoco influye en la ejecución, ya estamos añadiendo alguna capa de complejidad que se irá sumando posteriormente a otras.
WordPress es un sistema más imperativo y a veces se introducen conceptos de orientación a objetos con calzador, pretendiendo hacer encajar un triángulo en un cuadrado o al menos es la sensación que tengo constantemente. Aplicar técnicas de encapsulamiento se tornan imposibles (al final vas a tener que usar funciones de WP por alguna parte) porque no estamos creando un sistema nuevo sino integrando una extensión con WordPress y además lo hacemos de una manera que WordPress en realidad no acepta de buena gana.
Si además incluyes una batería de tests unitarios con PHPUnit, te habrás pegado cabezazos intentando integrar clases con dependencias externas (funciones de WordPress mayoritariamente), habrás visto que los mocks son costosos (y aburridos) de hacer y que lo que empezó como algo idílico se convierte en una pesadilla en la que cada ejecución de los tests dan ganas de cortarse las venas. Hay que tener algo en cuenta: Un PHPUnit que carga WordPress deja de ser Unit y pasa a convertirse en una batería de integración.
El patrón de WordPress
¿Cuál es el patrón en WordPress? Hay gente que dice que es un Spaguetti with Meatballs pero en realidad se trata de una arquitectura orientada a eventos, por eso tenemos los hooks, que es una manera excelente de extender lo que queramos de WordPress en un momento concreto, entonces ¿Para qué pretender extender WordPress todo con clases si ya tenemos los hooks en WordPress?
WordPress hace uso de algunas clases (WP_User
, WP_Post
entre otras) pero la mayoría de ellas no están bien desacopladas, tienen muchas dependencias y actúan en realidad como contenedores de funciones (la clase wpdb
es un ejemplo de cómo crear una clase larguísima que deja de tener sentido como entidad al cabo del tiempo).
En su entrada, Pablo explica cómo crear una clase desde la cual llamamos a los hooks necesarios para que la clase haga su trabajo. Aquí un ejemplo muy típico para crear una página en el wp-admin.
class MyAdminScreen {
public function init() {
add_action( 'admin_menu', [ $this, 'add_admin_menu' ] );
}
public function add_admin_menu() {
add_menu_page( ... );
}
}
Este patrón lo he usado mucho a lo largo del tiempo pero he terminado llegando a la conclusión de que una clase no es necesaria (al menos) en este caso: De nuevo depende de otras funciones de WordPress e instancia los hooks ahí mismo, aunque como recalca Pablo, se haya mejorado moviéndolos del constructor al método init
.
¿Hay alguna diferencia con esto?
namespace MyPlugin\Admin;
const PAGE_TITLE = 'My page';
function init() {
add_action( 'admin_menu', __NAMESPACE__ . '\\add_admin_menu' );
}
function add_admin_menu() {
add_menu_page( PAGE_TITLE, ... );
}
El código es muy similar excepto que no habría que instanciar ninguna clase aunque en mi opinión presenta algunas ventajas más:
- Las constantes del namespace se pueden reutilizar en otros sitios sin necesidad de instanciar nada. En esta situación usamos el título de una página que seguramente no usaremos más pero piensa en un slug de cualquier cosa o un post type. Seguramente quieras usar el slug de un post type en más sitios y sólo tendrías que definirlo aquí. Sí, se pueden usar propiedades de clase estáticas pero pienso que el uso de namespaces es un poco mejor para organizar código.
- Los tests unitarios son algo más fáciles de ejecutar. Seguimos teniendo el problema de las dependencias externas, claro, pero la instanciación y concretamente las propiedades de una clase suelen dar problemas en una arquitectura orientada a eventos a lo largo de la ejecución.
- Si en algún momento queremos dividir el código será más sencillo de hacer creando un nuevo namespace o bajo el mismo pero en otro fichero.
Mi fichero de bienvenida
El fichero de bienvenida es aquel por el que empieza todo en un plugin o tema. A menudo es una clase, con un Singleton y que carga los hooks iniciales para que el módulo eche a andar. Hace tiempo que dejé de usar una clase y simplemente me dedico a llamar a todas las partes necesarias una a una como en este ejemplo:
namespace MyPlugin;
Assets\init();
Admin\init();
RestApi\init();
Obviamente no siempre es tan sencillo y si alguien revisa mi código va a encontrar constantemente mis propias contradicciones en cuanto al tema que expongo pero es algo que tengo asumido.
¿Y las clases pa cuando?
A ver, yo diría que un plugin sencillito, pequeño y directo que sólo hace una cosa pero que la hace bien lo normal es que no necesite clases pero hay muchos casos en los que podríamos usar objetos:
- Una integración con una REST API externa donde existan numerosos tipos de llamada del tipo
$request->get( $path )
o$request->post( $path, $data );
. Estas llamadas requieren almacenar una API Key, API Secret o estar usando un método de autenticación como OAuth que requieren algo de persistencia durante la ejecución. El concepto Request en este caso creo que tendría sentido para ser abstraído. - Si estás montando el enésimo plugin de formularios. Cada tipo de campo puede heredar de una clase o implementar una interfaz. Además se puede tener un validador de datos y otro que los procese.
Hablemos
Este tema es algo que ha ido evolucionando en mi forma de programar a lo largo de los años, me he vuelto más práctico, más pragmático pero no estoy siendo dogmático y puede que el año que viene no soporte leerme de nuevo. Si quieres iniciar una conversación y como en esta casa las cosas van despacio y no tengo todavía los comentarios implementados, te invito a que me lances un mensaje en toda la cara en Twitter