archivo

Archivo de la etiqueta: Cobertura

En el entorno de personas con las que trabajo existen serias dudas de que la metodología de desarrollo guiado por las pruebas o Test-Driven Development, sea ágil, en el sentido de que entienden que si se aplica de manera ortodoxa o si bien las pruebas unitarias se desarrollan después, se pierde capacidad de trabajo efectivo por parte del equipo de proyecto.

Nunca he participado en un proyecto que siga esta metodología y en los desarrollos en los que he trabajado la importancia que se le da a las pruebas unitarias es muy escasa, por lo que no puedo aportar mi experiencia para opinar sobre las bondades o no de esta metodología, de manera que me tendré que centrar en lo que he leído sobre ella.

No obstante, hay que tener en cuenta una cosa. Agilidad no es lo mismo que desarrollar más rápido o por lo menos no es su objetivo principal, ya que si bien es cierto que la aplicación de metodologías ágiles, eliminan muchos aspectos que son prescindibles en un proyecto de desarrollo de software y por lo tanto pueden optimizar tiempos de desarrollo, el objetivo último de las mismas es conseguir productos software de calidad, que proporcionen satisfacción al cliente, dentro de unos plazos y presupuestos definidos (siempre y cuando, ambos sean realistas y acordes a la naturaleza del proyecto y al nivel de calidad que se espera del producto software), es decir, que no se produzcan los factores que derivan en la crisis del software.

El desarrollo guiado por las pruebas, fue introducido (o reintroducido) por Kent Beck, creador de la metodología de programación extrema y en cierto modo la segunda es una consecuencia natural de la primera, si bien ambas pueden utilizarse sin problemas por separado.

La metodología TDD a grandes rasgos es fácil de exponer:

– Se construyen las pruebas unitarias (o se puede extender si se desea el concepto a otros tipos de pruebas de más alto nivel, siempre y cuando sea automatizable su ejecución).

Como estos casos de prueba están construidos antes desarrollarse el componente software sobre el que se va a trabajar, van a fallar a la primera. De hecho se considera necesario verificar que fallan porque si no es así, querría decir que el test está más construido (¿por qué no?) o que el componente software ya está desarrollado (en proyectos donde se trabaja con sistemas de mediana o gran envergadura y con un número de desarrolladores alto, puede pasar que el componente o módulo ya se haya implementado previamente, bien porque le ha hecho falta a un compañero para otra funcionalidad o bien porque no se ha verificado que efectivamente ese componente ya estaba desarrollado).

Para la construcción de los casos de prueba, los desarrolladores deben basarse en las especificaciones indicadas en los casos de uso o en las historias de usuario. Ahí radica la importancia que tiene la construcción de las pruebas unitarias antes del desarrollo del componente, ya que implica un mayor enfoque en los requisitos, una mayor necesidad de comprender la funcionalidad y comportamiento deseado, es decir, implica comprender el problema, conocer los resultados esperados, antes de empezar a desarrollarlo.

La ventaja de desarrollar las pruebas antes de implementar los componentes es la indicada en el párrafo anterior, es decir, enfrentarse y comprender el problema antes de empezar a resolverlo.

Si las pruebas unitarias se construyen después, no estaríamos hablando de TDD, eso es importante, por lo que lo repito, si las pruebas unitarias se construyen después se trata de otra estrategia de desarrollo de software, no de desarrollo guiado por las pruebas.

Pese a que como he comentado, no se ha fomentado en los proyectos en los que he participado la construcción de pruebas unitarias, soy de la opinión de que es favorable tener una buena cobertura de código, por lo que la existencia de pruebas unitarias, antes o después de implementar los componentes, permite detectar de manera rápida la existencia de efectos colaterales o desarrollos erróneos de métodos o clases.

¿Por qué no lo hemos fomentado? Es una suma de varios factores, por un lado pese a que entendemos los beneficios que puede proporcionar, no tenemos del todo claro su retorno de la inversión, pese a que es objetivo que la deuda técnica se reduce en proporción a la cobertura, por otro lado, los proveedores con los que hemos trabajado tampoco utilizan esta práctica en sus desarrollos. Por tanto y en resumen, la falta de experiencia nuestra y de los proveedores hace que nos más dar el paso y prioricemos otros aspectos en el proceso de desarrollo y en la calidad del diseño y de la codificación.

– A continuación se construye el componente software que debe verificar las reglas de funcionamiento definidas en las pruebas.

Una vez desarrollado, se pasa el test y si funciona adecuadamente, se pasa de nuevo todo el juego de pruebas, si hay errores, se vuelve a revisar el componente, si todo ha ido bien, se pasa a la siguiente fase.

– Una vez que se tiene un software que verifica las pruebas implementadas, se procede a su refactorización.

El objetivo de la utilización de esta técnica es evidente, desarrollar software con el menor número de fallos posible, reduciendo además los errores debidos a efectos colaterales en el desarrollo, respetando también la calidad de diseño y de codificación para favorecer la comprensión y capacidad de mantenimiento del software.

Se sabe que cuanto más tarde se detecten los errores más esfuerzo se requiere para solucionarlo, esta técnica pretende disminuir el riesgo por la presencia de errores de ese tipo, además de facilitar un código donde sea mucho más fácil trabajar, tanto para corregir esas incidencias como para contruir nuevas funcionalidades o modificar, si fuera necesario (el software es de naturaleza adaptativa), las ya implementadas.

Espero poder probar más pronto que tarde esta metodología en alguno de los proyectos en los que participo y poder verificar personalmente las ventajas que tiene la utilización de esta estrategia de desarrollo.

Las técnicas de testing de caja blanca se realiza cuando el tester accede al código fuente de la aplicación y en consecuencia a los diferentes algoritmos y estructuras de datos utilizadas. La implementación de este tipo de pruebas requiere habilidades de programación, un conocimiento del framework de desarrollo y un cierto conocimiento funcional que permita conocer qué misión tienen determinadas clases y métodos.

Entre las técnicas de testing de caja blanca más conocidas tenemos la cobertura que consiste básicamente en la verificación de que todos los caminos lógicos de la aplicación son alcanzables teóricamente en función de los diferentes valores de entradas de los parámetros. Este tipo de pruebas, se automatizan con la ejecución de pruebas unitarias.

Otra técnica bastante conocida es la Mutation Testing que se suele utilizar para verificar la bondad de los métodos de testing utilizados. Se basa principalmente en realizar ligeras modificaciones en el programa que darían lugar a un comportamiento anómalo del mismo (resultados distintos) y verificar si la estrategia de testing utilizada es capaz de detectar estos cambios. Ejemplos de estos pequeños cambios lo podemos tener modificando el operador en las guardas de sentencias selectivas o iterativas, eliminando sentencias, etc…

Entre las críticas a esta estrategia de testing se encuentra el hecho de que hay que saber elegir muy bien cómo realizar las mutaciones, ya que hay que verificar que realmente lo que se cambia provoca un comportamiento diferente en el algoritmo porque lo mismo haces un cambio y no cambia de comportamiento.

Otra técnica de caja blanca la tenemos con la Fault Injection que persigue objetivos similares a la Mutation Testing (de hecho se considera a esta técnica como parte de la Fault Injection). Entre este tipo de técnicas tenemos la Code Insertion Testing, en la cual se introducen sentencias nuevas que provocan un error o un comportamiento anómalo en el sistema. Además de estas tećnicas, que se aplican en tiempo de compilación, la Fault Injection también aplica estrategias en tiempo de ejecución, como el hecho de provocar determinados fallos o excepciones del sistema que permitan estudiar el comportamiento del sistema en esos casos.

Una de las técnicas de testing de caja blanca más conocida es el análisis estático de código que tiene como objetivo principal evaluar (directa o indirectamente) la deuda técnica del software o lo que es lo mismo, evaluar el grado de mantenibilidad del sistema.

La mantenibilidad del sistema es una característica a la que no se suele dedicar suficiente atención, al fin y al cabo lo importante es que el sistema funcione (algo de lo que estoy totalmente de acuerdo), pero que una vez que se consigue debe ser compatible con el hecho de que el sistema sea escalable y pueda ser modificado a un coste razonable.

Además, por regla general, un sistema con una deuda técnica elevada será más propenso a tener errores.

Ver también:

Testing de caja negra.
Testing de caja gris.

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.