Clean Code: reglas y principios.
0. Índice de contenidos.
- 1. Introducción.
- 2. DRY Principle.
- 3. The Principle of Least Surprise.
- 4. The Boy Scout Rule.
- 5. F.I.R.S.T.
- 6. Single Responsibility Principle.
- 7. Open Closed Principle.
- 8. Dependency Inversion Principle.
- 9. Conclusiones.
1. Introducción
Como ya comentábamos, Clean Code es el título de un libro escrito por Robert C. Martin (Uncle Bob) donde nos habla de cómo escribir «código limpio». En el anterior artículo comentábamos un poco las impresiones tras leer el libro.
En este segundo tutorial intentaremos explicar algunas reglas y principios referenciados en el libro, que nos ayudarán a escribir «código limpio».
2. DRY Principle (Don’t Repeat Yourself).
Probablemente, el mayor enemigo del código limpio es el código duplicado. El principio DRY (uno de los pilares de Extreme Programming) viene a recordarnos este hecho.
El código duplicado hace que nuestro código sea más difícil de mantener y comprender además de generar posibles inconsistencias. Hay que evitar el código duplicado SIEMPRE. Para ello, dependiendo del caso concreto, la refactorización, la abstracción o el uso de patrones de diseño (ej. Template Method) de diseño pueden ser nuestros mejores aliados.
3. The Principle of Least Surprise.
También conocido como The Principle of Least Astonishment, nos dice que: las funciones o clases deben hacer lo que (razonablemente) se espera de ellas. Es decir, una función o una clase debe tener, en función de su nombre, un comportamiento obvio para cualquier programador, sin que este tenga la necesidad de sumergirse en su código.
En el libro vemos el siguiente ejemplo (Day es un enum que representa los días de la semana):
Day day = DayDate.StringToDay(String dayName);
Cualquier programador al ver esta función (método) esperará que, si le pasa la cadena de caracteres «Monday», la respuesta sea Day.MONDAY. Incluso podríamos esperar que diese igual enviar la cadena con mayúsculas o minúsculas. Si esta función no hiciese esto su comportamiento no sería obvio y no estaría cumpliendo con el principio.
4. The Boy Scout Rule.
Curiosa regla con altas dosis de moralidad y profesionalidad aplicable a muchísimas profesiones y, como no, el desarrollo de software no es una excepción.
La regla del Boy Scout dice: «deja el campamento más limpio de como te lo encontraste». Ampliándola a otros ámbitos sería algo así como: «deja las cosas mejor de como te las encontraste».
Muchas veces, revisando código, nos encontramos con que el nombre de una variable no es demasiado intuitivo o con un fragmento de código duplicado. Resolviendo este tipo de matices (en vez de mirar hacia otro lado y pasar de largo), estaremos aplicando la regla del Boy Scout.
5. F.I.R.S.T.
Como ya sabemos, los test forman un papel fundamental en el arte de escribir código limpio. No obstante, no es suficiente con escribirlos de cualquier manera. Deben cumplir una serie de reglas.
- Fast: los test deben correr rápido. Deben tardar poco en ejecutarse. De no ser así, es probable que nos de pereza ejecutarlos y, por tanto, que no lo hagamos con la frecuencia deseada. El no ejecutar los test con relativa frecuencia puede hacer que tardemos más en encontrar los problemas que vayan surgiendo.
- Independent: los test deben ser independientes unos de otros. El resultado de un test no debe condicionar el de los siguientes. Deben poder ejecutarse en el orden que se quiera. De lo contrario, un fallo en el primer test, puede desencadenar un fallo en cascada de los demás, haciendo complejo el diagnóstico del sistema.
- Repeatable: los test deben poder ejecutarse en cualquier entorno (desarrollo, pre-producción, producción…). De no ser así, siempre tendremos una excusa para cuando los test fallen.
- Self-Validating: los test deben devolver una respuesta booleana. Pasan o no pasan. No deben dejar una cadena de caracteres en un fichero de log que tengamos que comprobar nosotros mismos, o dejar dos ficheros de un tamaño determinado que, igualmente tengamos que comprobar. De lo contrario, requerirán una alta evaluación manual que nos hará perder tiempo y precisión.
- Timely: los test deben ser escritos antes que el código de producción. De no ser así, el código de producción será difícil de testear.
6. Single Responsibility Principle.
El principio de Responsabilidad Única (la S de los principios SOLID), hace referencia al diseño de nuestras clases. Dice que: «una clase debe tener una y solo una razón para cambiar». Debe tener una única responsabilidad.
Veamos un ejemplo sacado del libro de una clase con más de una responsabilidad:
public class SuperDashboard extends JFrame { public Component getLastFocusedComponent() {...} public void setLastFocusedComponent(Component lastFocusedComponent) {...} public int getMajorVersionNumber () {...} public int getMinorVersionNumber () {...} public int getBuildNumber () {...} }
Como podemos observar, la clase SuperDashboard accede al último componente que ha tenido el foco y nos da información acerca de la versión. Ese «y» nos da una pista para saber que esta clase está haciendo más de una cosa.
Para cumplir con SRP, podríamos mover la información relativa a la versión a otra clase de la siguente forma:
public class Version { public int getMajorVersionNumber () {...} public int getMinorVersionNumber () {...} public int getBuildNumber () {...} }
De esta forma, el comportamiento sobre el manejo de la información de la versión (razón para cambiar) queda en una clase y el comportamiento sobre el último componente que ha tenido el foco queda en otra.
7. Open Closed Principle.
Otro de los principios SOLID (en este caso la O), igualmente hace referencia al diseño de nuestras clases y está estrechamente relacionado con SRP.
Dice que: «una clase debe estar abierta a extensiones pero cerrada a modificaciones». O lo que es lo mismo, el comportamiento de dicha clase debe ser alterado sin tener que modificar su código fuente. De lo contrario podría desencadenar efectos colaterales.
Si cambiasen los requisitos, el comportamiento de la clase debe ser extendido, no modificado. La inyección de dependencias también nos puede ayudar en esta tarea.
En el siguiente ejemplo (también del libro) vemos una clase abierta a modificaciones:
public class Sql { public Sql (String table, Column[] columns) {...} public String insert (Object[] fields) {...} public String findByKey (String keyColumn, String keyValue) {...} public String select (Criteria criteria) {...} }
Como se puede apreciar, si necesitasemos añadir una nueva operación (ej. update) tendríamos que modificar la clase. Además, si necesitásemos que el método select soportase subconsultas, también tendríamos que modificar la clase. Esta clase viola el anterior principio SRP además de estar abierta a modificaciciones.
Vamos a ver como quedaría para que no violase ni SRP ni OCP.
abstract class Sql { public Sql (String table, Column[] columns) {...} public abstract String generate(); } public class InsertSql extends Sql { public InsertSql (String table, Column[] columns, Object[] fields) {...} public String generate () {...} } public class FindByKeySql extends Sql { public InsertSql (String table, Column[] columns, String keyColumn, String keyValue) {...} public String generate () {...} } // etc...
Como se puede apreciar, si ahora necesitasemos la operación update, bastaría con crearla y EXTENDER de la clase Sql para implementar su comportamiento. Ahora se cumplen tanto SRP como OCP.
8. Dependency Inversion Principle.
De nuevo otro principio de diseño de clases. El principio de inversión de dependencias nos dice: que nuestras clases deben depender de abstracciones, nunca de detalles concretos. De esta forma podremos tener nuestras entidades desacopladas facilitando su mantenimiento.
Veamos un ejemplo (este me lo he inventado yo, no viene en el libro). Imaginemos que tenemos una entidad Employee que se apoya en otra para calcular su salario.
public class BasicEmployeeSalaryCalculator { public float getSalary (Employee employee) { // calcula el salario del empleado } } public class Employee { public float calculateSalary (BasicEmployeeSalaryCalculator employeeSalaryCalculator) { return employeeSalaryCalculator.getSalary(this); } }
Vemos que la clase Employee está fuertemente acoplada a la implementación BasicEmployeeSalaryCalculator. Ahora bien, ¿qué pasaría si hubiese otra forma de calcular el salario de un empleado? Por ejemplo, en función del mes (imaginemos que tiene paga extra). Delegaremos esa responsabilidad en la clase ExtraPayEmployeeSalaryCalculator. Sin embargo, el método calculateSalary de la clase Employee solo puede recibir un BasicEmployeeSalaryCalculator. Tenemos un problema.
Veamos como se resolvería si hubiesemos hecho caso del Principio de Inversión de Dependencias.
public interface EmployeeSalaryCalculator { public float getSalary (Employee employee); } public class BasicEmployeeSalaryCalculator implements EmployeeSalaryCalculator { public float getSalary (Employee employee) { // calcula el salario del empleado } } public class ExtraPayEmployeeSalaryCalculator implements EmployeeSalaryCalculator { public float getSalary (Employee employee) { // calcula el salario del empleado en función de su paga extra } } public class Employee { public float calculateSalary (EmployeeSalaryCalculator employeeSalaryCalculator) { return employeeSalaryCalculator.getSalary(this); } }
Vemos que ahora, la clase Employee está desacoplada del detalle concreto (BasicEmployeeSalaryCalculator) y, por el contrario se vincula a la abstracción (la interface). Por tanto, ahora bastará con llamar al método calculateSalary de la clase Employee pasándole la calculadora concreta que necesitemos en cada momento (incluso pueden surgir nuevas calculadoras de salario).
9. Conclusiones.
En este tutorial hemos intentado explicar algunas reglas y principios que son mencionados en el Clean Code. Aplicar estos principios en nuestro día a día nos ayudará a mejorar nuestro código.
Nótese que el libro hace referencia a otras muchas reglas y principios pero no da tiempo a escribirlas en un tutorial ;).
Espero que os haya sido de ayuda. Un saludo.
Miguel Arlandy
Muy buena reseña del libro. Aunque falta otro principio, el K.I.S.S. (Keep It Simple!, Stupid)..con este ya todo sería muuuucho mas facil. Muy buen trabajo Miguel.
K.I.S.S. puede ser también, en los círculos mas selectos y finos: Keep It Short and Simple.
Hola Miguel, felicidades por la claritud con la que has explicado estos principios.
La verdad es que en la red no se suele encontrar un artículo como el tuyo que a la vez menciona la teoría y la explica tan claramente con ejemplos sencillos.
Chapó, me ha gustado mucho!