martes, 6 de febrero de 2018

Funciones - Clean code - Parte 1

En el tercer capítulo de Clean Code de Robert C. Martin expone la importancia de usar funciones. Lo cual, es el segundo paso para escribir un código más legible y comprensible, objetivo que persigue el libro Clean Code o código limpio. 

Funciones pequeñas

Las funciones deberían tan pequeñas como sea posible, aunque no hay definida una medida estándar en lo referente a la cantidad de líneas de código. No obstante, las funciones con dos o tres líneas de código también pueden ser buenas funciones.

Bloques e indentación

Los bloques que incluyen if, else y while deberían tener una línea de longitud, siendo esta una llamada a una función. Esta estrategia aporta valor documental, ya que la función puede tener un nombre descriptivo de lo que está sucediendo.
Esto implica que las funciones no deberían tener estructuras anidadas siendo el nivel de indentación de una o dos indentaciones a lo sumo.

Haz una cosa

Según establece el tío Bob:

«Las funciones deberían tener un único cometido; pero deberían realizarlo correctamente, y dedicarse en exclusiva al mismo

La razón por la que se escribir en funciones es la de descomponer un concepto más complejo en un conjunto de conceptos más sencillos.
En el siguiente ejemplo se quiere mostrar el código HTML para la publicación de un blog. 


Para ello se ha dividido el concepto «mostrar html del post» en una serie de pasos más sencillos, con conceptos más específicos.


Un nivel de abstracción por función

La mezcla de niveles de abstracción en una función siempre resulta algo confusa para los lectores. Encontrar conceptos con un nivel de abstracción alto como «getPost($id)» junto con niveles de abstracción medios «post::find($id)» y niveles bajos «$post == null» llevan al lector a tener que pensar si es un concepto o es un detalle de implementación.

Lectura descendente del código: la regla descendente

Lo ideal es leer el código debajo de forma descendente como si de un texto narrativo se tratase. También es aconsejable que todo tenga el mismo nivel de abstracción, con lo que  se consigue realizar una lectura y no una decodificación del código. A medida que vamos se desciende a los distintos niveles de abstracción es posible llegar a las instrucciones del código fuente que serán muy sencillas. A esto se le llama la regla descendente.

Otro enfoque consiste en leer el programa como si se tratase de un conjunto de párrafos «to» («para» en español) cada uno de los cuales describe un nivel de abstracción y hace referencia al siguiente párrafo «to» en el nivel de abstracción inmediatamente posterior.

Siguiendo el ejemplo de la publicación en un blog:

Para mostrar el código HTML de una publicación determinada se busca la publicación, se comprueba si existe y, si no existe se muestra una página de error, en cambio, si existe mostramos la página del post.

Para buscar la publicación se busca en la base de datos con el identificador de la publicación.

Para comprobar si la publicación existe se comprueba que la publicación existe.
Para mostrar la página de error se crea una plantilla de página no encontrada y se obtiene su código HTML.

Para mostrar la página de la publicación se crea una plantilla, se carga la misma con los datos de la publicación y obteniendo así el código HTML.

Cuando no se tiene práctica es complicado seguir esta regla y escribir funciones que tengan el mismo nivel de abstracción, aunque conseguirlo es fundamental ya que es la clave para crear funciones pequeñas y que sólo tengan un cometido.

Instrucciones switch

Los instrucciones switch son, por definición, instrucciones que tienen más de un cometido,  lo que incumple la norma anteriormente consignada. En ocasiones puntuales no se podrá evitar el switch pero podrá incluirse en un nivel de abstracción inferior. El polimorfismo resuelve este problema.

Si se observa el siguiente ejemplo en el cual se quiere obtener un menú distinto dependiendo del tipo de usuario:


Esta función es larga y, si en un futuro se añaden más tipos de usuarios, esta seguirá creciendo, lo cual incumple el Open Closed Principle (OCP, por sus siglas en inglés) debido a que habrá que modificar la función cuando se añada un nuevo tipo de usuario.

Además, la función tiene más de un cometido ya que devuelve varios tipos de menú, por lo que incumple el Single Responsibility Principle (SPR, por sus siglas en inglés) ya que tiene más de una razón para cambiar (cada uno de los tipos usuarios).

Y es muy posible que esta estructura se repita en más partes de la aplicación.
La solución consiste en esconder ese switch en una Abstract Factory y no permitir que los lectores lo vean. La factoría usará el switch para crear una instancia que derive de la clase Menu y las distintas funciones se definirán de forma polimórfica a través de la interfaz Menu.


El tio Bob establece que es el switch es justificable siempre y cuando este aparezca sólo una vez y se use para crear instancias polimórficas ocultas detrás de una herencia y de forma que el resto del sistema no pueda verlas. Aunque se trata más bien una directriz y no una regla fija.

Uso de nombres descriptivos

Cuanto más preciso sea el nombre de la función más fácil será comprender qué hace esa función. Cuanto más pequeña sea la función más fácil será encontrar un nombre que explique su cometido.

Los nombres largos son una buena opción mientras se siga una convención de nombres que sea fácil de leer y que homogenice todo el código. La coherencia en los conceptos también es la clave para mantener la comprensibilidad del código.

Argumentos de funciones

El número ideal de argumentos para una función es cero (niladic), seguido de un argumento (monadic) y dos argumentos (dyadic). Las funciones de tres argumentos (triadic) deberían evitarse en la medida de lo posible. Las funciones de más de tres argumentos (polyadic) no se deberían usar nunca.

Los argumentos tienen su propio concepto por lo que al pasarlos se obliga al lector a tener ese concepto en mente mientras lee la historia.

A la hora de realizar test, las combinaciones de argumentos aumentan la complejidad de los mismos por la cantidad de combinaciones que pueden llegar a surgir. Si no hay argumentos no hay complejidad añadida.

Los parámetros de salida son más difíciles de entender que los parámetros de entrada. En principio, es más normal hacer una llamada a una función con ciertos parámetros de entrada y esperar el valor de retorno de la función. Al utilizar los parámetros de entrada como salida también forzaremos al lector a mirar dos veces para comprender qué está pasando.

Formas monádicas habituales

Las razones más habituales para pasar un único argumento en una función son:
  • Hacer una pregunta sobre el argumento.
  • Hacer una operación sobre ese argumento.
En el primer caso se podría escribir «fileExists($filename)» para obtener un valor booleano mientras que en el segundo, con «getPathForFile($filename)» se hace una operación sobre el argumento devolviendo la ruta para un archivo.

Otra manera menos común es utilizar la función como un evento. La función utiliza el argumento para cambiar el estado del sistema. Por ejemplo la función «deleteFile($filename)» cambiaría el estado del sistema borrando un archivo.

Se desaconseja usar funciones monádicas que no sigan estas reglas. Usar los parámetros de entrada como argumentos de salida es confuso para un lector y debería evitarse.

Argumentos binarios

Los argumentos binarios revelan que la función tiene más de un cometido así que se deben evitar.
Una implementación sencilla con un argumento binario podría ser:


En estos casos es preferible separarlos en dos métodos distintos de manera que la legibilidad del código sea más clara.

funcion getAdministartorMenu()
function getDefaultMenu()

Martin Fowler tiene una publicación sobre este tema: Flag argument.

Funciones diádicas

Este tipo de funciones son más difíciles de entender ya que los lectores tienen que pararse a pensar cual es la función de cada parámetro. Pero incluso funciones tan simples como «assertEquals(expected, actual)» pueden llegar a generar confusión dando lugar a que un usuario ponga el valor de «expected» en el lugar de «actual» y viceversa.

En la medida de los posible se deberían utilizar los mecanismos pertinentes para convertirlas en funciones monádicas. 

La siguiente clase contiene un método (función diádica) «save» al cual se podrían aplicar una serie de refactorizaciones para convertirla en monádica.


Se puede refactorizar el parámetro «filename» como atributo de la clase de manera que esté accesible desde el interior de la función. Con esto se añade un nuevo método monádico,  que además nos permite poder asignarle un valor. La función «save» se convierte en monádica al poder acceder al parámetro «$filename» desde dentro de la propia función.


Se puede extraer la función de la instrucción «file_put_contents» facilitando la compresión del código. Para ello se extrae el método «saveToFile».


Para que toda la función tenga el mismo nivel de abstracción se puede hacer otra refactorización extrayendo en un método la cláusula de guarda.


Funciones triadas

Las funciones triadas son bastante más complejas de entender ya que, si con dos parámetros se puede confundir el orden, con tres las probabilidades aumentan.
En estas funciones los lectores tendrán que pararse a leer, entender cada parámetro, asimilar el orden, e incluso ignorar alguno de ellos en algunos casos.

Argumentos como objeto

Cuando una función necesita más de dos o tres parámetros es posible que algunos de ellos puedan agruparse para abstraerse en una nueva clase.

function make circle($x, $y, $radius)
function make circle(Point $center, $radius)

En este ejemplo, las coordenadas x e y pueden abstraerse en un nuevo concepto e implementar ese concepto con una nueva clase «Point».

No hay comentarios:

Publicar un comentario