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.
fuente
Respuestas:
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.
fuente
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
diff
entre la base de código antes y después. Eldiff
le 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.
fuente
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.
fuente
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.
fuente
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:
El índice de mantenibilidad es el que me parece útil. Es un índice compuesto, basado en:
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.
fuente
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 else
bloques 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
fuente
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.
fuente
¿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?
fuente
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é.
fuente
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:
(No dude en agregar preguntas adicionales que considere útiles para medir la mantenibilidad en los comentarios y las agregaré).
fuente
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.
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:
fuente
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.
fuente
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.
fuente