Has oído hablar de SOLID? Seguramente: Es un término que describe una colección de principios para «buen código» que fue acuñado por Robert C. Martin (aka «uncle bob«) nuestro gran evangelista del «clean code».
La programación está llena de acrónimos como este. Otros ejemplos son DRY(Don’t Repeat Yourself!) y KISS(Keep It Simple, Stupid!). Aunque obviamente hay muchos, muchísimos más.
Así que, ¿Por qué nos abordar este problema de los acrónimos desde el otro lado? Que «olores hacen que tu código sea mal código.
Lo siento, pero tu código es STUPID!
A Nadie le gusta oír que su código es estúpido. Es ofensivo. No lo digas. Pero honestamente: mucho código del que vemos a diario es un caos, no es mantenible y mucho menos reusable.
¿Qué caracteriza a este código? ¿Qué hace que un código sea STUPID?
- Singleton
- Tight coupling (mucho acoplamiento)
- Untestability (intesteable)
- Premature Optimization (optimización prematura)
- Indescriptive Naming (naming indescriptible)
- Duplication (duplicación)
¿Estás de acuerdo con esta lista? ¿Si? Genial, ¿no? Vamos a explicar los puntos con mayor detalle, para ver porque se eligieron exactamente estas palabras para el acrónimo.
Singleton
class DB {
private static $instance;
public static function getInstance() {
if (!isset(self::$instance)) {
self::$instance = new self;
}
return self::$instance;
}
final private function __construct() { /* something */ }
final private function __clone() { }
/* actual methods here */
}
Lo anterior es la típica implementación de acceso a la base de datos que se encuentra en casi cualquier tutorial de PHP. Creo que he usado algo similar a esto no hace mucho tiempo.
Ahora uno se pregunta: ¿Qué hay de malo en esto? Se puede acceder fácilmente a la base de datos desde cualquier sitio utilizando DB::getInstance()
y este código se asegura de que solo hay una conexión abierta con la base de datos. ¿Qué puede salir mal?
Bien, «solo necesito una colección» pensé. Cuando la aplicación se hizo más grande necesité una segunda conexión a otra base de datos. Y ahí es donde empieza el lío. Podríamos cambiar el singleton a algo a´si como getSecondInstance()
algo así como un ¿dupleton? En su lugar deberíamos darnos cuenta de que la conexión a la base de datos no es un «producto único» y por lo tanto debemos intentar implementarlo de otra manera. Esto mismo puede aplicarse a cualquier otro uso de singletons que encontremos. ¡Las request seguro que es un singleton! ¿O alguien ha oído hablar de subrequest? ¿Algo parecido puede pasar con el log?
Y esto es solo el principio. Otra cuestión importante de el uso de DB::getInstance
en nuestro código es que vamos a ligar todo nuestro código a la clase DB. No podemos extender la clase DB. No podríamos por ejemplo extender la clase DB para tener estadísticas de uso debido al acoplamiento con el nombre de la clase DB. Esto se solucionaría utilizando inyección de dependencias para pasar la nueva clase a través de la inyección. Pero singleton lo impide. pudiendo llegar a cosas tan horribles como esta:
// original DB class
class _DB { /* ... */ }
// extending class
class DB extends _DB { /* ... */ }
En una palabra: Horrible. Podríamos añadir otras palabras como Hack, Inmantenible, Crap. os simplemente STUPID.
Un último punto a considerar. Recuerdas cuando dijimos «Es posible utilizar la base de datos desde cualquier sitio usando DB::getInstance
«. Bien, esto también es malo en cierta medida. Traduce «desde cualquier sitio» como «globalmente» y tenemos alfo como «Un singleton es como una variable global con un bonito nombre». A medida que usamos PHP nos damos cuenta de que utilizar global
es malo. Pero usando singleton estamos haciendo justo eso: crear un estado global. Esto crea dependencias no evidentes y hace que la aplicación sea difícil de testar y reutilizar.
Mucho acoplamiento
En realizad el asunto del singleton podemos generalizarlo al uso de métodos y variables static
en general. Cuando estamos escribiendo Foo::bar()
en nuestro código nos estamos acoplando a la clase Foo
. Esto hace imposible extender la funcionalidad de Foo
y complicado reutilizar y testar.
Del mismo modo hay otros «malos olores» como el uso de clases y el operador new
:
class House {
public function __construct() {
$this->door = new Door;
$this->window = new Window;
}
}
¿Cómo podemos reemplazar door o window en nuestra casa ahora? Simple: No podemos. Aunque una solución mucho mejor sería:
class House {
public function __construct(Door $door, Window $window) { // Door, Window are interfaces
$this->door = $door;
$this->window = $window;
}
}
De esta manera podemos crear puertas y ventanas fácilmente. El código es fácilmente extensible, fácilmente reutilizable y fácilmente testeable. ¿Que más queremos?
El código anterior es en pocas palabras «inyección de dependencias». Mucha gente asocia DI con contenedores de inyección de dependencias como el de Symfony, pero el concepto de DI es mucho más simple.
Intesteable
El Testing unitario es importante. Si no testeamos nuestro código, podríamos estar entregando un producto roto. Aún así, la mayoría de los desarrolladores no testean su código. ¿Por qué? Muchas veces es poco el código es difícil de testear. ¿Qué hace que el código sea difícil de testear? Principalmente el punto anterior: Mucho acoplamiento. El testing unitario – algo que parece obvio – testea unidades de código (generalmente clases). Pero, ¿Cómo podemos testear las clases individualmente si el código está acoplado? Seguramente encontremos alguna manera, aunque sea sucia. El problema es que la gente normalmente no se esfuerza y termina dejando el código sin test y roto.
Siempre que no escribimos test unitarios porque «no hay tiempo» probablemente la causa sea que nuestro código es malo. Si nuestro código es bueno se puede testear en cualquier momento. Solo el mal código convierte al testing unitario en una carga.
Optimización prematura
Aquí un trozo de código de un antiguo sitio web:
if (isset($frm['title_german'][strcspn($frm['title_german'], '<>')])) {
// ...
}
¡Adivina lo que hace!
¿Cuanto tiempo te llevó darte cuenta de que el código no hace nada más que comprobar que ‘title_german’ contiene el carácter <
o >
? ¿De verdad te diste cuenta?
Déjame explicarte: Si <
y >
no están entonces strcspn devolverá la longitud de la cadena. Así que si el número de elementos de la cadena es mayor que el número de de elementos desde el inicio hasta encontrar uno de los caracteres se devolverá false.
¿Por qué escribir un trozo de código tan inteligible como este? Porqué no escribirlo así:
if (strlen($frm['title_german']) == strcspn($frm['title_german'], '<>'))) {
// ...
}
Seguramente porque alguna vez escuchamos que isset
es mucho más rápido que strlen
… Pero ese código todavía es un poco difícil de seguir, porque necesitamos conoces exactamente que hace la función strcspn
(la cual no conocen muchos programadores). Así que por qué no escribir algo como esto:
if (preg_match('(<|>)', $frm['title_german'])) {
// ...
}
Porque las expresiones regulares son lentas… (no creo que las expresiones regulares sean lentas, si se saben utilizar son mucho más potentes de lo que pensamos).
Por lo tanto ¿qué ganamos con estas «optimizaciones» aparte de código ilegible? Nada. Incluso si un sitio web atrae a más de 40 millones de páginas vistas cada mes, estas micro-optimizaciones no darán ninguna diferencia. Simplemente porque no son el cuello de botella de la aplicación. El cuello de botella es el triple JOIN
en una query que se ejecuta en cada petición (probablemente el cuello de botella sea similar en tu aplicación)
Podemos encontrar muchos más consejos sobre micro-optimización en Internet. Como «usar comillas simples es mucho más rápido». No los escuches. La mayoría de los consejos son erróneos, o incluso si lo son no hará que su código vaya más rápido, solo te harán perder tiempo.
Nombres indescriptibles
Por cierto, ¿sabes lo que hace en PHP la función strpbrk? ¿No? ¿Ni siquiera sabías que existía? Que no te sorprenda. Nadie que quiera buscar una cadena por cualquiera de los elementos de un conjunto de caracteres busca una función llamada strpbrk
. ¿De donde proviene ese nombre? Esta función se hereda de C y su nombre es algo así como «ruptura del puntero de cadena». Sí, muy bien, sobre todo en un lenguaje que no tiene punteros como PHP.
Ah, y en la sección de código anterior, ¿recuerdas lo que hacía la función strcspn? ¿No? Que no te sorprenda. Es la abreviatura de «string complement span» en caso de que no lo sepas.
La lección de todo esto: Por favor, piensa bien el nombre de tus clases, métodos, atributos, para que la gente sepa lo que realmente querías decir. No estamos discutiendo acerca de variables $i
, pero los nombres deben ser explicativos. El problema de los nombres como los de antes. Funciones como strpbrk
o variables como $yysstk
son solo obvios para el autor en el momento que escribe el código, pero solo en ese momento.
Duplicación
Creo que el código que es particularmente corto y conciso se considera elegante (No me refiero al estilo Ruby). Por otro lado la gente considera que el código largo resulta extremadamente redundante y feo. Esto es lo que mencionamos al principio DRY(Don’t Repeat Yourself) y KISS (Keep It Simple, Stupid!) son los principios de diseños que tendríamos que aprender.
Entonces, ¿De donde viene la duplicación del código? Los programadores somos vagos, por lo que intentamos escribir tan poco código como sea posible. Aún así la duplicación aparece.
Creo que la razón principal por la que aparece la duplicación es por segur el segundo principio de STUPID. Alto acoplamiento. Si tu código está muy acoplado, no podes reutilizar nada. Y aquí es donde aparece la duplicación.
Don’t be STUPID: GRASP SOLID!
¿Cuáles son las alternativas a escribir código STUPID? Simple: SOLID y GRASP
SOLID: – Single responsibility principle – Open/closed principle – Liskov substitution principle – Interface segregation principle – Dependency inversion principle
GRASP (General Responsibility Assignment Software Principles):
- Information Expert
- Creator
- Controller
- Low Coupling
- High Cohesion
- Polymorphism
- Pure Fabrication
- Indirection
- Protected Variations
Este artículo es una traducción de Don’t be STUPID: GRASP SOLID! de nikic
No seas STUPID y piensa antes de escribir código