Escribiendo código limpio: complejidad de código
David Vicente Fuentes Colaboraciones 03/05/2018
Este artículo ha sido escrito por Juan P. Villafañez y publicado originalmente en el blog de Solid GEAR
A los usuarios realmente no les importa lo complejo que sea tu código mientras funcione y muestre cosas de una forma bonita. Podrías escribir Twitter entero en una línea kilométrica y los usuarios no notarán tal blasfemia. Sin embargo, tú como desarrollador y como jefe de producto debería preocuparte lo complejo que es el código que estás creando. Veamos algunas razones.
¿Por qué no queremos código complejo?
Listemos algunas razones técnicas:
- Es más complejo de leer. Leer y entender el código es un requisito para poder modificarlo correctamente
- Es más complejo de modificar. Cualquier cambio que tengamos que hacer en ese trozo de código implica saber lo que el código debe hacer y cómo debe comportarse en cada situación. Cuanto más complejo sea el código, más dificil será predecir las consecuencias de ese cambio.
- Es más complejo de testear. El número de tests requeridos para verificar que el código funciona aumenta. Esto normalmente implica que los tests podrían no ser suficientemente detallados a cambio de cubrir una porción más grande de funcionalidad.
También hay razones de gestión a tener en cuenta:
- El código complejo se convierte en una caja negra que nadie sabe lo que hace exactamente.
- Los bugs que aparecen en ese codigo no se solucionan, o si se hace hay un riesgo alto de que nuevos bugs aparezcan.
- Las nuevas funcionalidades necesitan hacer "workarounds" sobre los bugs que ese código pudiera tener (suponiendo que esos bugs no se arreglen dentro de ese código). Este nuevo código genera una reacción en cadena en el sentido de que esos "workarounds" añaden una complejidad innecesaria en esa nueva funcionalidad.
- Si el código necesita cambios (quizás soporte adicional o mejoras de rendimiento) podrías necesitar rehacer ese código desde el principio, con el correspondiente sobrecoste.
¿Cómo podemos detectar código complejo?
Aquí tienes unas ideas sencillas para detectar código potencialmente problemático:
- Funciones, clases y / o ficheros excesivamente grandes. Funciones por encima de 50 líneas, clases con más de 10 o 15 métodos o ficheros con más de 900 líneas suelen ser indicadores de que algo va mal. ¿Esa función realiza una sola tarea o hace más cosas? ¿Esa clase tiene una única responsabilidad o realiza las tareas de varios posibles componentes?
- Gran número de parametros en una función. Un líimte razonable suele ser entre 5 y 7 parámetros. De nuevo, si necesitas usar más parámetros quizás estás haciendo algo mal. ¿Necesitas todos esos parámetros o quizás necesitas un objeto?
- Gran número de dependencias para un objeto. Mismo razonamiento que el punto anterior.
Complejidad ciclomática
Si revisamos un concepto un poco más técnico, la complejidad ciclomática nos da una buena aproximación de lo complejo que es nuestro código. De forma sencilla, la complejidad ciclomática cuenta el número de sentencias "if" o condiciones que tiene una función. La definición formal cuenta el número de caminos independientes que tiene la función. Cuanto mayor sea este valor, más compleja será la función. Para dar una idea, para funciones, valores menores que 5-7 son buena señal, pero valores mayores que 10 indican que deberíamos cambiar el codigo. Esta misma idea se puede extender a clases, paquetes o incluso aplicaciones.
Soluciones simples para un código complejo.
Sin considerar soluciones complejas que impliquen una refactorización grande del código, las soluciones más sencillas implican mover el código de sitio.
- Puedes dividir las funciones grandes en otras más pequeñas, Estas funciones harán una pequeña parte de la función original.
- Si la clase tiene un gran número de funciones, debería ser posible agrupar algunas de esas funciones y moverlas a otra clase más especializada. A veces esto no es posible, pero otras veces la clase asume más responsabilidades de la que le corresponde, y esto causa el incremento del número de metodos de la clase. En cualquier caso, este punto se basa en que la clase sólo debería tener una única responsabilidad; si la clase necesita 30 métodos para cumplir su responsabilidad, entonces no hay nada que hacer.
- De forma similar, si una clase tiene un gran número de dependencias, debería ser posible agrupar esas dependencias para que nuestro código dependa de unos pocos objetos.
Por ejemplo, una función puede validar los parámetros, llamar a varios métodos de otros objetos, hacer varias comprobaciones para verificar que los métodos anteriors han funcionado correctamente, mandar notificaciones o eventos, y finalmente devolver un valor. Cada uno de estos pasos puede ocultar una gran complejidad, pero nuestra función será más fácil de manejar. Otro ejemplo: una clase puede depender, entre otras cosas, de una clase para validar correos, uno o varios proveedores de correo (que requieren configuración). y una clase para mandar el correo a traves de uno de los proveedores configurados. Deberías se posible crear una clase que dependa de esos 3 componentes, y que al mismo tiempo provea de una interfaz sencilla para mandar correos. Nuestra clase dependería de esta nueva clase en vez de las otras 3.