Determinar el coste y la ubicación de la deuda técnica utilizando medidas de velocidad en una organización DevOps.

Caricatura de la deuda técnica

La deuda técnica se define (vía Wikipedia) como:

El coste implícito de la reelaboración adicional causada por la elección de una solución fácil ahora en lugar de utilizar un enfoque mejor que llevaría más tiempo.

Muchos asumen que esta deuda se encuentra únicamente en el código y miden la Complejidad Ciclomática, la Cobertura de Pruebas y/o utilizan un linter para determinar cuánta deuda hay y dónde reside. Estas medidas funcionan bien, pero no ponen de manifiesto qué tipo de deuda (complejidad, falta de pruebas, documentación deficiente) causa los mayores problemas o dónde es mejor empezar. Tampoco miden los tipos de deuda que son más difíciles de detectar, por ejemplo:

Duplicación de funciones
Todo el bloque de construcción se duplica, pero se escribe de manera diferente, tal vez en un idioma diferente
No hay diseño para la depuración
La depuración es difícil cuando un fallo se produce a distancia (en las instalaciones del cliente, en el departamento de control de calidad) debido a un registro deficiente, a la notificación de errores o a la variación entre versión y depuración.
Conjunto de pruebas lento
El conjunto de pruebas tarda mucho tiempo en ejecutarse, lo que supone un mayor tiempo de respuesta
...

Posiblemente, esto aleja un poco la definición estándar de Deuda Técnica, por lo que en Cristie utilizamos la siguiente:

El coste del trabajo adicional causado por la elección de una solución fácil ahora en lugar de utilizar un enfoque mejor que llevaría más tiempo.

Esto nos da una forma de averiguar esto:

  1. Si asumimos que es preferible comprometerse con más frecuencia
    (...y decimos que lo es, y animamos a la gente a hacerlo...)
  2. entonces la única razón para no comprometerse con más frecuencia es que no se puede
  3. y la razón por la que no puedes la definimos como nuestra deuda técnica porque
  4. si refactorizáramos, arquitecturáramos o mejoráramos de alguna manera, entonces podría.

Así que podemos medir la deuda técnica como

La cantidad de tiempo invertido en commits por encima de α desviaciones estándar que contienen un archivo que está a α desviaciones estándar de lo habitual.

Es decir, la cantidad de tiempo extra en los commits que contienen archivos que aparecen habitualmente en los commits que llevan más tiempo.

Cómo averiguar nuestro historial de commits

Escribimos un script en python que procesa los registros de subversión y git para producir:

  • El autor del compromiso
  • El tiempo de negocio desde que el autor hizo su último commit en cualquier repo
  • Los archivos de esa confirmación

A continuación, normalizamos estos commits para cada autor y elaboramos este gráfico:

Historial de compromisos normalizado

Un poco de ensayo y error (hay definitivamente una forma mejor de hacerlo... pero los conocimientos estadísticos se aprendieron durante la experimentación) determinaron que aplicar lambda x: np.log(x) ** 2 como dos transformaciones Box-Cox concurrentes da un muy distribución normal cercana y aplicando esto a la inversa se obtiene la línea de mejor ajuste en el gráfico anterior:

Historia de los commits transformados

Deuda técnica

Ahora podemos averiguar la deuda técnica calculando la puntuación z (una medida de lo "normal" que es una población que debería ser normal) para cada archivo como:

def zscore(commits):
"Z-Score per-file for all files in commits"
perpath = collections.defaultdict(list)
for commit in commits:
for path in commit.paths:
perpath[path].append(commit.interval)
return {path : sum(perpath[path]) / math.sqrt(len(perpath[path]))}

lo que significa que podemos calcular los intereses que estamos pagando por la deuda técnica:

def debt(commits, deviation=1):
"Total debt for this series of commits"
scores = zscore(commits)
debt = 0
for commit in commits:
for path in commits.paths:
if scores[path] > deviation:
debt += commit.duration -
inverse(0) -
inverse(deviation)
return debt * 100.0 / sum(c.duration for c in commits)

Utilizando una ventana deslizante podemos elaborar un gráfico de la deuda técnica a lo largo del tiempo y encontrar los archivos que contribuyen a ella. A continuación se muestra la población de commits para los archivos de alto interés en comparación con los normales:

Archivos de alto interés en comparación

Resultados

La deuda técnica de 2018 ronda el 20%:

Intereses de la deuda, 2018

Lo más importante es que esto nos da una lista de archivos en los que tenemos que trabajar (editado para proteger a los inocentes):

Z-Score% CompromisosMultiplicadorArchivo
3.181.66 %2.44xAppliance Dockerfile
0.824.76 %2.14xCódigo de licencia para el nuevo producto
2.940.98 %2.15xAPI REST remota de Python

En los casos anteriores, todo el equipo estuvo de acuerdo en que se trataba de áreas problemáticas. Nuestro diagnóstico, por orden, fue:

Appliance Dockerfile
El tamaño del dispositivo era demasiado grande, por lo que bajar los paquetes necesarios desde Nexus a través de la red provocaba un gran retraso en el viaje de ida y vuelta.
Para contrarrestarlo, redujimos considerablemente el tamaño del dispositivo.
Código de licencia para el nuevo producto
Este código fue comprado y tenía una serie de problemas:

  • Compilación errónea de arquitecturas de 32/64 bits y big/little endian
  • Funciones duplicadas, pero con nombres similares, por ejemplo CheckLicence vs check_licence
  • Nombres engañosos (dsUint32_t es en realidad sólo un int...)

Hemos refactorizado mucho este código y hemos añadido pruebas para aclarar esto.

API REST remota de Python
Esto tenía una alta complejidad cíclica y poca documentación pero, sobre todo, tenía un alto tiempo de ejecución de las pruebas de integración.
Esto se ha mejorado eliminando algunas de las etapas de la prueba de integración realizada para cada prueba.

Argumentos en contra de este enfoque

¿Qué pasa con las cosas que no van en el control de versiones?

En primer lugar... ¡todo debería estar en control de versiones! Es fácil. Por supuesto, siempre hay acciones que no lo están - pero éstas son independientes del commit actual (por lo que no afectarán a los datos cuando se toman en volumen) o afectan al commit actual - en cuyo caso son deuda técnica.

¿Y si algunas personas tienen más cuidado que otras?

Normalizamos por autor para intentar que nuestros datos se refieran al código y al proceso, y no al individuo.

Los intereses sólo se pagan cuando el código está activo, por lo que un descenso en los intereses puede significar que el código activo actual es mejor

Esto es muy cierto... y es definitivamente un problema. El código inactivo con una elevada deuda técnica representa un riesgo, pero en realidad no está frenando las cosas... todavía. Aquí es donde las métricas secundarias (complejidad, cobertura y linting) pueden ayudar, especialmente si se puede encontrar un vínculo con la medida empírica de la deuda. ...mira este espacio.