Una Explicación en Profundidad de la Complejidad del Código

No es un código secreto es algo complicado de escribir, depurar y mantener, lo que es necesario para una alta calidad de software. Además, la alta complejidad del código trae consigo un mayor nivel de defectos de código, lo que hace que el código sea más costoso de mantener.

Por lo tanto, al reducir la complejidad del código, podemos reducir el número de errores y defectos, junto con su costo de por vida. ¿Qué es exactamente el código complejo? ¿Cómo podemos evaluar objetivamente cuán complejo es un fragmento de código, si se trata de una base de código completa o de una función pequeña?

En este artículo, voy a recorrer tres métricas de complejidad para evaluar la complejidad del código. Estos son:

  • Complejidad ciclomática
  • Instrucción de conmutación y complejidad de condición lógica
  • Habilidad de desarrollador

También analizaré algunos de los beneficios de evaluar y comprender la complejidad del código.

Complejidad ciclomática

En 1976, Thomas McCabe Snr propuso una métrica para calcular la complejidad de código, llamada Complejidad Ciclomática. Se define como:

Una medida cuantitativa del número de rutas linealmente independientes a través del código fuente de un programa computed calculada utilizando el gráfico de flujo de control del programa.

Si no está familiarizado con un gráfico de Flujo de Control:

Es una representación, usando notación gráfica, de todas las rutas que se pueden recorrer a través de un programa durante su ejecución.

Dicho de manera más directa, cuanto menos caminos atraviesen una pieza de código, y cuanto menos complejos sean esos caminos, menor será la Complejidad ciclomática. Como resultado, el código es menos complicado. Para demostrar la métrica, usemos tres ejemplos de código Go, algo arbitrarios.

Ejemplo Uno

func main() { fmt.Println("1 + 1 =", 1+1)}

Como solo hay un camino a través de la función, tiene una puntuación de Complejidad Ciclomática de 1, que podemos encontrar ejecutando gocyclo en ella.

Ejemplo Dos

func main() { year, month, day := time.Now().Date() if month == time.November && day == 10 && year == 2018 { fmt.Println("Happy Go day!") } else { fmt.Println("The current month is", month) }}

En este ejemplo, estamos recuperando el año, mes y día actuales. Con esta información, verificamos si la fecha actual es el 10 de noviembre de 2018 con una condición if/else.

Si lo es, el código imprime » Happy Go day!»a la consola. Si no lo es, imprime «El mes actual es» y el nombre del mes actual. El ejemplo de código se hace más complicado como si la condición se compone de tres sub-condiciones. Dado eso, tiene una puntuación de complejidad más alta de 4.

Ejemplo Tres

func main() { _, month, _ := time.Now().Date() switch month { case time.January: fmt.Println("The current month is January.") case time.February: fmt.Println("The current month is February.") case time.March: fmt.Println("The current month is March.") case time.April: fmt.Println("The current month is April.") case time.May: fmt.Println("The current month is May.") default: fmt.Println("The current month is unknown.") }}

En este ejemplo, imprimimos el mes actual, basado en el valor de month, recuperado de la llamada a time.Now().Date(). Hay siete rutas a través de la función, una para cada una de las sentencias case y otra para la predeterminada.

Como resultado, su Complejidad Ciclomática es de 7. Sin embargo, si hubiéramos contabilizado todos los meses del año, junto con un incumplimiento, su puntuación sería de catorce. Esto sucede porque Gocyclo utiliza las siguientes reglas de cálculo:

1 es la complejidad base de una función
+1 para cada’ if’,’ for’, ‘case’, ‘&&’ o ‘||’

Usando estos tres ejemplos, podemos ver que al tener una métrica estándar para calcular la complejidad del código, podemos evaluar rápidamente cuán complejo es un fragmento de código.

También podemos ver cómo son diferentes secciones complejas de código en comparación entre sí. Sin embargo, la complejidad ciclomática no es suficiente por sí sola.

Complejidad de Condición lógica y sentencia de Conmutador

El siguiente evaluador de complejidad de código es la complejidad de condición lógica y sentencia de conmutador. En el siguiente ejemplo de código, he tomado el segundo ejemplo Go y dividido la condición if compuesta en tres condiciones anidadas; una para cada una de las condiciones originales.

func main() { year, month, day := time.Now().Date() output := fmt.Sprintf("The current month is %s", month) if month == time.November { if day == 13 { if year == 2018 { output = fmt.Sprintf("Happy Go day!") } } } fmt.Println(output)}

¿Cuál es más fácil de entender (o menos complicado), el original, o este? Ahora vamos a basarnos en esto, considerando las siguientes tres preguntas.

  • ¿Qué pasaría si tuviéramos, como lo hacemos anteriormente, múltiples condiciones if y cada una fuera bastante compleja?
  • ¿Y si tuviéramos múltiples condiciones if y el código en el cuerpo de cada una fueran bastante complejos?
  • ¿El código sería más fácil o más difícil de entender?

Es justo decir que cuanto mayor sea el número de condiciones anidadas y mayor sea el nivel de complejidad dentro de esas condiciones, mayor será la complejidad del código.

Nivel de habilidad de Desarrollador de software

¿Qué pasa con el nivel de habilidad del desarrollador? Echa un vistazo a la versión en C del segundo ejemplo de Go a continuación.

#include <stdio.h>#include <time.h>#include <string.h>int main(){ time_t t = time(NULL); struct tm tm = *localtime(&t); const char * months = {"January", "February", "March", "April", "May", "June", "July", "August", "September", "October", "November", "December"}; if (tm.tm_year == 2018 && strncmp(months, "November", strlen(months)) == 0 && tm.tm_mday == 10) { printf("Happy C Day!.\n"); } else { printf("The current month is %s.\n", months); }}

Técnicamente, hace lo que hacen los otros ejemplos. Sin embargo, se requiere más código para lograr el mismo resultado. Para ser justos, si tuviera una mayor familiaridad con C, el código podría no ser más largo que el ejemplo Go.

Sin embargo, digamos que este es el mínimo requerido para lograr el mismo resultado. Si comparas los dos, dada la naturaleza más detallada de la sintaxis de C en comparación con Go, es más difícil de entender.

Además, si no tuviera experiencia previa con C, a pesar de una puntuación de Complejidad Ciclomática comparativamente similar, ¿cuál sería su percepción?

¿Considerarías que el código es menos o más complicado? Este es otro factor esencial para entender la complejidad del código.

Los beneficios de Medir la complejidad del software

Hay cuatro beneficios principales de medir la complejidad del código, más uno extra.

Mejores pruebas

Al saber cuántas rutas independientes hay a través de un fragmento de código, sabemos cuántas rutas hay que probar.

Por cierto, no estoy abogando por una cobertura de código del 100%, a menudo es una métrica de software sin sentido. Sin embargo, siempre defiendo un nivel de cobertura de código tan alto como sea práctico y posible.

Por lo tanto, al saber cuántas rutas de código hay, podemos saber cuántas rutas tenemos que probar. Como resultado, tiene una medida de cuántas pruebas se requieren, como mínimo, para garantizar que el código esté cubierto.

Riesgo reducido

Como dice el viejo refrán:

Es más difícil leer código que escribirlo.

Además:

  1. El código se lee mucho más de lo que se escribe
  2. Un buen desarrollador de software nunca debe ser evaluado por las líneas de código que ha escrito (o cambiado), sino por la calidad del código que ha mantenido.

Dado que, al reducir la complejidad del código, se reduce el riesgo de introducir defectos, ya sean pequeños o grandes, un poco embarazosos o que inducen a la bancarrota.

Menores costos

Cuando se reduce el riesgo de defectos potenciales, hay menos defectos que encontrar y eliminar. Como resultado, el costo de mantenimiento también se reduce.

Todos hemos visto y estamos familiarizados con los costos asociados con la búsqueda de defectos en las diversas etapas de la vida de un software, como se ejemplifica en la tabla a continuación.

 Aumento del coste de los defectos

Así que tiene sentido que, si entendemos la complejidad de nuestro código, y qué secciones son más complicadas que otras, entonces estamos en una posición mucho mejor para reducir dicha complejidad.

Por lo tanto, al reducir esa complejidad, reducimos la probabilidad de introducir defectos. Eso fluye en todas las etapas de la vida de un software.

Mayor previsibilidad

Al reducir la complejidad del software, podemos desarrollar con mayor previsibilidad. Lo que quiero decir con eso es que somos más capaces de decir, con confianza, cuánto tiempo tarda una sección de código en completarse. Al saber esto, somos más capaces de predecir cuánto tiempo tarda una liberación en enviarse.

En base a este conocimiento, la empresa u organización está en mejores condiciones de establecer sus objetivos y expectativas, especialmente las que dependen directamente de dicho software. Cuando esto sucede, es más fácil establecer presupuestos realistas, pronósticos, etc.

Ayuda a los desarrolladores a aprender

Ayudar a los desarrolladores a aprender y crecer es el beneficio final de comprender por qué su código se considera complejo. Las herramientas que he usado para evaluar la complejidad hasta este momento no hacen eso.

Lo que hacen es proporcionar una puntuación de complejidad general o granular. Sin embargo, una herramienta de complejidad de código integral, como Codacy, sí lo hace.

 Lista de complejidad de archivos

En la captura de pantalla anterior, podemos ver que, de los seis archivos enumerados, uno tiene una complejidad de 30, una puntuación generalmente considerada bastante alta.

La complejidad ciclomática es un gran indicador para entender si la calidad del código se deteriora para cualquier cambio dado. La complejidad ciclomática puede ser más difícil de razonar al mirarla o comparar módulos enteros dada su escala infinita y no estar relacionada con el tamaño del módulo. Sin embargo, algo que podría resultarle útil es buscar en la lista de archivos de Codacy ordenados por prioridad, lo que le ayudará a comprender qué archivos son candidatos de mala calidad de código y, en consecuencia, sus módulos.

Eso es un ajuste

Además, esta ha sido una discusión en profundidad sobre qué es la complejidad del código, cómo se evalúa, así como los beneficios significativos de reducirla. Si bien hay más para entender la complejidad del código de lo que he cubierto aquí, hemos recorrido un largo camino para entenderlo.

Si es la primera vez que escucha sobre el término o aprende sobre cualquiera de las herramientas, le animo a explorar los artículos y herramientas vinculados, para que aprenda más. Si no codificas en Go o C, busca en Google la «herramienta de complejidad de código» más el idioma del software. Seguro que encontrará muchas herramientas disponibles.

Para obtener más consejos para mejorar la calidad del código, echa un vistazo a otras publicaciones de blog de Codacy.

Finalmente, si desea una herramienta completa para evaluar la calidad del código, y una que ayude a sus desarrolladores a aprender y crecer, pruebe Codacy.

¡Codacy es utilizado por miles de desarrolladores para analizar miles de millones de líneas de código todos los días!

¡Comenzar es fácil y gratis! Solo usa tu cuenta de GitHub, Bitbucket o Google para registrarte.

EMPEZAR

Deja una respuesta

Tu dirección de correo electrónico no será publicada.