En busca de código repetido
Uno de los problemas que más me quitan el sueño en los últimos tiempos, es el código repetido.
¿Por qué tener código repetido es un problema? La respuesta creo que es conocida por todos. El problema es que eventualmente las distintas versiones que se tienen se terminan desfazando, y empiezan a aparecer errores por tocar solo una. Además después de un cierto tiempo, es muy difícil saber cual de todas las versiones es la correcta, porque en general los cambios no se hacen en todas parejo.
Entonces, ¿cuanto código repetido es aceptable? Esta pregunta indudablemente es más abierta, y pueden haber diferentes criterios. ¿Una línea de código duplicada está bien?
Por ejemplo, si tengo el siguiente código (GeneXus 9.0), ¿está bien?:
Haciendo algunos cambios al programa para eliminar estas repeticiones, llegamos a algo así:
Pienso que cuando se trata de 2 o más líneas de código es más fácil estar de acuerdo que no es bueno tener dicho código repetido.
Ahora, el problema, es como detectar esta situación.
Cuando el código repetido está dentro del mismo objeto, entonces es bastante fácil de detectar. No es trivial cuando se tienen programas grandes (más de 500 líneas por ejemplo), pero es posible.
El mayor problema es cuando el código repetido está en varios objetos. En esos casos no es para nada fácil de detectar.
Pienso que se podría automatizar, aunque no parece ser trivial. Algunas heurísticas que se me ocurre se podrían utilizar:
¿Por qué tener código repetido es un problema? La respuesta creo que es conocida por todos. El problema es que eventualmente las distintas versiones que se tienen se terminan desfazando, y empiezan a aparecer errores por tocar solo una. Además después de un cierto tiempo, es muy difícil saber cual de todas las versiones es la correcta, porque en general los cambios no se hacen en todas parejo.
Entonces, ¿cuanto código repetido es aceptable? Esta pregunta indudablemente es más abierta, y pueden haber diferentes criterios. ¿Una línea de código duplicada está bien?
Por ejemplo, si tengo el siguiente código (GeneXus 9.0), ¿está bien?:
if DocImpres = 'S'Nótese que los dos call al procedure GLog son idénticos... Además, los literales 'OC' y 'CMANUDOCOC' se usan varias veces en el código...
call(WGLLogDe,&msg1, &msg2, 'OC', 'CMANUDOCOC', &GlLogRef, '', &confirmo)
if &confirmo = 'N'
Call(PGLog, &msg1, &msg2, 'OC', 'CMANUDOCOC', &GlLogRef, '', &PedirDet)
endif
else
Call(PGLog, &msg1, &msg2, 'OC', 'CMANUDOCOC', &GlLogRef, '', &PedirDet)
endif
Haciendo algunos cambios al programa para eliminar estas repeticiones, llegamos a algo así:
&logGrabado = 'N'¿Es mejor que el código anterior? Yo creo que sí, pero seguramente para un caso tan simple haya discrepancias...
&GLLogSis = 'OC'
&TpoModId = 'CMANUDOCOC'
if DocImpres = 'S'
&logGrabado = udp(WGLLogDe, &msg1, &msg2, &GLLogSis, &TpoModId, &GlLogRef, '')
endif
if &logGrabado = 'N'
Call(PGLog, &msg1, &msg2, &GLLogSis, &TpoModId, &GlLogRef, '', '')
endif
Pienso que cuando se trata de 2 o más líneas de código es más fácil estar de acuerdo que no es bueno tener dicho código repetido.
Ahora, el problema, es como detectar esta situación.
Cuando el código repetido está dentro del mismo objeto, entonces es bastante fácil de detectar. No es trivial cuando se tienen programas grandes (más de 500 líneas por ejemplo), pero es posible.
El mayor problema es cuando el código repetido está en varios objetos. En esos casos no es para nada fácil de detectar.
Pienso que se podría automatizar, aunque no parece ser trivial. Algunas heurísticas que se me ocurre se podrían utilizar:
- Analizar las navegaciones de los objetos, para ver si hay navegaciones iguales. Esos objetos son candidatos a analizar. No necesariamente quiere decir que se pueda mejorar el código, pero sí vale la pena revisarlo.
- En general cuando se da que hay código repetido, el mismo está en el entorno de la llamada a un objeto, o en torno al uso de un determinado atributo. Por ejemplo, tengo un procedimiento que me devuelve un valor, y luego hago algo con dicho valor, pero en varios lugares lo que hago es exáctamente lo mismo...
Hola Marcos.
ResponderBorrarCasualmente hace años estoy en la misma inquietud.
En una época hice algunas implementaciones de prueba pero no logré nada muy bueno (hay que lograr implementar un buen parser GeneXus para luego poder crear una buena abstracción del mismo para lograr analizar patrones para encontrar clones).
Es un tema muy complejo, espero lograr avanzar en el correr del año con el proyecto Genoma como para poder incluir luego de ese proyecto algo de detección de clones al mismo.
Existe mucha literatura al respecto, tengo un sitio en donde se encuentra un gran compilado sobre el tema por si alguien quiere ponerse un poco más al tanto
http://twitter.com/3dgiordano/status/9438337663
Siempre existe la posibilidad de irse a la solución más simple, y experimentar... si alguien tiene tiempo y quiere hacerlo, sugiero usar alguna implementación como SIMIAN que posee soporte de búsqueda de clones en texto plano, volcando los códigos fuente de pgms GX a texto plano podrían encontrar algo http://www.redhillconsulting.com.au/products/simian/index.html
(En el link de recursos hay una sección Tools en donde existen otras implementaciones, SIMIAN tiene la particularidad de tener una implementación .NET para quienes quieran integrarlo con extensiones GX).
David, gracias por la información.
ResponderBorrarPor el lado de Simian capaz que se puede hacer algo... Hay que ver si la comparación de texto plano tiene alguna "inteligencia" como no considerar los espacios en blanco y demás.
Voy a ver si le puedo dedicar un rato.
El tema suena "divertido". Varias puntas: codigo repetido, legibilidad, modularización,re-uso, en fin, creo que muchas puntas.
ResponderBorrarUna idea capaz que es super simple pero que capaz aporta ¿que tal armar Data Selectors a partir de las navegaciones?
Lo veo para el lado de KBDoctor es decir, analizar el código y sugerir los cambios. Ya que lo haga una herramienta solito se complica
Nada, una idea "chota" capaz
Marcos:
ResponderBorrarMe gusta que hayas planteado este tema en publico. Es una area de oportunidad de mejora del software desarrollado, pues siempre que hay codigo repetido, hay posibilidades de refactoring o de reutilizacion.
Una vez encontrado el codigo repetido, puede merecer la pena la creacion de una herramienta (gxpatterns?) para generar esos objetos, o partes de objetos, o como dice GusCarr, hacer el refactoring para mejorar la calidad del codigo.
En base a lo que mando David, estuve probando con simian, con los fuentes generados (en mi caso en C#) y aparecieron varias cosas interesantes que voy seguir investigando.
La contra de trabajar con el codigo generado, es que es mas trabajoso identificar cual es el codigo que se identifico como repetido en el codigo Genexus, pero el que identifique los objetos que lo contiene, ya es un gran paso.
Tambien hay gran cantidad de codigo, que Genexus genera en forma muy similar en todos los objetos, por lo que hay muchos "falsos positivos", que hay que eliminar.
La ventaja que tiene el enfoque es que las herramientas funcionan tal como estan, sin tener que hacer ninguna modificacion.
Enrique : Probaron el simian con los xmls de navegación en lugar del fuente generado? Falsos positivos vas a tener igual, pero deberían ser menos y los casos más claros de identificar.
ResponderBorrarPablo:
ResponderBorrarNo probe usar el simian con los archivos de la navegacion.
Cuando lo use con muchos archivos, no logre que terminara en un tiempo razonable.
Tenemos un utilitario que genera un distribute por cada objeto (KBCVSP - Control de versiones sin pretensiones) y vamos a comparar estos fuentes primero.
Hay varios archivos que se pueden comparar para buscar duplicados, que pueden ser:
Archivos de navegacion = Para encontrar navegaciones parecidas, pueden servir para hacer refactoring y poner Data Selectors.
Archivos xpz = Para encontrar codigos parecidos.