archivo

Archivo de la etiqueta: pruebas unitarias

La metodología de programación extrema fue introducida por el americano Kent Beck (autor también de la metodología de desarrollo guiado por las pruebas (TDD: Test-driven Development) y coautor de JUnit) en el año 1999, aunque empezó a trabajar en la misma años antes, concretamente desde el año 1996 cuando fue contratado por Chrysler Corporation, para intentar sacar adelante una aplicación para nóminas que hasta entonces tenía importantes problemas en el desarrollo. Cuando empezó a trabajar en el proyecto, detectó una serie de aspectos en el proceso de desarrollo que debían cambiar, aplicando para ello una serie de estrategias, muchas de ellas basadas en buenas prácticas ya contempladas y utilizadas.

La programación extrema se sitúa en una posición diferente a los ciclos de vida tradicionales. Ya no se trata de requisitos que se cierran en etapas tempranas del desarrollo y que constituyen el contrato de lo que posteriormente se va a desarrollar (y que los usuarios no ven hasta mucho después) sino de requisitos que están vivos y que son modificables a lo largo del ciclo de vida.

La programación extrema se centra en cinco principios:

– Simplicidad: Conforme en el proceso de construcción un sistema va creciendo paulatinamente (aplicable también a mantenimientos sobre sistemas ya construidos), la complejidad del código va creciendo de manera proporcional (o incluso exponencial en función del tamaño del proyecto y del número de personas que participen en el proceso).

Mediante la refactorización, se intenta mantener el código en unas condiciones donde la complejidad esté controlada y facilite la escalabilidad del sistema. Esto requiere disciplina y creer que esta metodología produce resultados ya que resulta complicado tomar la decisión de tocar código que ya funciona (esto va en contra de una de las máximas de la programación que consiste en que no toques algo que funciona, aunque también es cierto que la misma viene derivada de las malas experiencias provocadas por la crisis del software).

La integración continua resulta esencial en esta metodología, ya que ofrece la oportunidad de probar con una cierta regularidad la integración de los componentes, de manera que este tipo de problemas se puede detectar lo antes posible.

El control de la calidad del código se complementa con otras estrategias como por ejemplo el intento de que el mismo sea lo más autocomentado posible (sin prescindir de comentarios cuando sean necesarios). ¿Por qué la metodología prioriza la autodocumentación? Como el proyecto es algo que está vivo, el código se modifica con cierta frecuencia (sobre todo en el proceso de construcción), ¿por qué preocuparse en comentar un código que tal vez tenga que modificar muy pronto ya sea por una modificación funcional, por una corrección o por una refactorización? A esto hay que añadirle el peligro de que empiecen a quedarse atrás comentarios que no se han eliminado y que no se correspondan con la realidad del código. Si el código está autodocumentado evoluciona de la misma manera que lo hace el proyecto.

Para esta metodología lo más importante del proceso de desarrollo de software es el código, la base para esto tiene un razonamiento simple, sin código no hay aplicación. Se considera que una aplicación que funciona según las necesidades del cliente tiene mucho más peso que el hecho de que esté documentada al detalle.

Para sostener esta afirmación en sistemas de un cierto tamaño es fundamental un código de mucha calidad. De no ser así, el cambio de un equipo de desarrollo por otro puede provocar un riesgo importante para conseguir una continuidad en el mantenimiento del proyecto ya que se estará caminando por un terreno lleno de trampas y con poca luminosidad.

– Comunicación: Fluida y continua. Para ello se programa por parejas (pair-programming, un ordenador, dos personas) y los usuarios designados por el cliente forman parte del equipo de desarrollo (se eliminan fronteras). Pero la comunicación va más allá de eso, un código comentado (autodocumentado) y con pruebas unitarias definidas expresa información para la comprensión del mismo, es como una caja del tiempo o un mensaje en una botella y que será muy útil cuando se tengan que realizar modificaciones.

Algunos de los críticos de esta metodología comentan que la tendencia de los programadores a considerar el código desarrollado como propio dificulta el proceso de comunicación ya que cada cual considera que sus opiniones y decisiones son y han sido las mejores, que su código es difícilmente superable (o por lo menos que era complicado hacerlo mejor en el tiempo que se ha dispuesto para desarrollarlo) y además no se suelen aceptar de buen grado las correcciones (sobre todo si están relacionadas con la calidad).

Para resolver ese inconveniente la programación extrema considera que el código es colectivo, no hay parcelas, de manera que cualquier integrante del equipo de proyecto puede realizar contribuciones a cualquier parte del mismo.

Las pruebas unitarias resultan esenciales, ya que permiten automatizar la revisión del funcionamiento de cada módulo y así tener la posibilidad de realizar este tipo de tareas con frecuencia. Hay que tener en cuenta también que el código está en continua evolución por lo que las pruebas de regresión cobran importancia. El objetivo es detectar posibles errores lo antes posible. La detección tardía de errores es más costosa y su corrección además tiende a ensuciar el código (cuando el tiempo aprieta lo importante es apagar el fuego).

Aunque no es obligatorio, se recomienda incluso que las pruebas unitarias de cada componente estén construidas antes de desarrollarlo.

Otro aspecto característico de la metodología son las reuniones diarias, que se realizan de pie y en círculo (stand-up meetings) y que tienen una duración aproximada de quince minutos. En las mismas se habla sobre el estado actual del proyecto y sobre contingencias, problemáticas o dudas que se produzcan en el mismo.

Para favorecer la comunicación se aconseja que todo el equipo de trabajo se encuentre en la misma ubicación física y en entornos despejados.

– Retroalimentación: La participación y proximidad de los usuarios permite resolver dudas casi de forma instantánea, pueden ver funcionalidades concretas construidas parcialmente y opinar sobre ellas. Se comparten opiniones sobre cuál puede ser la mejor solución (es importante lo funcional pero hay que equilibrar lo que se quiere con la complejidad de realizarlo, como dijo Voltaire, lo perfecto es enemigo de lo bueno)

El desarrollo iterativo incremental permite que en cada evolución el usuario pueda expresar su opinión sobre los resultados obtenidos y si es necesario corregir algo, planificarlo para la siguiente o próximas iteraciones.

– Coraje: Optar por esta metodología no es sencillo por muchos factores, por muchos interrogantes. ¿Es sencillo admitir que se programe por parejas?, es decir, ¿puede todo el mundo entender que en cada momento uno toque el teclado y el otro mire?, ¿se puede comprender que esta estrategia no tiene por qué afectar a la productividad?.

La programación en parejas tiene una serie de ventajas:

Las decisiones en la codificación no son algo unilateral de la persona que programa el componente (los arquitectos o analistas orgánicos no pueden llegar a todo el detalle de la programación, ya que ellos determinan el armazón de la aplicación que librerías utilizar, su división en capas, etc…, es decir, establecen el marco general).

El código es revisado en tiempo real, disminuyendo la probabilidad de error e incrementando la calidad del código.

Pero no es solo lo anterior, ¿puede ser admisible que se retoque software que ya funciona con el objeto de mejorar su calidad (simplicidad)?, ¿se puede vivir en un proyecto donde los requisitos están siempre en el aire?, ¿es sostenible un proyecto orientado a entregas continuas cada poco tiempo donde el cumplimiento de los plazos resulta de gran importancia?.

Sobre esto último algunos de los críticos con la metodología consideran que el equipo de trabajo está vendido a las entregas y que al haber muchas y frecuentes, los errores en la planificación provocarán numerosos picos de trabajo con el consiguiente overtime para los distintos integrantes del equipo de proyecto.

Sin embargo los defensores sostienen que el mismo Kent Beck considera que es necesario evitar el cansancio de los componentes del equipo de proyecto, debiéndose respetar su jornada laboral, situándose esta en cuarenta horas semanales. De hecho considera que no se debe haber overtime dos semanas consecutivas. Aplica por tanto el principio de responsabilidad en el sentido de que hay que tener disciplina con las entregas pero que eso no puede estar por encima de un continuo sobreesfuerzo de los integrantes del equipo de trabajo. ¿Por qué defiende esto? Pues porque los programadores cansados escriben código de peor calidad y además son menos productivos.

– Respeto: Algo complicado de encontrar en los proyectos de desarrollo de software y en un mundo tan ombliguista como es este. Se basa en el hecho de que todos los participantes en el equipo de proyecto suman y que es importante que se tenga en cuenta que para que todo vaya bien, todos tienen que dar lo mejor de sí y para que esto se pueda producir hay que crear un ambiente que lo propicie. Las críticas deben ser constructivas, siempre para mejorar, siempre para ir hacia adelante. Es importante que se tenga en cuenta que para que el barco vaya hacia adelante todos tienen que remar y que si se hunde se hunden todos.

Por otro lado cada participante debe asumir su responsabilidad, cada cual tiene su rol y debe entender que tras él hay una serie de tareas que se tienen que realizar, gustén más o gusten menos.

En el siguiente artículo sobre este tema se tratará sobre el ciclo de vida/metodología de la programación extrema.

El puglin de Sonar Quality Index proporciona otra métrica agregada (resultado de otras métricas) para obtener un valor que indique la calidad del software. En este caso lo que se pretende es obtener un valor entre 0 y 10.

Particularmente me gusta más SIG MM ya que se centra en la mantenibilidad que es lo que realmente me preocupa sobre el software que nos entregan, pero todo es cuestión de gustos y de lo que cada uno valore lo que te calcula la métrica y lo claro que te exprese los resultados. Desde mi punto de vista este indicador da demasiado peso a la verificación de reglas PMD y pone casi al mismo nivel la verificación de estilos que la complejidad ciclomática y no otorga ningún valor al acoplamiento o a la cohesión (algo de lo que también adolece SIG MM).

Para su cálculo se basa en la obtención previamente de cuatro métricas:

– Violaciones de código: basada en los resultados obtenidos mediante la aplicación de las reglas PMD y se calcula a partir de la siguiente fórmula:

Coding Violations = (Blocker * 10 + Critical * 5 + Major * 3 + Minor + Info) / validLines

donde validLines es el número total de líneas de código de la aplicación menos el código duplicado. Como puede darse el caso de que la métrica código duplicado supere al número de líneas de código cuando ocurre esto el valor de validLines es 1. Como casi todas las métricas de Quality Index se hacen en relación al número de líneas válidas la influencia de la duplicidad de código en los resultados finales es importante (como también lo es en el cálculo de otras métricas como Technical Debt o SIG MM, algo que me parece totalmente razonable. Esto va a provocar en consecuencia que soluciones basadas en generadores de código, salvo que éstos estén muy depurados van a verse muy perjudicadas por esta métrica.

– Complejidad: basada en los resultados obtenidos mediante la obtención de la complejidad ciclomática de los diferentes métodos y se calcula a partir de la siguiente fórmula:

Complexity = (Complexity>30 *10 + Complexity>20 * 5 + Complexity>10 * 3 + Complexity>1) / validLines

Como se puede apreciar se le da un peso importante a métodos con una complejidad ciclomática alta que son aquellos que al presentar un mayor número posible de caminos lógicos hace que sean más complicados de mantener

– Cobertura: Se obtiene directamente a partir de la cobertura de código por pruebas unitarias (mirando un comentario del código del plugin (clase CoverageDecorator.java) no tiene en cuenta para el cálculo de esta métrica las líneas duplicadas.

– Violaciones de estilo: basada en los resultados obtenidos mediante la aplicación de las reglas PMD y se calcula a partir de la siguiente fórmula:

Style = (Errors*10 + Warnings) / ValidLines * 10

Una vez calculadas las métricas ya se puede tener el valor de Quality Index:

QI = 10 – (4.5 * Coding – 2 * Complexity – 2 * Coverage -1.5 * Style)

El plugin además de calcularte el valor global, te indica el valor de las 4 métricas (ejes) llevados a la escala correspondiente en función de su peso en la fórmula del cálculo de la métrica, representando en rojo la proporción que incumple y en verde la proporción que cumple.

Sonar permite calcular muchas métricas de carácter individual, como por ejemplo Cobertura, Duplicated Lines, Complejidad Ciclomática, RFC, LCOM4, etc…, pero también dispone de métricas que ofrecen resultados en función de poner en relación una serie de métricas diferentes.

Un ejemplo de ello lo tenemos en Technical Debt (base como he comentado en otros artículos de lo que es la filosofía y fin último de Sonar, tal y como dice su lema «Put your technical debt under control) y otro lo tenemos por ejemplo en las métricas proporcionada por el plugin de Sonar denominado SIG Maintainability Model.

Como indica la página que el plugin tiene en Sonar, se trata de una implementación del Modelo de Mantenimiento del Software Improvement Group (SIG).

¿Qué es el Software Improvement Group? Es una firma de consultoría holandesa con sedes en otros países europeos, que según especifica en su página web se basa en dar una serie de resultados, basados en hechos (métricas objetivas) y no en opiniones, proporcionando evaluciones de riesgos imparciales, objetivas,verificables y cuantitativas relacionados con los sistemas de información corporativos que se quieran estudiar.

Uno de los servicios que proporciona la empresa SIG es la certificación de la mantenibilidad de los productos software según el estándar internacional ISO/IEC 9126. Dicho estándar clasifica la calidad del software en una serie de aspectos, uno de los cuales es la mantenibilidad basándose el mismo en la obtención de las siguientes métricas: Estabilidad, Facilidad de análisis, Facilidad de cambios y Facilidad de Pruebas.

Por tanto el plugin de Sonar SIG Maintainability Model, permite calcular dichas métricas, las cuales resultan significativas ya que determinan el grado de mantenibilidad de un software basado en un estándar internacional.

Una vez que hemos podido apreciar lo relevante que resulta este plugin, toca analizar como calcular cada una de sus métricas.

El cálculo se basa en la obtención en primer lugar de una serie de métricas más simples (ya que las métricas finales indicadas anteriormente son el resultado de la combinación de las mismas) y clasificando los resultados de dichas métricas en cinco posibles escalas (las cuales voy a ordenar de menor a mejores resultados desde el punto de vista de la mantenibilidad):

Volumen: Se obtiene a partir del número de líneas de código de la aplicación.

Escalas:

–: > 1310000 líneas de código.
-: > 655000 líneas de código.
0: > 246000 líneas de código.
+: > 66000 líneas de código.
++: > 0 líneas de código.

Duplicidades: Se obtiene a partir del porcentaje de líneas duplicadas en la aplicación:

Escalas:

–: > 20% líneas duplicadas
-: > 10% líneas duplicadas
0: > 5% líneas duplicadas
+: > 3% líneas duplicadas
++: > 0% líneas duplicadas

Pruebas unitarias: Se obtiene a partir del grado de cobertura del código a través de pruebas unitarias.

Escalas:

–: > 95% cobertura
-: > 80% cobertura
0: > 60% cobertura
+: > 20% cobertura
++: > 0% cobertura

Complejidad: Se obtiene a partir de la complejidad ciclomática de los métodos.

La forma de cálculo es un poco más compleja ya que se realiza en dos pasos:

1) Clasificar cada método en uno de los siguientes rangos en función de su complejidad ciclomática:

Muy alta: > 50
Alta: > 20
Media: > 10
Baja: > 0

2) A continuación la escala se obtiene a partir del porcentaje de métodos que se encuentran en los rangos definidos en el paso 1):

Escalas:

–: Resto de casos
-: C.C. Media <50%; C.C. Alta <15%; C.C. Muy Alta <5%
0: C.C. Media <40%; C.C. Alta <10%; C.C. Muy Alta <0%
+: C.C. Media <30%; C.C. Alta <5%; C.C. Muy Alta <0%
++: C.C. Media <25%; C.C. Alta <0%; C.C. Muy Alta <0%

Tamaño unitario: Se obtiene a partir del número de líneas de código de los métodos.

La forma de cálculo es un poco más compleja ya que se realiza en dos pasos:

1) Clasificar cada método en uno de los siguientes rangos en función de su número de líneas de código (LOC):

Muy alta: > 100
Alta: > 50
Media: > 10
Baja: > 0

2) A continuación la escala se obtiene a partir del porcentaje de métodos que se encuentran en los rangos definidos en el paso 1):

Escalas:

–: Resto de casos
-: LOC Media <50%; LOC Alta <15%; LOC Muy Alta <5%
0: LOC Media <40%; LOC Alta <10%; LOC Muy Alta <0%
+: LOC Media <30%; LOC Alta <5%; LOC Muy Alta <0%
++: LOC Media <25%; LOC Alta <0%; LOC Muy Alta <0%

Una vez calculadas las métricas simples, se calculan las métricas complejas que son las que definen la métrica de mantenibilidad de la ISO/IEC 9126, realizando una simple media de las escalas obtenidas en las métricas que definen cada métrica compleja:

Analizabilidad (Analysability) (A): [VOLUMEN], [DUPLICIDADES], [PRUEBAS UNITARIAS], [TAMAÑO UNITARIO].
Modificabilidad (Changeability) (C): [DUPLICIDADES], [COMPLEJIDAD]
Estabilidad (Stability) (S): [PRUEBAS UNITARIAS]
Testeabilidad (Testability) (T): [PRUEBAS UNITARIAS], [COMPLEJIDAD], [TAMAÑO UNITARIO].

De esta manera los valores de A, C, S y T oscilará cada uno entre — (valor más malo) y ++ (valor más bueno).

Estos cuatro valores se representarán en un gráfico donde cada eje representa el valor de estas métricas, teniendo cada eje tres escalas que estarán más separadas de la posición (0,0) que se corresponde con el valor (–) conforme más buena sea la métrica. En sentido horario, los ejes representan respectivamente a T, A, C y S.

Además de esa representación, el color del gráfico también es identificativo de la mantenibilidad de la aplicación, ya que tendrá un color entre el rojo (todas las métricas –) y verde (todas las métrica ++).

Que una aplicación tenga una cobertura de pruebas unitarias alta (y se haya verificado el resultado de las mismas) supone una garantía para la reducción del número de errores de la aplicación tanto en la fase de pruebas funcionales como una vez que la misma se encuentre en producción.

La implementación de pruebas unitarias tiene su coste en tiempo, por lo que salvo que la organización para la que se desarrolla tenga unos umbrales de cobertura de código que haya que cumplir de manera obligatoria, es recomendable determinar en qué clases centramos nuestras pruebas unitarias, para que con el menor esfuerzo posible consigamos cubrir aquellas que pueden resultar más conflictivas y de esta forma, tal y como se comentó en el párrafo anterior reducir el número de errores que se propagan a etapas posteriores del desarrollo.

Estrategias para colocar las pruebas unitarias puede hacer muchas. Una de ellas es la que voy a comentar en este artículo y consiste en seleccionar aquellas clases que presentan de manera combinada una mayor complejidad ciclomática (posibles caminos lógicos) y mayor RFC (acoplamiento con métodos de otras clases).

Lo de seleccionar aquellas con mayor complejidad ciclomática puede parecer lógico, pero, ¿por qué combinarlas con las que tienen un mayor RFC? Pues porque el RFC es otra medida de complejidad y mide otro tipo de aspecto complejo, ya que se centra en la relación de la clase con otras y por tanto se puede ver afectada por cambios en cualquiera de los métodos de las clases con las que se relaciona.

Me gusta Sonar y creo que a mis compañeros también. Para ser justo, hay que mencionar también a PMD, Checkstyle y FindBugs porque Sonar debe mucho a esos proyectos.

Las métricas de Sonar asustan porque son muchas y algunas son complicadas de entender tanto en su significado como en su cálculo. Entenderlas es importante, pero tratar de interpretar a corto plazo el significado de todas puede llegar a hacer que sea algo tan abrumador que haga que se pierda el interés en todo lo que te puede proporcionar el producto.

Siguiendo este punto de vista, yo entiendo que la integración de Sonar dentro de nuestros procedimientos de certificación de la calidad del software resulta muy importante, pero también que su incorporación debe ser gradual, consolidando diversas métricas y mediante diferentes iteraciones y con tiempo, definir lo que serán nuestros márgenes de tolerancia, una vez que consigamos esto, ya será mucho más fácil ir incorporando en nuestros análisis nuevas métricas, sin olvidar de la mejora continua de los umbrales que se definan, porque sólo el tiempo y nuestra experiencia nos permitirá definir unos límites acordes a tener una deuda técnica baja, sin que suponga necesariamente un sobrecoste no asumible en los desarrollos, porque los presupuestos que tenemos son los que son y las aplicaciones, sobre todos aquellas que llevan en producción más tiempo, están como están.

Por ese motivo, probablemente nos centremos inicialmente en las siguientes métricas: Cobertura del código por pruebas unitarias, duplicidad de código, complejidad ciclomática y los comentarios, ya que son las más fáciles de interpretar y tienen repercusión directa en la deuda técnica.

Nos tocará definir, como he comentado anteriormente, cuáles serán los umbrales a partir del cual aceptaremos un software entregado, al principio nos equivocaremos y seremos o muy injustos con el proveedor o demasiado blandos, pero eso no me preocupa porque realmente lo importante es consolidar unos umbrales cada vez más acordes con la realidad y que también los proveedores entiendan que para nosotros no sólo es importante que el producto funcione, sino que también que los costes de mantenimiento se vean afectados lo menos posible por esos intereses que genera la existencia de deuda técnica en nuestras aplicaciones.

Probablemente definamos para cada métrica diferentes umbrales, ya que como he comentado en otros artículos, en el nuevo enfoque que tenemos pensado darle en las revisiones de la calidad del software y de su proceso de desarrollo y mantenimiento, vamos a definir diferentes itinerarios de validación en función de las características de la aplicación, ya que no es lo mismo un mantenimiento que un nuevo desarrollo y además todas las aplicaciones no tienen el mismo nivel de criticidad, complejidad, etc…

Soy consciente de que cada itinerario que definamos complicará un poco todo, ya que serán más umbrales los que tenemos que ajustar, pero entiendo que este incremento de complejidad es necesaria, porque umbrales generales no darán buenos resultados porque son precisamente eso, demasiado generales.

Por tanto, tenemos que ser coherentes con el número de itinerarios ya que tampoco es cuestión de tener itinerarios a la carta (para sistemas especialmente críticos sí que puede tener justificación, pero serían excepciones), para de esta forma tanto el ajuste de los umbrales no se nos escape de las manos y para tener en general, en los procesos de verificación del software y del desarrollo una visión de lo que se hace (demasiados caminos o formas de hacer las cosas, hace que la idea de conjunto se difumine y se requiera estar muy encima de todo para saber realmente qué se está haciendo y supondrá un esfuerzo que realmente no creo que merezca la pena).

En paralelo debemos empezar a trabajar con la definición de las reglas, ya que si bien son de utilidad las que Sonar trae de serie, necesitamos algo más, ya que nos gustaría que se verificase hasta donde sea posible el cumplimiento de las directrices de nuestro libro blanco de desarrollo, creado en su día y actualizado desde entonces con el objetivo de dar uniformidad a todos los nuevos desarrollos y mantenimientos evolutivos importantes, para de esta manera acotar su diversidad tecnológica y de arquitectura, facilitando de esta forma futuros mantenimientos y simplificando, hasta donde es posible, las tareas de administración del software que sirve las mismas.

Para ello, tendremos que utilizar los mecanismos de extensión de PMD. Este tema está todavía bastante verde y tocará madurar, ya que será necesario realizar ese desarrollo, pero teniendo claro qué reglas queremos que se verifiquen, además, tenemos que elegir dentro del conjunto de reglas de PMD, Checkstyle y FindBugs con cuáles nos quedamos.

Una vez realizada la extensión, tocará definir los umbrales para las reglas, que también serán variables en función de los itinerarios que se definan. Como podéis ver, si todavía nos queda camino por recorrer con las primeras métricas, no quiero decir nada lo que nos queda por andar en lo que a las reglas se refiere. En cualquier caso es cuestión de ir avanzando, aunque sea lentamente, pero avanzando.

Sonar es un producto bastante interesante y que desde mi punto de vista, debería ser tenido en cuenta en los ecosistemas de verificación de la calidad del software. Cierto es que no te dice si un producto funciona o no (cierto es que están las pruebas unitarias, pero para ello éstas deben estar desarrolladas), ya que para eso están las pruebas funcionales y no el análisis estático de código, pero sí que es bastante útil para poder evaluar la calidad de desarrollo de las aplicaciones, su mantenibilidad y su grado de adecuación a las directrices de desarrollo que se marquen.

También Sonar debería ser un referente para los proveedores de servicios de desarrollo de software, ya que la calidad de lo que se entrega puede ser un aspecto diferencial respecto a la competencia.

La existencia de código duplicado en las aplicaciones es un problema más grande de lo que realmente se suele considerar.

Es importante entender que código duplicado no es exáctamente código exáctamente igual, sino que puede ser código con ligeras adaptaciones. Existen algoritmos, como el que utiliza CPD para la detección de estas duplicaciones. Es cierto que a veces puede haber falsas alarmas, pero su funcionamiento está lo suficientemente depurado como para pensar que los resultados que ofrece deben ser tenidos en cuenta, aunque sea para estudiarlos.

La existencia de código duplicado afecta a la mayoría de los ejes de los que depende la deuda técnica, ya que provocará una mayor complejidad ciclomática (ya comenté en el artículo que dediqué a esta métrica que esta es función de las líneas de código de la aplicación porque es normal que conforme crezca se incrementen el número de posibles caminos lógicos de la misma), un mayor número de pruebas unitarias (lo que afectará al % de cobertura, si no se construyen esas pruebas), una mayor cantidad de código que documentar, un posible incremento del número de reglas incumplidas, ya que todas aquellas reglas que se incumplan en un fragmento de código, se incumplirán en todos aquellas clases y métodos donde se haya duplicado el mismo, etc…

Por tanto, el código duplicado incrementará los costes de mantenimiento del software y esto es un problema.

¿Cómo se llega a la existencia de código duplicado? En primer lugar porque es prácticamente imposible conseguir un código sin duplicaciones, salvo que la cantidad de dinero que se invierta para depurar y depurar sea tan grande que al final, no merezca la pena intentar conseguir un código sin duplicados. En segundo lugar, porque en muchos casos el código duplicado se genera sin querer, generalmente porque hay diversas personas trabajando en el proyecto y existen determinadas funcionalidades comunes que cada uno termina implementándola por su cuenta (se hace más de una vez lo mismo y de la misma manera), esto resulta complicado de controlar en un proyecto con un cierto tamaño, y también porque a veces el proyecto es tan grande y tan largo que te olvidas de que ya has implementado un determinado código y lo vuelves a hacer.

Tambień pesa mucho en todo esto la experiencia y pericia del programador, ya que un técnico de estas características si decide esmerarse en ello, producirá un código con menos duplicaciones que otro que no cuente con su bagaje profesional y/o sus capacidades.

Existen otras situaciones, las típicas urgencias, que también derivan en duplicar código. Hay que resolver un problema, he visto que en esta parte de la aplicación está resuelto de tal manera, tengo poco tiempo, así que copio el código y lo adapto.

PMD a través de CPD o Sonar que a su vez puede hacer uso del mismo, permiten obtener métricas de duplicidad del código y también navegar hasta donde se encuentran esas posibles duplicidades, por lo que pueden ser herramientas de mucha utilidad para estudiar esta métrica en las aplicaciones de tu organización y para tomar medidas para mejorar los datos si se estima conveniente.

La complejidad ciclomática es una métrica a tener en cuenta para evaluar la mantenibilidad, comprensión, testeabilidad y probabilidad de errores de una determinada aplicación.

Este término fue propuesto por Thomas J. McCabe en 1976 y tiene como una de sus características principales su independencia respecto a los lenguajes de programación (por lo menos, de los más convencionales), por lo que teóricamente podrían ser comparables aplicaciones realizadas con tecnologías distintas en base a su complejidad ciclomática. Digo teóricamente, porque además de la algorítmica las características propias del lenguaje de programación pueden influir en que sean más sencillas o no su inteligibilidad, mantenibilidad, etc… No obstante y en cualquier caso, resulta una aproximación muy a tener en cuenta de esos factores.

¿Qué mide la complejidad ciclomática? Pues el número de posibles caminos lógicos que tiene una aplicación. ¿Quiénes crean esos posibles caminos lógicos? Los métodos, las sentencias selectivas (if, case), sentencias iterativas (while, for), lanzamiento y tratamiento de excepciones (throw, catch), devolución de resultados de un método cuando no es lá última línea del mismo (return), operadores lógicos en guardas, etc…

Cuanto más caminos lógicos tenga una aplicación, una clase o un método más complejo resulta y por tanto, requiere un mayor esfuerzo desde el punto de vista de las pruebas unitarias (el número de pruebas unitarias para recorrer todo el código crece linealmente con la complejidad ciclomática (además de cumplirse eso, se verifica que: 1) la complejidad ciclomática es menor o igual que el número de casos de pruebas que se requiere para hacer cobertura de todo el código de la aplicación (téngase en cuenta que los else no suman complejidad ciclomática), 2) la complejidad ciclomática es mayor o igual que el número de casos de prueba que se requieren para verificar que realmente se «abren» los distintos caminos físicos)).

Por tanto, una baja cobertura de una aplicación viene a decir que existe una gran cantidad de código que no se ha verificado (utilizando esta estrategia), que se recorra, por lo que probablemente si no se ha recurrido a pruebas intensivas de la aplicación, muy probablemente se traduzca en errores evitables que se detectan posteriormente, incluso en producción).

También se requiere un mayor esfuerzo para la mantenibilidad y comprensión del código (¿cuántas veces nos hemos perdido en un mar de sentencias if anidadas?) y por todo lo anterior se incrementan las posibilidades de que existan errores, porque ¿cuántas veces nos hemos encontrado con que un problema en un método era provocado porque no se metía por la guarda correspondiente de una condicional o porque no entraba en un determinado bucle?, por lo que simplemente la mera experiencia nos indica que debe existir una relación entre la complejidad ciclomática y el número de errores de una aplicación.

Como la complejidad ciclomática es el resultado de los diferentes caminos lógicos que puede tener una aplicación es lógico que esta crezca conforme más extensa (en líneas de código) es una aplicación y por regla general una aplicación con muchas funcionalidades, grande, etc…, es más compleja, requiere mayor codificación y tendrá como consecuencia una mayor complejidad ciclomática (de esta manera existirá prácticamente una relación entre una clasificación de las aplicaciones por líneas de código y una clasificación de las aplicaciones por complejidad ciclomática, es importante señalar que no tienen por qué conservar el mismo orden aunque sí más o menos estarán por el mismo entorno, es decir, la segunda aplicación con más líneas de código, puede ser la cuarta en complejidad ciclomática o la quinta aplicación con más código, la segunda en complejidad. Por tanto este tipo de complejidad es inherente a las aplicaciones y existe por el simple hecho de la existencia de las mismas y por tanto, aplicaciones con envergadura se traducen en aplicaciones complejas de mantener y eso es consecuencia de su complejidad ciclomática intrínseca.

Independientemente de que una codificación eficiente puede eliminar caminos lógicos innecesarios, existe un límite y es el que da la propia dinámica de funcionamiento de un sistema de información que provoca que se codifique su comportamiento en algoritmos que como se han comentado anteriormente pueden ser complejos. Cuando se aproxima a este límite, lo más que se puede hacer es utilizar la complejidad ciclomática para determinar si determinados métodos o clases son demasiado complejas y requerirían ser analizadas de cara a dividirlas en otras más simples, mejorando así la mantenibilidad de las mismas.

Existen algunas clasificaciones basadas en rangos de complejidad ciclomática que determinan cuando es recomendable hacer la simplificación de determinadas clases y métodos y también hay casos de éxito de la aplicación de los mismos en algunas compañías.

En muchas ocasiones a simple vista se puede determinar si un método o una clase tienen una complejidad ciclomática alta, no obstante, esta tarea puede ser agotadora si la aplicación a analizar es muy grande, por ese motivo, lo mejor es delegar esa función en analizadores estáticos de código, como es el caso de Sonar, que permite obtener métricas de la complejidad ciclomática tanto a nivel de aplicación, como de clases y de métodos y utilizando su implementación de funcionalidades de drill down ir de datos más generales a información más de detalle.

Como todas las métricas de ingeniería del software, no hay que tomarse los resultados de la complejidad ciclomática al pie de la letra, sino saber interpretar qué es lo que están diciendo y tomar medidas para reducir el impacto de dicha complejidad en la aplicación (mayor cobertura de pruebas unitarias) y para tomar decisiones de revisión de la codificación en métodos y clases complejos y su posible simplificación en otros más sencillos.

Desde hace unas semanas tenemos en marcha Sonar en nuestra organización para recoger métricas realizando una revisión estática del código. Estamos muy contentos con la implantación de la herramienta y realmente le estamos viendo bastantes posibilidades de cara a un futuro, no obstante es algo que tenemos que tomarnos con prudencia, ya que cada programa es un mundo y no podemos juzgar de antemano y sin un estudio en profundidad de las métricas y de las circunstancias del producto si el mismo a nivel de codificación ha resultado de calidad o no. Hay mucho que trabajar en este sentido, ya que independientemente de lo anterior, una vez analizadas sus bondades y defectos nos permitirá por un lado mejorar la mantenibilidad de los sistemas y la calidad general del producto.

Uno de las cosas que más nos llamó la atención fue que para casi todos los proyectos para los que recogíamos métricas el valor de Cobertura era 0% o lo que es lo mismo, que el 0% del código de gran parte de los sistemas de información de la casa no esta cubierto por pruebas unitarias.

Una vez descubierto esto (no es que hayamos descubierto ninguna maravilla, simplemente es algo que con el día a día no se prestaba atención, pero que sin embargo son tan obvios y llamativos los datos que nos da Sonar que no podemos dejar pasarlo por alto), vamos a meter mano en este tema, porque muchos errores que se detectan en la fase de pruebas del producto o en producción se podían haber detectado previamente con unas sencillas pruebas unitarias, de esta manera mejoraremos la eficiencia de funcionamiento de nuestra Oficina de Calidad, ya que tendrán que dedicar menos tiempo a detectar y reportar este tipo de incidencias y por supuesto reduciremos el número de errores que nos llegan a producción.

De las pruebas unitarias se habla mucho, incluidos los propios proveedores de servicios de desarrollo de software, pero después queda demostrado que del dicho al hecho hay mucha distancia.

La clave de todo está en el equilibrio, si intentamos cubrir con pruebas unitarias todo el código, lo mismo cuesta más el collar que el perro, pero no verificar nada tiene tan poco equilibrio como lo anterior. Por lo menos sería necesario que se verificasen los métodos más conflictivos, comprobando si en función de distintas posibilidades se ejecuta o no el código contenido en los mismos. Como esto dependerá mucho de la complejidad de las aplicaciones, el factor de cobertura será distinto entre ellas, pero una cosa es esa y otra cosa la nada.

Hace un tiempo tuve la oportunidad de leer en un foro el debate suscitado entre un par de personas en el que una hacía referencia a la calidad de la documentación que le generaba un determinado proveedor y otra hacía mención a la calidad del código que le generaba otro proveedor. Por lo visto ambos conocían a los proveedores y precisamente lo que destacaban de uno era lo que le faltaba al otro.

¿Cuál es mi opinión al respecto? Pues que si bien es valorable que un proveedor entregue documentación de calidad y otro entregue código de calidad, ambos tendrían mucho que aprender de un proveedor que entregue una buena documentación (aunque no sea excelente) y además un buen código (aunque tampoco sea excelente).

Entiéndase por calidad no sólo el continente, sino el contenido (sobre todo el contenido), porque se puede entregar una documentación excepcional que para nada refleje en su conjunto lo que el usuario desea y se puede entregar un código que saque roce el diez en la realización de las pruebas unitarias y en una revisión estática de código, pero que después no verifique el conjunto de requisitos funcionales.

No obstante, hay un aspecto que es importante señalar: una documentación de calidad siempre es un buen punto de partida, una mala (o nula) documentación supone siempre un riesgo muy importante para el proyecto.

Uno de los aspectos que más me preocupan en el desarrollo de proyectos informáticos es que el producto llegue al entorno de producción con el menor número de errores funcionales, que tenga un buen rendimiento y sea fácilmente mantenible a nivel de código y de documentación.

Esto que parece de perogrullo, es tremendamente difícil sobre todo si nos encontramos con proyectos de una gran envergadura con un parque de usuarios heterogéneo en localizaciones dispersas con distintos nivel de calidad en su ancho de banda.

Después de analizar las metodologías de desarrollo clásicas como Métrica v.3 y tratar de hacer uso de ella en mis proyectos (más bien un subconjunto de ella, pero aunque sea un subconjunto, por definición de Métrica v.3 sigue siendo Métrica v.3) he llegado a la conclusión de que sin ser una mala opción, hay algunos aspectos que sí es conveniente centrarse y en otros menos:

– Plan de proyecto. Contendrá el cronograma del proyecto (lo mejor es que ese cronograma se mantenga con una herramienta informática compartida: Redmine, dotProject, etc…), por lo que valdrá con una referencia a la url donde se puede seguir el cronograma del proyecto, el equipo de proyecto (igualmente lo mejor es que ese equipo de proyecto se vaya actualizando, por tanto lo mejor es una herramienta informática compartida y si es la misma que la del cronograma, mejor que mejor y se sepa si así lo desea el cliente su imputación de horas al proyecto) y la documentación que deberá acompañar al proyecto.

– Lo fundamental en un proyecto de desarrollo de software son los requisitos. La empresa que posea analistas con la suficiente capacidad para obtener del usuario lo que quiere (cosa tremendamente difícil) tienen una ventaja muy importante respecto a sus competidores. Este catálogo de requisitos es fundamental tenerlo actualizado en todas las fases del proyecto, incluido el mantenimiento. Si es necesario dedicar tiempo a una fase del proyecto concreta esta es el análisis de requisitos. El catálogo de requisitos debe ser completo y fácilmente inteligible por el usuario. Si hay presupuesto en el proyecto y puede facilitar el proceso de desarrollo el analista puede trabajar con los diagramas de casos de uso.

– Modelo de datos. Muy importante tenerlo documentado en la versión 1 del proyecto. Para posteriores versiones, si en el mantenimiento hay presupuesto y tiempo (aspectos no siempre disponibles) se puede mantener la documentación del modelo de datos, en caso contrario no pasa nada, siempre hay herramientas que te los puede obtener por ingeniería inversa a través de la base de datos.

– Interfaz de usuario. Es fundamental que el usuario conozca y valide la interfaz de usuario, ya que es donde ellos van a trabajar, de nada vale saber lo que quiere el usuario si después la interfaz de usuario no es ágil. Por este motivo también conviene invertir en esta fase del proyecto. Cuanto más se aproxime esta interfaz de usuario a su implementación real, más conciencia tendrá el usuario de lo que se va a encontrar y por tanto más posibilidades de éxito existen en el proyecto.

– Arquitectura del sistema. Debe ser breve, conciso y cuanto más gráfico mejor. Es importante para proyectos donde se deleguen funcionalidades en terceras herramientas (motor de tramitación, plataformas de autenticación y firma electrónica, etc…).

– Plan de pruebas (unitarias, integración, sistema, implantación y aceptación). Aunque es una realidad que nosotros, los clientes, tengamos nuestros propios medios para garantizar la calidad de las entregas, son absolutamente necesarias dos premisas: Que la empresa desarrolladora realice un primer filtro de pruebas muy importante (nunca es perdido el tiempo que se dedica a probar) y que se entregue un manual de pruebas al cliente donde como mínimo se pueda garantizar, tras realizar las pruebas, que el sistema funciona y que además verifica los requisitos funcionales y no funcionales más importantes.

– Manual de usuario. Debe estar siempre actualizado. Ser muy completo y tener casos de uso reales en los que se refleje, al menos, el 90% de la casuística del programa. Si el manual está accesible on-line por los usuarios mejor que mejor, independientemente de eso es aconsejable que sea también fácilmente imprimible.

A todo lo anterior es necesario sumar la necesidad de utilizar un sistema de control de versiones buenos, como por ejemplo Subversion y hacer un buen uso del mismo (política correcta de etiquetado de versiones, etc…), utilizar unos mecanismos estándar para el despliegue de los proyectos: MAVEN + Hudson + Artifactory, por ejemplo y algo fundamental, un libro blanco de desarrollo que homogeinice los desarrollos que se realizan para tu organización.