Muchas veces se nos llena la boca hablando de los principios SOLID y de que tenemos que escribir mejor código, pero ¿qué significa eso de escribir mejor código? En realidad yo no lo sé, pero en este post voy a contar como intento acercarme a los principios SOLID y al Clean Code en mi día a día como desarrollador PHP.

A rasgos generales intento usar Object Calisthenic unido a trabajar la legibilidad del código y que este se lea como prosa. Y algo muy importante, estas indicaciones no debes seguirlas estrictamente, son solo una serie de normas que sigo a veces (repito a veces). Son unas pequeñas pautas, nada más así que si las aplicas no seas demasiado rígido.
Utiliza variables que puedas pronunciar
La idea es que puedas pronunciar el nombre de las variables, funciones y clases. No importa que el nombre quede un poco largo, es mejor saber de qué trata una variable con solo leerla, que tener que intuir el acrónimo.
Mal
$ymdstr = (new \DateTime())->format('y-m-d');
Bien
$currentDate = (new \DateTime())->format('y-m-d');
Utiliza constantes para minimizar los números mágicos
La idea es que todo se lea como prosa, así que encontrarse con un 5 o un 23 no mola.
$result = $serializer->serialize($data, 448);
¿Qué significa el 448? Creo que queda mejor algo así:
$json = $serializer->serialize($data, JSON_UNESCAPED_SLASHES | JSON_PRETTY_PRINT | JSON_UNESCAPED_UNICODE);
Esto mismo puede aplicarse dentro de una clase a por ejemplo los estados de un usuario.
class User { const ACCESS_READ = 1; const ACCESS_UPDATE = 4; const ACCESS_DELETE = 8; } if ($user->access && User::ACCESS_UPDATE) { // do edit ... }
Utiliza variables explicativas
¿Qué hace este trozo de código?
$str = 'One Infinite Loop, Cupertino 95014'; preg_match('/^[^,]+,\s*(.+?)\s*(\d{5})$/', $str, $matches); save($matches[1], $matches[2]);
Seguramente en 1 minuto seas capaz de decirmelo, pero imagina que estas debugeando porque quieres resolver un bug y te encuentras con esto. Tardarás un minuto en saber que hace y lo peor de todo es que quizás pierdas el foco de lo que estabas haciendo.
Por ello creo que lo mejor es utilizar variables que expliquen que es lo que estás haciendo, algo así:
$address = 'One Infinite Loop, Cupertino 95014'; $cityZipCodeRegex = '/^[^,]+,\s*(?<city>.+?)\s*(?<zipCode>\d{5})$/'; preg_match($cityZipCodeRegex, $address, $matches); saveCityZipCode($matches['city'], $matches['zipCode']);
¿Se entiende algo mejor?
Encapsula condicionales
Cuando estamos leyendo código, llevamos encima una mochila de carga cognitiva sobre que es lo que hace el código, el contexto de la aplicación, las variables… por eso lo que siempre intento es minimizar esa carga y hacer que con solo leer una variable o una condición sepa que es lo que hace. Por ejemplo, normalmente evito hacer cosas como esta:
if ($article->state === 'published') { // ... }
Lo que hago es intentar dar contexto y sobre todo en los if crear funciones booleanas que para que sea mucho más fácil responder a la pregunta del if que evaluar la condición. Me explico:
if ($article->isPublished()) { // ... }
La función isPublished siempre devolverá un booleano, así es más facil seguir la traza del código.
Evita los else y apuesta por los “early returns”
La idea es que no haya demasiados else y que los casos base se resuelvan los más pronto posible.
Vamos a poner el ejemplo del cálculo de fibonacci:
function fibonacci(int $n) { if ($n < 50) { if ($n !== 0) { if ($n !== 1) { return fibonacci($n - 1) + fibonacci($n - 2); } else { return 1; } } else { return 0; } } else { return 'Not supported'; } }
Pero ¿y si intentamos primero los casos base/excepciones y luego el flujo normal):
function fibonacci(int $n): int { if ($n === 0 || $n === 1) { return $n; } if ($n > 50) { throw new \Exception('Not supported'); } return fibonacci($n - 1) + fibonacci($n - 2); }
Demasiado contexto
Esto es algo que me pasa siempre, intento dar buenos nombre y al final lo que acaba pasando es que me paso de frenada y las variables tienen demasiado contexto. Algo así tampoco es bueno.
class Car { public $carMake; public $carModel; public $carColor; //... }
Hay que mantener el equilibrio entre el gym y el ñam
Evita los nombres genéricos
En la búsqueda del equilibrio, también tenemos que tener en cuenta lo malos amigos que son los nombres genéricos. Nombres como Manager, Pool, Handle… funcionan en cualquier contexto, da igual que sea un ERP que una central nuclear en los dos habrá un Manager o un Handler. Así que lo mejor es que tengas cuidado con su uso. Estamos en lo de siempre el equilibrio entre el gym y el ñam.
class Email { public function handle(): void { mail($this->to, $this->subject, $this->body); } } $message = new Email(...); $message->handle();
¿Qué hace handle en este contexto?, queda un poco rato. Lo ideal sería cambiarlo por send, así le damos contexto a la clase Email. Ese contexto surge porque la clase Email realiza acciones “send”.
No utilizar flags
Las funciones no cuestan dinero, así que no te agobies cuando tengas que crear una nueva. Es cierto que reutilizar está bien, seguir el principio de única responsabilidad y todo eso. Pero no debemos perder la cabeza con estas cosas, así que una de las alertas para saber que has perdido un poco el foco es ver funciones con flags como esta:
function createFile(string $name, bool $temp = false): void { if ($temp) { touch('./temp/'.$name); } else { touch($name); } }
Creo que se podrían crear 2 funciones:
function createFile(string $name): void { touch($name); } function createTempFile(string $name): void { touch('./temp/'.$name); }
Proximidad
Declaro las variables tan cerca de su uso como sea posible. Algo así queda un poco raro, sobre todo a la hora de refactorizar y mover cosas de sitio se hace complicado con una estructura como esta:
function hi() { $a = $this->getA(), $b = $this->getB(), $c = $this->getC(), $d = $this->getD(); $this->doSomething($b); $this->doAnotherThing($a); $this->doOtherStuff($c); $this->finishUp($d); }
Mi idea es intentar escribir algo así:
function hi() { $b = $this->getB(); $this->doSomething($b); $a = $this->getA(); $this->doAnotherThing($a); $c = $this->getC(); $this->doOtherStuff($c); $d = $this->getD(); $this->finishUp($d); }
Ahora queda mucho más claro por lo que lo ideal es usar una variable justo después de su declaración.
Normalmente no es tan simple como este ejemplo, ya que hay dependencias y tal, pero la idea general es esta.
Resumen
Si tuviese resumir que es lo que intento o hacia donde empujo mis soluciones cuando estoy escribiendo código es hacia:
- Usar poca indentacion: eso hace que el código sea más entendible,
- Evito utilizar else para conseguir un flujo lineal del programa.
- Early return siempre que puedo: hacer todas las comprobaciones al principio para intentar ejecutar más código del necesario.
- Que el código se lea bien: variables con buenos nombres, sin abreviaturas excesivas y con el contexto adecuado.
- Explaining variables: En las guardas de los if intento que siempre haya una función o variable que empiece por «is» así sé si es true o false.
- Paciencia: poner un buen nombre no se consigue fácilmente, muchas veces hay que cambiarlo más de 1 o 2 veces.
- Equilibrio: nos pagan por solucionar problemas y por hacer buen código. Así que no hay que agobiarse, quizás en una primera pasada tomemos algún que otro atajo. No es malo, solo hay que llegar a ese punto en el que escribimos buen código sin perder la cabeza por el código bien escrito.
Este artículo se basa en este repositorio donde encontrarás muchos más ejemplos.
¿Qué hace handle en este contexto?, queda un poco rato
Me gustaMe gusta
Buen articulo
Me gustaMe gusta