lunes, 19 de febrero de 2018

Comentarios - Clean code - Parte 2

Esta publicación es la continuación de la anterior publicación: Comentarios - Clean code - Parte 1

Comentarios confusos


En ocasiones habrá comentarios que no sean lo suficientemente precisos y provoquen que el lector termine finalmente leyendo el código para averiguar el porqué de ese comportamiento distinto al expresado.


En el siguiente ejemplo cualquier programador se extrañaría al descubrir que se están añadiendo «likes» al usuario.


Comentarios obligatorios

No tiene ningún tipo de sentido incluir comentarios obligatorios sólo porque haya una regla que lo indique o porque el IDE los proporcione automáticamente. Estos comentarios no aportan valor, ensucian el código y lo hacen más confuso, ya que los lectores no distinguen los comentarios con valor de aquellos que carecen de él.

Comentarios periódicos

Hace tiempo, cuando los controladores de versiones estaban menos evolucionados, se usaban para llevar un registro de los cambios añadiendo estos a un comentario al comienzo del fichero. Afortunadamente hoy en día no es necesario por lo que deberían desaparecer.

Comentarios sin valor

Los comentarios generados automáticamente por un IDE son comentarios sin valor que muestran lo que es obvio.

Debido a esto, el lector aprende a ignorar estos comentarios pasándolos por alto y, por lo tanto, esto puede llegar a hacer ignore comentarios que sí aporten algo sustancial.

Los comentarios que no aportan nada suelen derivar de la producción de un mal código por parte de un programador que, en lugar de invertir tiempo en redactar el comentario, debería haber limpiado el código.

Comentarios sin valor, copiados y pegados

En ocasiones, cuando se copia y pega un código se incluyen los comentarios en la copia. Una vez actualizado el código los comentarios pueden no actualizarse correctamente por lo que el lector tendrá que perder tiempo en entender lo que está leyendo.

En el siguiente ejemplo se puede observar como la variable «$address» presenta un comentario con un nombre distinto, lo que obliga al lector a leer varias veces el código hasta comprender que el comentario está mintiendo.

No usar un comentario cuando se puede usar una función o una variable


En determinadas ocasiones se podrá refactorizar el código de una manera que exprese lo mismo que el comentario.


Podría refactorizarse de la siguiente manera:


Marcadores de posición


Los marcadores de posición se usan para llamar la atención del lector en una parte del archivo.

Esto rara vez aporta valor por lo que se deberían evitar.

Comentario después de cerrar llaves


De nuevo, cuando no había control de versiones, podría haber tenido algún sentido. En un futuro, este código comentado permanecerá ahí ya que si quien lo redactó no lo borró es que tendría alguna buena razón, por lo tanto, los programadores siguientes no lo borrarán tampoco. Un ejemplo:

Atribuciones


Los comentarios que son atribuciones no tienen ningún sentido con los controles de versiones actuales.

Comentar código obsoleto


De nuevo, cuando no había control de versiones, podría haber tenido algún sentido. En un futuro, este código comentado permanecerá ahí ya que si quien lo redactó no lo borró es que tendría alguna buena razón, por lo tanto, los programadores siguientes no lo borrarán tampoco. Un ejemplo:

Si el cálculo del valor de un post ha cambiado no debería existir código que no fuera útil.

Comentarios HTML

Se desaconseja añadir HTML en un comentario del código fuente para que alguna herramienta lo muestre. Debería ser la propia herramienta la que lo interprete y lo maquete.

Comentarios sobre código no cercano


Los comentarios deben añadir información sobre el código cerca de donde aparece. Por lo tanto, debe evitarse presentar información de otras partes del sistema.

En el siguiente ejemplo se añade el puerto por defecto para conectar con la base de datos pero, en un futuro, podría cambiar dicho puerto y el comentario permanecería.

Comentarios con demasiada información


Son comentarios que aportan información irrelevante para el funcionamiento del sistema. Añadir el RCF para la especificación de un email para una función que valide un email es un exceso de información.

Comentarios que no son obvios


Cuando un lector se encuentra un comentario este debe describir la intención del código. Si el propio comentario necesita un comentario para entenderse. el fallo de expresión del programador es completo, lo que obliga al lector a leer el mal código.

En el siguiente ejemplo observamos el cálculo de una publicación. El valor inicial es 0 pero si esta publicada y filtrada se realiza el cálculo de otra manera. Cuando el lector se encuentra la función «isNotFilter», esta no proporciona ningún tipo de intención ya el lector desconoce el significado de «no estar filtrado» y se verá obligado así a leer esa función para entender su intención.

Cabeceras de funciones


Si se sigue la regla que especifica que cada función debe tener un solo cometido, siendo esta lo suficientemente corta y nombrada adecuadamente, los comentarios de cabecera no aportan ningún valor.


Documentación en código que no va a ser público

La documentación es útil para las API que van a ser públicas. Incluir este tipo de documentación para la parte privada del código no suele ser útil, ya que es, a menudo, más una distracción que una ayuda.


jueves, 15 de febrero de 2018

Comentarios - Clean code - Parte 1

Esta publicación se centra en el capítulo de Comentarios de Clean Code (Robert C. Martin).

Los comentarios son una herramienta para transmitir conocimiento. Si estos se utilizan con el fin de expresar de una manera más clara el propósito del código, esto significará que no se está desarrollando un código lo suficientemente comprensible y, por lo tanto, que la capacidad de expresión del programador está fallando.

El uso de comentarios no suele considerarse una buena práctica porque la mayoría de las veces mienten, principalmente, porque quedan desactualizados al no hacerse un mantenimiento de los mismos.

Es mejor no incluir comentarios que incluir comentarios poco precisos, ya que estos producen confusión en el lector. Lo único que es verdad siempre es el propio código.

Los comentarios no compensan un mal código

Es mejor invertir el tiempo en reescribir un mal código que en comentarlo.

Explicarse en el código

A veces resulta un poco más difícil explicarse en el código, pero invertir algo de tiempo y esfuerzo en tratar de explicar la intención del mismo revierte en una mayor comprensibilidad.

Por ejemplo se puede escribir:



Se puede evitar el comentario invirtiendo tiempo en realizar una refactorización de la condición que derive en la creación de una nueva función «canUserEditAPost()» que comunique la intención de dicha condición:



Comentarios útiles

Hay algunas ocasiones en las que los comentarios son necesarios o beneficiosos aunque no sea así por regla general.

Comentarios legales

A veces las empresas fuerzan a escribir cierto tipo de comentarios por razones legales.

Comentarios informativos

En ocasiones, un comentario informativo podría ser de utilidad si evita al lector tener que decodificar algún código.

Por ejemplo, en el caso de escribir una expresión regular que pueda llegar a ser relativamente compleja, el comentario podría comunicar la intención de esta sin que el lector tenga que invertir tiempo en comprender su intención.



Explicación de la intención

En algunos casos, un comentario brinda información útil sobre la implementación y la intención detrás de una decisión. Es posible que no se comparta la solución adoptada pero por lo menos se conoce la intención.

Por ejemplo, se pueden comparar dos publicaciones y valorarlas.


Clarificación

A veces resulta útil clarificar algo que es complicado de leer y convertirlo en algo más comprensible aunque se corre el riesgo, como con cualquier comentario, de caer en incorrecciones, por lo que siempre es recomendable tratar de buscar una alternativa.



Advertencia de consecuencias

A veces es útil advertir a otros programadores de alguna consecuencia.



Hay maneras más seguras de no ejecutar ese test pero la intención es comunicar que, cuando se utiliza, el servidor va sufrir una carga significativa que, en el momento de escribir el comentario, saturaba el servidor.

Comentarios TODO

Los comentarios TODO son comentarios sobre cosas que los programadores piensan sobre cómo debería estar hecha una cosa pero por alguna razón no se pudo hacer. Podría ser un recordatorio para borrar alguna función que esté obsoleta, una nota para mejorar la implementación cuando el tiempo lo permita, o un cambio que depende de una decisión que aún no está tomada.

Un TODO no debería ser una excusa para dejar un mal código en el sistema.

Amplificación

Un comentario podría amplificar la importancia de algo que, a priori, podría parecer más trivial.



Documentación

La documentación en las cabeceras de los métodos es muy útil si hay una API pública que mantener. En el caso contrario, la documentación es como cualquier otro tipo de comentario que puede no estar mantenido.

Comentarios malos

La mayoría de los comentarios son de este tipo, ya que suelen ser declaración de intenciones para un código que no es capaz de expresarse adecuadamente.

Inútiles

Son aquellos comentarios a los que el autor, incapaz de transmitir la intención ni a través de estos, no ha dedicado suficiente tiempo.



En este caso parece que, si se produce una excepción, los valores por defecto ya están cargados pero surgen dudas al respecto: ¿dónde se cargan esos valores por defecto? ¿El fichero de configuración en la ruta «$pathToConfig» no es el fichero por defecto? ¿Por qué el autor deja el catch vacío? Al final parece que el único recurso para conocer el funcionamiento real es examinar el código.

Cualquier comentario que obligue a examinar el código para entender su significado es un comentario inútil.

Comentarios redundantes

Son comentarios que no aportan un valor real ya que se tarda más tiempo en leerlos que en leer el código.



Todos los comentarios generados automáticamente por un IDE tampoco aportan valor real. En el caso siguiente se crean en la cabecera del método.



martes, 13 de febrero de 2018

Funciones - Clean code - Parte 2

Esta publicación es la continuación de la segunda parte del capítulo de Funciones de Clean Code (Robert C. Martin).

Listas de argumentos

En algunas ocasiones se pasará una lista de argumentos en una función. Si estos argumentos se tratan de la misma manera se podrán considerar como un único tipo de argumento de tipo lista.


Esta función es diádica puesto que los argumentos se tratan de la misma manera.


Esta regla se puede aplicar a funciones monádicas, diádicas o triádicas pero sería un error incluir más argumentos.

Verbos y palabras clave

La elección de nombres óptimos es, junto con el orden de los argumentos, la clave para transmitir la intención de la función. Las funciones monádicas deberían formar una tupla verbo / sustantivo. Por ejemplo «delete($filename)». El nombre del archivo «$filename» se está borrando.

Un nombre mejor sería «deleteFile($filename)» lo cual indica que un fichero (File) con un nombre de fichero «$filename» se va a borrar.

Sin efectos secundarios

Los efectos secundarios son acciones que se producen en la función y que no están descritas en el nombre. Esto rompe la norma que especifica que la función debe tener un solo cometido. Estos cambios pueden producirse en alguna variable de la función o el sistema y en otras ocasiones pueden derivar en extraños acoplamientos temporales y dependencias de orden.

La siguiente función valida un «password» y devuelve un valor «true» o «false».

El efecto secundario es iniciar una sesión. Esto crea acoplamientos temporales ya que sólo se podrá llamar a esta función cuando sea seguro iniciar una sesión pues de otra manera se podrían perder los datos de la sesión actual.

Argumentos de salida

Cuando un lector se encuentra con un argumento de salida tendrá que examinar el código dos veces para comprender qué está pasando, ya que los argumentos de una función se suelen usar normalmente como argumentos de entrada. Por esta razón se deben evitar los argumentos de salida.

Si una función tiene que cambiar el estado de algo debería cambiar el estado de su propio objeto.

Separación de consultas de comando

Las funciones deberían hacer algo o responder a algo pero no ambas cosas. Esto vulnera el principio que especifica que una función no debe tener más de un cometido.

La siguiente función asigna un valor y devuelve «true» o «false» dependiendo de si la asignación se ha hecho correctamente:


Esto puede llevar a encontrar situaciones como:


Desde el punto de vista de un lector, lo más posible es que, cuando llegue a esta parte del código, se pregunte si «set» se está utilizando como un verbo o como un adjetivo, si se intepreta como un verbo entonces se está asignando un valor y si se interpreta como un adjetivo entonces se está preguntando si la variable tiene un valor determinado.

Podría sustituir el nombre de la función por «checkAndSetAttribute» pero la función vulneraría el principio que especifica que las funciones deben tener un solo cometido.

La solución real es separar la consulta del comando para evitar ambigüedades:

Mejor excepciones que códigos de error

Devolver un código de error es una leve vulneración de la norma que especifica que cada función debe dedicarse a un solo cometido.

Además, surge el problema de tener que controlar el error en ese momento. Esto conlleva la creación de estructuras anidadas más complejas.


Si se usan excepciones se consigue evitar vulnerar dicho principio y tener que controlar el error en ese momento.


Extraer los bloques try / catch

Los bloques «try» y «catch» mezclan el código con la gestión de errores por lo que es recomendable extraer cada bloque de código con el fin de mejorar la legibilidad.


Al extraer el bloque de código en una función se da a entender que se realiza un proceso de borrado y un proceso de gestión de error, lo cual, facilita la compresión del código.

El control de errores es un cometido

Como las funciones deberían dedicarse a un solo cometido, cuando hay bloques «try» y «catch» en una función no debería haber nada más que dicho bloque «try» y «catch».

Imán de dependencias

Los códigos de error deberían estar definidos en un sólo lugar, ya sea una clase o una enumeración. En este caso vulnera el principio SRP, ya que tiene dos razones para cambiar, una si un código cambia y otra si hay que añadir un código nuevo. También vulnera el Open Close Principle ya que la clase no debería cambiar por añadir un nuevo código de error.

En el caso de usar excepciones se pueden crear nuevas clases que deriven de una excepción más general respetando de esta manera el Open Close Principle.

No repetirse

El principio Don’t repeat yourself se centra en reducir la duplicación de código mediante distintas abstracciones.

Si algunas partes de código están duplicadas se corre el riesgo de que no estén todas sincronizadas dando lugar a errores. Por lo que habría que abstraer ese código en una función o clase de manera que se encuentre en un único lugar dentro del sistema.

Programación estructurada

El programador holandés Dijkstra estableció que cada función o bloque debería tener una entrada y una salida por lo que cada función debería tener sólo una instrucción «return» y los bloques de tipo bucle no debería tener «break» ni «continue».

Una vez más, se trata de directrices más que de reglas fijas por lo que en ocasiones, la comprensión del código puede mejorar en funciones pequeñas aun presentando múltiples salidas o instrucciones «break» y «continue».

Cómo escribir este tipo de funciones

El primer paso es escribir una función que haga lo que tenga que hacer, es decir, que funcione. En esta primera fase las funciones son extensas, con distintos niveles de anidamiento, código duplicado, etc. También debería haber una cobertura de test que se adecue a las funcionalidades y verifique el comportamiento.

A partir de ese momento se debería empezar a modificar el código para mejorar su legibilidad, aplicando distintas estrategias como extraer métodos, renombrar variables y métodos, eliminar duplicados, extraer clases, etc.

Conclusión

Los buenos programadores piensan en los sistemas como historias que contar más que en programas que escribir. Ellos usan los recursos del lenguaje de programación para construir una historia más rica y más expresiva.

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».