¿Cómo se mide significativamente la mantenibilidad?


23

Contexto: soy un desarrollador empresarial en una tienda de MS.

¿Alguien puede recomendar una buena manera de medir objetivamente la mantenibilidad de un código o una aplicación?

Por qué mantenibilidad : estoy cansado de las métricas de "calidad" en mi grupo que giran solo alrededor del número de errores y la cobertura del código. Ambas métricas son fáciles de jugar, especialmente cuando no estás midiendo la mantenibilidad. La miopía y los plazos dan como resultado enormes cantidades de deuda técnica que realmente nunca se abordan.

Por qué la capacidad de medir objetivamente : trabajo en un gran grupo empresarial. Si no puede medirlo objetivamente, no puede responsabilizar a las personas por ello ni hacer que mejoren en ello. Las mediciones subjetivas no suceden o no suceden consistentemente.

Estoy mirando las métricas del código VS2010 , pero me pregunto si alguien tiene alguna otra recomendación.


@Anon - Estoy de acuerdo, pero al menos me daría un lugar para comenzar. En este momento no hay nada; Ni siquiera tiene que ser jugado.
nlawalker

1
Realmente no veo cómo podría hacer esto sin revisiones de código de pares. Alguien necesita comprender realmente el diseño general del sistema (y uno debe existir) para mirar una unidad de código e ir ... hm, esto podría mejorarse con un diseño mejorado o esto es la sustitución del código o un buen señor, sus herramientas están desactualizadas ... En una nota similar, podría mantener pautas generales como, "Hola chicos, no es una buena idea codificar los índices en vistas de cuadrícula, usar plantillas de elementos y seleccionar columnas por nombre". Cuando se trata de eso, los desarrolladores solo tienen que ser buenos y fáciles de formar. Da Vinci no puede enseñar genialidad.
P.Brian.Mackey

8
Si tiene métricas de juego para desarrolladores en lugar de escribir un buen código, entonces agregar más métricas solo dará como resultado que también jueguen esas métricas, pero no resolverá el problema . La solución es eliminar por completo las métricas y utilizar otros medios (revisiones de código público, por ejemplo) para garantizar la calidad del código.
Anon

3
"Todo lo que se puede contar no necesariamente cuenta; todo lo que cuenta no necesariamente se puede contar". -Einstein
Jason Baker

@nlawalker Además de los problemas que ya respondieron los encuestados, su pregunta se carga con una suposición cuestionable, de que si existiera tal medida, las personas podrían hacer algo al respecto. La baja capacidad de mantenimiento es el resultado de varios factores externos al software en sí: cuán difícil o bien definido es el problema que el programa intenta resolver, la experiencia del personal, la rotación, los requisitos de tiempo de comercialización, los cambios en el alcance ... simplemente no puede ofrecer una recompensa Esperando que el problema sea una cuestión de buena voluntad.
Arthur Havlicek

Respuestas:


7

La advertencia con la medición de la capacidad de mantenimiento es que está intentando predecir el futuro. La cobertura del código, el recuento de errores, el LOC, la complejidad ciclomática se ocupan del presente .

La realidad es que, a menos que tenga pruebas concretas de que el código no se puede mantener como está ... es decir ... arreglar un error causó N cantidad de horas de tiempo innecesario debido a un código que no se puede mantener; entonces tener una pierna para pararse será inherentemente difícil. En este ejemplo, podría haber sido causado por el hecho de que se utilizó una metodología demasiado compleja cuando habría bastado algo mucho más simple. Entrar en un área donde intentas medir metodologías, paradigmas y mejores prácticas se vuelve cada vez más difícil con poco o ningún beneficio a largo plazo.

Ir por este camino desafortunadamente es un camino a ninguna parte. Concéntrese en descubrir problemas raíz que tengan méritos sustanciales y que no estén vinculados a sentimientos personales sobre un problema como la falta de convenciones de nomenclatura en la base del código y encuentre una manera de medir el éxito y los fracasos en torno a ese problema raíz. Esto le permitirá comenzar a armar un conjunto de bloques de construcción a partir de los cuales podrá comenzar a formular soluciones.


7

Bueno, la medida que uso, o me gusta pensar que uso, es esta:

Para cada requisito funcional independiente, único, de una línea, tómalo o déjalo, toma una foto de la base del código antes de implementarlo. Luego impleméntelo, incluida la búsqueda y reparación de cualquier error introducido en el proceso. Luego ejecute un diffentre la base de código antes y después. El diffle mostrará una lista de todas las inserciones, eliminaciones y modificaciones que implementaron el cambio. (Al igual que insertar 10 líneas consecutivas de código es un cambio). ¿Cuántos cambios hubo? Cuanto menor es ese número, por lo general, más fácil de mantener es el código.

Llamo a eso la redundancia del código fuente, porque es como la redundancia de un código de corrección de errores. La información estaba contenida en 1 fragmento, pero estaba codificada como N fragmentos, que deben hacerse todos juntos, para ser coherentes.

Creo que esta es la idea detrás de DRY, pero es un poco más general. La razón por la que es bueno que ese recuento sea bajo es que, si se necesitan N cambios para implementar un requisito típico, y como programador falible, solo obtienes N-1 o N-2 de ellos correctamente al principio, has introducido 1 o 2 errores. Además del esfuerzo de programación O (N), esos errores tienen que ser descubiertos, localizados y reparados. Por eso la pequeña N es buena.

Mantener no significa necesariamente legible para un programador que no ha aprendido cómo funciona el código. La optimización de N puede requerir hacer algunas cosas que crean una curva de aprendizaje para los programadores. Aquí hay un ejemplo. Una cosa que ayuda es si el programador trata de anticipar cambios futuros y deja instrucciones prácticas en los comentarios del programa.

Creo que cuando N se reduce lo suficiente (lo óptimo es 1), el código fuente se lee más como un lenguaje específico de dominio (DSL). El programa no "resuelve" tanto el problema como "enuncia" el problema, porque lo ideal es que cada requisito se reexprese como una sola pieza de código.

Desafortunadamente, no veo gente aprendiendo mucho a hacer esto. Más bien parecen pensar que los sustantivos mentales deberían convertirse en clases, y los verbos en métodos, y todo lo que tienen que hacer es girar la manivela. Eso da como resultado un código con N de 30 o más, en mi experiencia.


¿No es esta una suposición muy importante: que todos los requisitos funcionales son aproximadamente del mismo tamaño? ¿Y no sería esta métrica desalentar la separación de responsabilidades? Necesito implementar una característica horizontal; por lo tanto, el código más "mantenible" es una reescritura casi total de un programa que está completamente contenido dentro de un método monolítico.
Aaronaught

@Aaronaught: No sé cuán grandioso es, pero en nuestro grupo trabajamos con listas de requisitos / características, algunas interdependientes, otras no. Cada uno tiene una descripción relativamente corta. Si se necesita una reescritura importante, seguro que los he visto / hecho, pero me dice que probablemente haya una mejor manera de organizar el código. Este es mi ejemplo canónico. No digo que sea fácil de aprender, pero una vez que se aprende ahorra una gran cantidad de esfuerzo medible y se realizan cambios rápidamente sin errores.
Mike Dunlavey

5

La mantenibilidad no es tan medible realmente. Es una visión subjetiva de un individuo basada en sus experiencias y preferencias.

Para obtener una pieza de código, idee un diseño perfecto .

Luego, para cualquier desviación del código real de ese perfecto, disminuya el valor de 100 en algún número. Por lo que depende exactamente de las consecuencias de un enfoque no perfecto elegido.

Un ejemplo:

Un fragmento de código lee e importa algún formato de datos y puede mostrar un mensaje de error si algo está mal.

Una solución perfecta (100) tendría mensajes de error guardados en un lugar común. Si su solución los tiene codificados como constantes de cadena directamente en el código, tome, digamos 15 de descuento. Entonces su índice de mantenibilidad se convierte en 85.


4

Un resultado del código que es difícil de mantener es que le llevará más tiempo (en promedio) corregir los errores. Entonces, a primera vista, una métrica parece ser el tiempo necesario para corregir un error desde que se asigna (es decir, se inicia la reparación) hasta que está "listo para la prueba".

Ahora, esto solo funcionará después de que haya solucionado un número razonable de errores para obtener el tiempo "promedio" (lo que sea que eso signifique). No puede usar la figura para ningún error en particular, ya que lo difícil que es rastrearlo no solo depende de la "mantenibilidad" del código.

Por supuesto, a medida que corrige más errores, el código se vuelve "más fácil" de mantener a medida que lo mejora (o al menos debería estarlo) y se está familiarizando con el código. Contrarrestar ese hecho es que los errores tenderán a ser más oscuros y, por lo tanto, aún más difíciles de rastrear.

Esto también tiene el problema de que si las personas tienden a apresurarse a corregir errores para obtener un puntaje más bajo, ya sea causando nuevos errores o no arreglando adecuadamente el existente, lo que lleva a más trabajo y posiblemente incluso a un código peor.


2

Considero que las métricas de código de Visual Studio son bastante decentes para proporcionar una métrica de "mantenibilidad" rápida. Se capturan 5 métricas principales:

  • Complejidad Ciclomática
  • Profundidad de herencia
  • Clase Couling
  • Líneas de código (por método, por clase, por proyecto, lo que sea, según su nivel de acumulación)

El índice de mantenibilidad es el que me parece útil. Es un índice compuesto, basado en:

  1. Tamaño total (líneas de código)
  2. # de clases o archivos
  3. # de métodos
  4. Complejidad ciclomática superior a 20 (o 10 - configurable, 10 es mi preferencia)
  5. Duplicación

De vez en cuando voy a mirar mis métodos con un índice de mantenimiento bajo (bajo = malo para este). Casi sin falta, los métodos en mi proyecto con el índice de mantenimiento más bajo son los que más necesitan una reescritura y los más difíciles de leer (o mantener).

Consulte el documento técnico para obtener más información sobre los cálculos.


1

Dos que serán significativos son la complejidad ciclomática y el acoplamiento de clases. No puede eliminar la complejidad, todo lo que puede hacer es dividirla en piezas manejables. Estas 2 medidas deberían darle una idea de dónde se puede encontrar el código difícil de mantener, o al menos dónde buscar más.

La complejidad ciclomática es una medida de cuántas rutas hay en el código. Cada ruta debe ser probada (pero probablemente no). Algo con una complejidad superior a aproximadamente 20 debería dividirse en módulos más pequeños. Un módulo con una complejidad cycomatic de 20 (uno podría duplicar esto con 20 if then elsebloques sucesivos ) tendrá un límite superior de 2 ^ 20 rutas para probar.

El acoplamiento de clases es una medida de cuán estrechamente vinculadas están las clases. Un ejemplo de algún código incorrecto con el que trabajé en mi empleador anterior incluye un componente de "capa de datos" con aproximadamente 30 elementos en el constructor. La persona en su mayoría "responsable" de ese componente seguía agregando parámetros empresariales y de capa de interfaz de usuario a las llamadas abiertas / del constructor hasta que se convirtió en una gran bola de barro. Si la memoria me sirve correctamente, hubo aproximadamente 15 llamadas nuevas / abiertas diferentes (algunas ya no se usan), todas con conjuntos de parámetros ligeramente diferentes. Instituimos revisiones de códigos con el único propósito de evitar que haga más cosas como esta, y para evitar que parezca que lo estamos señalando, revisamos el código de todos en el equipo, por lo que perdimos aproximadamente medio día durante 4-6 personas todos los días porque no lo hicimos


2
Tener revisiones de código para todos no es algo malo, sinceramente. Puede sentir que está perdiendo el tiempo, pero a menos que todos lo usen como una excusa para relajarse, debería obtener información valiosa de ellos.
Anon

1

En resumen, la capacidad de mantenimiento solo se puede medir después de lo requerido, no antes . Es decir, solo puede decir, si un fragmento de código es mantenible, cuando tiene que mantenerlo.

Es relativamente obvio medir lo fácil que fue adaptar un fragmento de código a los requisitos cambiantes. Es casi imposible medir con anticipación cómo responderá a los cambios en los requisitos. Esto significaría que debe predecir cambios en los requisitos. Y si puede hacer eso, debería obtener un precio nobel;)

Lo único que puede hacer es acordar con su equipo, sobre un conjunto de reglas concretas (como principios SÓLIDOS), que todos creen que generalmente aumentan la capacidad de mantenimiento.
Si los principios se eligen bien (creo que comenzar con SOLID sería una buena opción para comenzar), puede demostrar claramente que se están violando y responsabilizar a los autores por eso.
Tendrá dificultades para tratar de promover una medida absoluta de mantenibilidad, mientras convence gradualmente a su equipo para que se adhiera a un conjunto acordado de principios realistas.


1

enormes cantidades de deuda técnica que realmente nunca se abordan

¿Qué pasa con la deuda técnica que es "superada por los eventos"?

Escribo un código horrible y lo apresuro a la producción.

Usted observa, correctamente, que no es mantenible.

Sin embargo, ese código es la última ronda de características para una línea de productos que se dará de baja porque el contexto legal ha cambiado y la línea de productos no tiene futuro.

La "deuda técnica" se elimina mediante un cambio legislativo que la hace obsoleta.

La métrica de "mantenibilidad" pasó de "mala" a "irrelevante" debido a consideraciones externas.

¿Cómo se puede medir eso?


"En cien años estaremos todos muertos y nada de esto importará. Un poco pone las cosas en perspectiva, ¿no?" Si hay algo irrelevante, es esta respuesta la que no es una respuesta a la pregunta.
Martin Maat

0

La siguiente mejor opción para las revisiones de código de pares es crear una arquitectura funcional antes de codificar una unidad o producto. El refactor rojo-verde es una forma bastante ordenada de hacerlo. Pídale a un padre que prepare una interfaz viable y divida el trabajo. Todos pueden tomar su pieza del rompecabezas y rojo-verde para llegar a la victoria. Después de esto, una revisión y refactorización del código de pares estaría en orden. Esto funcionó bastante bien en un producto importante anterior en el que trabajé.


0

Cuestionario

¿Qué hay de hacer un cuestionario anónimo para los desarrolladores, para completar una vez al mes más o menos? Las preguntas serían algo como:

  • ¿Cuánto tiempo dedicó el último mes al proyecto X (aproximadamente) [0% ... 100%]
  • ¿Cómo calificaría el estado de la base del código en términos de mantenibilidad [realmente pobre, pobre, neutral, bueno, bueno, realmente bueno].
  • ¿Cuán complejo calificaría la base del código en comparación con la complejidad del proyecto [demasiado complejo, correcto, demasiado simplificado].
  • ¿Con qué frecuencia te sentiste obstruido para resolver tus tareas debido a la excesiva complejidad de la base de código? [en absoluto, de vez en cuando, a menudo, constantemente].

(No dude en agregar preguntas adicionales que considere útiles para medir la mantenibilidad en los comentarios y las agregaré).


0

Puedo pensar en dos formas de ver la mantenibilidad (estoy seguro de que hay más esperanza de que otros puedan llegar a buenas definiciones).

Modificación sin comprensión.

¿Puede un corrector de errores entrar en el código y solucionar un problema sin necesidad de comprender cómo funciona todo el sistema?

Esto se puede lograr proporcionando pruebas unitarias completas (pruebas de regresión). Debería poder comprobar que cualquier cambio en el sistema no cambia la forma en que el sistema se comporta con una buena entrada específica.

En esta situación, un solucionador de errores debería poder entrar y corregir un error (simple) con un conocimiento mínimo del sistema. Si la solución funciona, ninguna de las pruebas de regresión debería fallar. Si alguna prueba de regresión falla, entonces necesita pasar a la etapa 2.

maintainabilty1 = K1 . (Code Coverage)/(Coupling of Code) * (Complexity of API)

Modificación con comprensión.

Si una corrección de error no es trivial y necesita comprender el sistema. Entonces, ¿cómo es la documentación del sistema? Somos no hablando documentación de la API externa (que son relativamente inútiles). Lo que necesitamos entender es cómo funciona el sistema donde se utilizan trucos inteligentes (hacks de lectura) en las implementaciones, etc.

Pero la documentación no es suficiente, el código debe ser claro y comprensible. Para medir la comprensibilidad de un código, podemos usar un pequeño truco. Una vez que el desarrollador haya terminado de codificar, dele un mes para trabajar en otra cosa. Luego pídales que regresen y documenten el sistema hasta el punto de que un muelle ahora pueda entender el sistema. Si el código es relativamente fácil de entender, entonces debería ser rápido. Si está mal escrito, tomarán más tiempo resolver lo que construyeron y escribir la documentación.

Entonces tal vez podríamos llegar a alguna medida de esto:

maintainability2 = K2 . (Size of doc)/(Time to write doc)

0

A menudo encuentro que la solución de "equivalente más corto" tiende a ser más fácil de mantener.

Aquí, el más corto significa la menor cantidad de operaciones (no líneas). Y equivalente significa que la solución más corta no debería tener una complejidad de tiempo o espacio peor que la solución anterior.

Esto significa que todos los patrones repetitivos lógicamente similares deben extraerse a la abstracción apropiada: ¿Bloques de código similares? Extraerlo para que funcione. ¿Variables que parecen ocurrir juntas? Extraerlos en una estructura / clase. ¿Clases cuyos miembros difieren solo por tipo? Necesitas un genérico. ¿Parece volver a calcular lo mismo en muchos lugares? Calcule al principio y almacene el valor en una variable. Hacer esto dará como resultado un código más corto. Ese es el principio DRY básicamente.

También podemos acordar que las abstracciones no utilizadas deben eliminarse: las clases, las funciones que ya no son necesarias son código muerto, por lo que deben eliminarse. El control de versiones recordará si alguna vez necesitamos restablecerlo.

Lo que a menudo se debate son abstracciones a las que se hace referencia solo una vez: funciones sin devolución de llamada que se invocan solo una vez sin razón alguna para que se las llame más de una vez. Un genérico que se instancia usando solo un tipo, y no hay ninguna razón por la que se instanciará con otro tipo. Interfaces que se implementan solo una vez y no hay una razón real para que alguna otra clase lo implemente, etc. Mi opinión de que estas cosas son innecesarias y deben eliminarse, ese es básicamente el principio de YAGNI.

Por lo tanto, debería haber una herramienta que pueda detectar la repetición del código, pero creo que ese problema es similar a encontrar la compresión óptima, que es el problema de complejidad de Kolmogorov que es indecidible. Pero en el otro extremo, las abstracciones no utilizadas y poco utilizadas son fáciles de detectar en función de la cantidad de referencias: una verificación para eso puede automatizarse.


0

Todo es subjetivo y cualquier medida basada en el código en sí es irrelevante. Al final, todo se reduce a su capacidad para satisfacer las demandas. ¿Todavía puede entregar las funciones que se solicitan y, si puede, con qué frecuencia volverán a recibir esos cambios porque algo aún no está bien y qué tan graves son esos problemas?

Acabo de (re) definir la mantenibilidad pero aún es subjetiva. Por otro lado, eso puede no importar tanto. Solo necesitamos satisfacer a nuestro cliente y disfrutarlo, eso es a lo que apuntamos.

Aparentemente, siente que tiene que demostrarle a su jefe o compañeros de trabajo que se necesita hacer algo para mejorar el estado de la base del código. Diría que debería ser suficiente para usted decir que está frustrado por el hecho de que por cada pequeña cosa que tiene que cambiar o agregar, tiene que solucionar o solucionar otros 10 problemas que podrían haberse evitado. Luego nombra un área notoria y crea un caso para darle la vuelta. Si eso no genera ningún apoyo en su equipo, puede que esté mejor en otro lugar. Si a las personas que te rodean no les importa, probar tu punto no va a cambiar de opinión de todos modos.

Al usar nuestro sitio, usted reconoce que ha leído y comprende nuestra Política de Cookies y Política de Privacidad.
Licensed under cc by-sa 3.0 with attribution required.