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()
|
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