El objetivo de este blog es repasar los conceptos básicos que podemos aplicar día a día para que nuestro código sea lo más limpio y mantenible posible.
Conceptos básicos para mejorar el código
Convención de nomenclatura o Naming conventions
Cada lenguaje tiene unas normas básicas que todo programador debería de seguir. Además todos los IDEs modernos tienen soporte sintáctico para dichos lenguajes y nos presentan avisos visuales y propuestas para cumplir las normas.
Utilizar nombres coherentes
Muchas veces cuando estamos implementando código nos encontramos con que no sabemos exactamente qué nombre utilizar.
Como norma general a la hora de nombrar variables se recomienda que el nombre sea claro y conciso. Por lo que debemos de evitar usar letras simples para nombrar variables o nombres muy largos. Uno de los errores más comunes es utilizar la descripción de la implementación como parte del nombre. Por ejemplo si tenemos un mapa de productos por categoría, es recomendable usar un nombre como productsByCategory que productsMap.
Cuando nombramos un método debemos de intentar describir su comportamiento en el nombre de este. Generalmente si vemos que utilizamos conjunciones ( y / o) en los nombres de nuestros métodos y estos contienen código directamente implementado en lugar de llamar a otros métodos es señal de que podemos refactorizar para tener varias funciones.
Agrupar el código en funciones lógicas
Se recomienda que los métodos no tengan más de 35 líneas de código. Obviamente hay situaciones en las que esto puede no ser tan sencillo de conseguir, pero sí que debemos de estar atentos a la lógica que implementamos ya que muchas veces se puede fragmentar en varias funciones más pequeñas donde cada una tiene sentido por sí misma.
Esta buena práctica permitirá que quién tenga que modificar el código dentro de unos meses (y posiblemente vuelvas a ser tú) pueda entender a alto nivel la lógica simplemente viendo los nombres de las funciones y realizar los cambios oportunos de forma mucho más rápida, ordenada y precisa.
Ejemplo:
// BAD
complex_function:
// simple functional operation one
// simple functional operation two
// GOOD
complex_function:
simple_function_one()
simple_function_two()
Parámetros de código
Todas las funciones necesitan parámetros para ejecutar su lógica, el problema empieza cuando en lugar de uno o dos parámetros nos encontramos con funciones gigantescas que utilizan más de 5 o 10. Se recomienda que las funciones no tengan más de tres parámetros, siendo las más comunes las de 1 y 2.
Uno de los errores más comunes que podemos encontrar a nivel de código es no tener una encapsulación correcta de los datos, si esto ocurre en lugar de pasar el encapsulado como parámetro, entonces nos vemos obligados a pasar todas las variables una a una.
Ejemplo:
// BAD
calculate_distance(int x1, int x2, int y1, int y2)
// GOOD
calculate_distance(Point p1, Point p2)
Otro error muy común es implementar funciones donde se pasan tanto los datos encapsulados como parte de ellos también de forma individual.
Ejemplo:
var customer = …
var name = customer.name
// BAD
var greetings = greetings(customer, name)
// GOOD
var greetings = greetings(customer)
Headcoding
Los valores hardcodeados hacen que el código sea mucho más difícil de entender y de mantener. Se recomienda que todos los valores constantes se extraigan a variables estáticas con una visibilidad adecuada dependiendo de donde se usen y con una nomenclatura que sea comprensible.
Comentarios
Como regla general podemos asumir que si necesitamos comentar el código es porque algo no estamos haciéndolo bien del todo. Por ejemplo, si necesitamos comentar distintas zonas lógicas dentro de una función significa que seguramente podemos dividirla en varias funciones a bajo nivel, si necesitamos comentar que implica un valor hardcodeado significa que tendríamos que haberlo convertido en una constante. Finalmente si el comentario tiene utilidad hay que tener en cuenta que también se debe de mantener, por lo que si el código cambia, también debemos de ser responsables de actualizarlo si procede.
Código muerto
Hoy en día todos los equipos de desarrollo utilizan herramientas de control de versiones para mantener la trazabilidad de los cambios. Debido a este motivo no tiene ningún sentido mantener fragmentos de código comentado o funciones / parámetros que ya no se utilizan en nuestro código. Este tipo de comportamientos lo único que hacen es añadir más ruido a cualquiera que tenga que mantener dicho código.
Refactorizar
Mientras desarrollamos una nueva funcionalidad o ampliamos una nueva existente posiblemente nos encontraremos con el caso de que la aproximación utilizada para intentar resolver el problema no se adapta bien del todo. En este punto se suele tomar una de las siguientes filosofías: si funciona no se toca o se refactoriza para dejar el código más compacto y mantenible.
Si se toma la decisión de dejarlo como está, estamos añadiendo deuda técnica al proyecto. Además por regla general cada vez que se toma dicha decisión sobre la misma zona de código esta aumenta de forma exponencial, causando que cada nuevo mantenimiento requieran sobre esfuerzo desmedido para poder mantener dicha parte del código.
Cuando tomamos la decisión de refactorizar, podemos mejorar la salud del código mediante un abanico de acciones como por ejemplo:
-Extraer constantes
-Renombrar variables
-Reorganizar métodos
-Cláusulas de salvaguarda
-Encapsular datos y comportamientos en clases
-Aplicar patrones como estrategia para tener una mejor solución al problema
Hay que tener en cuenta que esto afecta tanto al código en sí como a los tests. Un conjunto de tests difícil de mantener termina siendo abandonado.
Hacer uso de comentarios especiales TODO / FIXME
Muchos IDEs modernos dan soporte a comentarios especiales mostrándose en listados globales y permitiendo navegar directamente a ellos. Estos son perfectos para detectar posibles puntos débiles del código y dejar una pequeña documentación de que problema tiene e incluso qué estrategia se podría realizar para solventar dicho problema. Estos comentarios son de grán valor para todos los programadores que luego tengan que mantener el código siempre y cuando el comentario asociado sea coherente. No nos sirve de nada los comentarios del tipo “Revisar” o “Esto está mal” ya que no aportan absolutamente ningún valor a quién los lea. Estos comentarios no deben de eliminarse de forma arbitraria simplemente porque vayamos a sacar una nueva versión.
Logging
El contexto importa. Muchas veces cuando tenemos que revisar una incidencia o un comportamiento inesperado, podemos ver claramente el resultado, pero muchas veces no sabemos el estado inicial ni las operaciones que se han realizado.
El logging nos ayuda a entender que ha ido ocurriendo durante la ejecución, pero un logging incorrectamente definido/configurado puede ser tan poco útil como no tenerlo.
Lo primero que tenemos que tener en mente cuando hablamos del logging es el nivel que deberíamos de mostrar en cada mensaje.
- El error como su propio nombre indica debería de ser para errores inesperados, situaciones críticas que rara vez deberían de ocurrir en nuestros sistemas.
- El warning debería de ser para situaciones que no son las ideales o para errores esperados.
- El info debería de ser para tener constancia de puntos de información importantes.
- El debug para tener una importante trazabilidad de datos.
Una correcta gestión de los log levels luego nos permitirá utilizar herramientas de gestión de logs / errores de forma más eficiente. Un ejemplo de ello es Sentry, el cual nos presenta en tiempo real los errores (realmente podemos configurar el log level mínimo a gestionar, entre otras cosas) que van ocurriendo en nuestro sistema.
El segundo aspecto importante de los logs es la información que vamos a mostrar. Está debe de ser clara, no inducir a confusión y tener todos los datos necesarios. Como norma general una excepción no esperada siempre debería de mostrar la traza completa del error. Todos los logs siempre deberían de mostrar los valores entre comillas y se deberían de mostrar todos los valores que se consideren necesarios.
Ejemplo:
// BAD
log.error(“Product error”)
// GOOD
log.error(“Error processing product ‘{}’ :”, product.id, exception)
Principios / Reglas
DRY
DRY (Don’t repeat yourself) hace especial hincapié en evitar duplicar código. Si para implementar algo tenemos que hacer un copy paste de un fragmento de otra parte del código, seguramente podamos aplicar este principio y refactorizar.
KISS
KISS (Keep it simple, stupid) hace referencia a que muchas veces nosotros mismos nos complicamos a la hora de implementar una solución, añadiendo condiciones o casos que nunca se van a dar. Por ejemplo, el validar su una lista que devuelve otra función es null “por si acaso” cuando realmente el contrato de la otra función indica explícitamente que nunca devolverá null es un claro ejemplo de complicar las cosas de forma totalmente necesaria.
SOLID
S de responsabilidad única (Single responsibility): nos indica que una clase o método solo debería de encargarse de una única cosa.
O de abierto/cerrado (Open/Closed): el código debería de estar abierto para su extensión pero cerrado para su modificación.
L de sustitución de Liskov: Los tipos deberían de poder sustituirse por subtipos sin alterar el correcto funcionamiento del programa.
I de segregación de interfaz (Interface segregation): Hay que tener interfaces.
D de inversión de dependencias (Dependency inversion): Las clases deberían de depender de interfaces y no de implementaciones explícitas.
RTFM
RTFM (Read the fucking manual) hace referencia a que muchas veces cuando estamos haciendo uso de otras funciones o librerías de terceros, en lugar de dedicar algo de tiempo en entender cómo funcionan leyendo la documentación oficial, dedicamos bastante más tiempo aplicando el “prueba-error” hasta que obtenemos un resultado que creemos que es el que queremos (y que luego puede resultar en que no lo és).
BOY SCOUT
Esta regla implica dejar el código en mejor estado que como lo encontraste.
Libros recomendados
- Clean code collection (Robert C. Martin)
- xUnit Test Patterns: Refactoring Test Code
- Refactoring: Improving the Design of Existing Code
- Working Effectively with Legacy Code
¡Contacta con nosotros!
Author