Trabajo en una base de código bastante grande. Cientos de clases, toneladas de archivos diferentes, muchas funcionalidades, lleva más de 15 minutos extraer una copia nueva, etc.
Un gran problema con una base de código tan grande es que tiene bastantes métodos de utilidad y que hacen lo mismo, o tiene un código que no usa estos métodos de utilidad cuando podría. Y también los métodos de utilidad no son todos en una sola clase (porque sería un gran desastre).
Soy bastante nuevo en la base del código, pero el líder del equipo que ha estado trabajando en él durante años parece tener el mismo problema. Conduce a una gran cantidad de código y duplicación de trabajo, y como tal, cuando algo se rompe, generalmente se divide en 4 copias del mismo código.
¿Cómo podemos frenar este patrón? Al igual que con la mayoría de los proyectos grandes, no todo el código está documentado (aunque algunos lo están) y no todo el código está ... bueno, limpio. Pero, básicamente, sería realmente bueno si pudiéramos trabajar en mejorar la calidad a este respecto para que en el futuro tuviéramos menos duplicación de código, y cosas como las funciones de utilidad fueran más fáciles de descubrir.
Además, las funciones de utilidad generalmente están en alguna clase auxiliar estática, en alguna clase auxiliar no estática que funciona en un solo objeto, o es un método estático en la clase con el que principalmente "ayuda".
Tuve un experimento al agregar funciones de utilidad como métodos de Extensión (no necesitaba nada interno de la clase, y definitivamente solo se requería en escenarios muy específicos). Esto tuvo el efecto de evitar el desorden en la clase primaria y demás, pero ya no se puede descubrir a menos que ya lo sepas.
Respuestas:
La respuesta simple es que realmente no puede evitar la duplicación de código. Sin embargo, puede "arreglarlo" a través de un difícil proceso continuo incremental repetitivo que se reduce a dos pasos:
Paso 1. Comience a escribir pruebas en código heredado (preferiblemente usando un marco de prueba)
Paso 2. Reescribe / refactoriza el código que está duplicado usando lo que has aprendido de las pruebas
Puede usar herramientas de análisis estático para detectar código duplicado y para C # hay muchas herramientas que pueden hacer esto por usted:
Herramientas como esta lo ayudarán a encontrar puntos en el código que haga cosas similares. Continúe escribiendo pruebas para determinar que realmente lo hacen; use las mismas pruebas para simplificar el uso del código duplicado. Esta "refactorización" se puede hacer de varias maneras y puede usar esta lista para determinar la correcta:
Además, también hay un libro completo sobre este tema de Michael C. Feathers, Working Effectively with Legacy Code . Se profundizan las diferentes estrategias que puede tomar para cambiar el código a mejor. Tiene un "algoritmo de cambio de código heredado" que no está lejos del proceso de dos pasos anterior:
El libro es una buena lectura si se trata de un desarrollo de campo marrón, es decir, código heredado que debe cambiar.
En este caso
En el caso del OP, puedo imaginar que el código no comprobable es causado por una olla de miel para "métodos y trucos de utilidad" que toman varias formas:
Tenga en cuenta que no hay nada de malo en esto, pero por otro lado, generalmente son difíciles de mantener y cambiar. Los métodos de extensiones en .NET son métodos estáticos, pero también son relativamente fáciles de probar.
Sin embargo, antes de continuar con las refactorizaciones, hable con su equipo al respecto. Deben mantenerse en la misma página que usted antes de continuar con cualquier cosa. Esto se debe a que si está refactorizando algo, entonces hay muchas posibilidades de que cause conflictos de fusión. Entonces, antes de reelaborar algo, investígalo, dile a tu equipo que trabaje en esos puntos de código con precaución por un tiempo hasta que hayas terminado.
Dado que el OP es nuevo en el código, hay otras cosas que hacer antes de que debas hacer algo:
¡Buena suerte!
fuente
También podríamos intentar ver el problema desde otro ángulo. En lugar de pensar que el problema es la duplicación del código, podemos considerar si el problema se origina en la falta de políticas para la reutilización del código.
Hace poco leí el libro Ingeniería de software con componentes reutilizables y, de hecho, tiene un conjunto de ideas muy interesantes sobre cómo fomentar la reutilización del código a nivel de la organización.
El autor de este libro, Johannes Sametinger, describe un conjunto de barreras para la reutilización del código, algunas conceptuales y otras técnicas. Por ejemplo:
Según el autor, se producen diferentes niveles de reutilización dependiendo de la madurez de una organización.
Entonces, tal vez, además de todas las sugerencias dadas en otras respuestas, podría trabajar en el diseño de un programa de reutilización, involucrar a la administración, formar un grupo de componentes responsable de identificar los componentes reutilizables mediante el análisis de dominio y definir un repositorio de componentes reutilizables que otros desarrolladores puedan fácilmente consultar y buscar soluciones cocinadas a sus problemas.
fuente
Hay 2 posibles soluciones:
Prevención : trate de tener la mejor documentación posible. Haga que cada función esté debidamente documentada y sea fácil de buscar en toda la documentación. Además, al escribir código, haga obvio dónde debe ir el código, por lo que es obvio dónde buscar. La cantidad limitada de código de "utilidad" es uno de los puntos clave de esto. Cada vez que escucho "vamos a hacer una clase de utilidad", mi cabello se levanta y mi sangre se congela, porque obviamente es un problema. Siempre tenga una forma rápida y fácil de pedirle a las personas que conozcan la base de código siempre que exista alguna característica.
Solución : si la prevención falla, debería ser capaz de resolver rápida y eficientemente el código problemático. Su proceso de desarrollo debería permitir corregir rápidamente el código duplicado. Las pruebas unitarias son perfectas para esto, ya que puede modificar eficientemente el código sin temor a romperlo. Entonces, si encuentra 2 piezas de código similares, abstraerlas en una función o clase debería ser fácil con un poco de refactorización.
Personalmente, no creo que la prevención sea posible. Cuanto más lo intentes, más problemático será encontrar características ya existentes.
fuente
No creo que este tipo de problemas tenga la solución general. El código duplicado no se creará si los desarrolladores tienen la voluntad suficiente para buscar el código existente. También los desarrolladores podrían solucionar los problemas en el acto si lo desean.
Si el lenguaje es C / C ++, la fusión de duplicación será más fácil debido a la flexibilidad de vinculación (se puede llamar a cualquier
extern
función sin información previa) Para Java o .NET, es posible que deba idear clases auxiliares y / o componentes de utilidad.Por lo general, empiezo la eliminación de duplicación del código existente solo si los principales errores surgen de las partes duplicadas.
fuente
Este es un problema típico de un proyecto más grande que ha sido manejado por muchos programadores, que han estado contribuyendo a veces bajo mucha presión de grupo. Es muy tentador hacer una copia de una clase y adaptarla a esa clase específica. Sin embargo, cuando se encuentra un problema en la clase de origen, también debe resolverse en sus descendientes, que a menudo se olvidan.
Hay una solución para esto y se llama Genéricos, que se ha introducido en Java 6. Es el equivalente de C ++ llamado Plantilla. Código del cual aún no se conoce la clase exacta dentro de una clase genérica. Verifique Java Generics y encontrará toneladas y toneladas de documentación para ello.
Un buen enfoque es reescribir el código que parece ser copiado / pegado en muchos lugares reescribiendo el primero que necesita, es decir, corregir debido a un determinado error. Vuelva a escribirlo para usar Generics y también escriba código de prueba muy riguroso.
Asegúrese de invocar todos los métodos de la clase Genérica. También puede introducir herramientas de cobertura de código: el código genérico debe ser una cobertura de código completo porque se usará en varios lugares.
También escriba código de prueba, es decir, utilizando JUnit o similar para la primera clase designada que se utilizará junto con el código genérico.
Comience a usar el código genérico para la segunda versión (la mayoría de las veces) copiada cuando todo el código anterior funcione y esté completamente probado. Verá que hay algunas líneas de código que son específicas para esa Clase designada. Puede llamar a estas líneas codificadas en un método protegido abstracto que necesita ser implementado por la clase derivada que usa la clase base Genérica.
Sí, es un trabajo tedioso, pero a medida que avanza, cada vez será mejor extraer clases similares y reemplazarlo con algo que esté muy limpio, bien escrito y mucho más fácil de mantener.
He tenido una situación similar en la que una clase genérica eventualmente reemplazó algo como 6 o 7 otras clases casi idénticas que eran casi casi idénticas pero que han sido copiadas y pegadas por varios programadores durante un período de tiempo.
Y sí, estoy muy a favor de las pruebas automatizadas del código. Al principio costará más, pero definitivamente le ahorrará una gran cantidad de tiempo en general. E intente lograr una cobertura de código general de al menos 80% y 100% para el código genérico.
Espero que esto ayude y buena suerte.
fuente
De hecho, voy a hacerme eco de la opinión menos popular aquí y de lado
Gangnus
y sugerir que la duplicación de código no siempre es dañina y, a veces, podría ser el mal menor.Si, por ejemplo, me das la opción de usar:
A) Una biblioteca de imágenes estable (inmutable) y pequeña, bien probada , que duplica algunas docenas de líneas de código matemático trivial para matemática vectorial como productos de punto y lerps y pinzas, pero está completamente desacoplada de cualquier otra cosa y se construye en una fracción de un segundo.
B) Una biblioteca de imágenes inestable (que cambia rápidamente) que depende de una biblioteca matemática épica para evitar esa docena de líneas de código mencionadas anteriormente, con la biblioteca matemática inestable y constantemente recibiendo nuevas actualizaciones y cambios, y por lo tanto la biblioteca de imágenes también tiene que ser reconstruido si no se cambia directamente también. Se tarda 15 minutos en limpiar, construir todo.
... entonces, obviamente, debería ser obvio para la mayoría de las personas que A, y en realidad precisamente debido a su duplicación de código menor, es preferible. El énfasis clave que necesito hacer es la parte bien probada . Obviamente, no hay nada peor que tener un código duplicado que ni siquiera funciona en primer lugar, momento en el que está duplicando errores.
Pero también hay que pensar en el acoplamiento y la estabilidad, y algunas duplicaciones modestas aquí y allá pueden servir como un mecanismo de desacoplamiento que también aumenta la estabilidad (naturaleza inmutable) del paquete.
Por lo tanto, mi sugerencia será centrarme más en probar y tratar de llegar a algo realmente estable (como no cambiar, encontrar pocas razones para cambiar en el futuro) y confiable cuyas dependencias de fuentes externas, si las hay, son muy estable, sobre tratar de eliminar todas las formas de duplicación en su código base. En un entorno de equipo grande, este último tiende a ser un objetivo poco práctico, sin mencionar que puede aumentar el acoplamiento y la cantidad de código inestable que tiene en su base de código.
fuente
No olvide que la duplicación de código no siempre es perjudicial. Imagínese: ahora tiene que resolver una tarea en módulos absolutamente diferentes de su proyecto. Justo ahora es la misma tarea.
Podría haber tres razones para ello:
Algunos temas relacionados con esta tarea son los mismos para ambos módulos. En este caso, la duplicación del código es mala y debe liquidarse. Sería inteligente crear una clase o un módulo para admitir este tema y utilizar sus métodos en ambos módulos.
La tarea es teórica en términos de su proyecto. Por ejemplo, es de física o matemáticas, etc. La tarea existe independientemente en su proyecto. En este caso, la duplicación del código es mala y también debe liquidarse. Crearía una clase especial para tales funciones. Y use dicha función en cualquier módulo donde lo necesite.
Pero en otros casos la coincidencia de tareas es una coincidencia temporal y nada más. Sería peligroso creer que estas tareas seguirán siendo las mismas durante los cambios del proyecto debido a la refactorización e incluso a la depuración. En este caso, sería mejor crear dos mismas funciones / piezas de código en diferentes lugares. Y los cambios futuros en uno de ellos no tocarán al otro.
Y este tercer caso ocurre muy a menudo. Si duplica "sin saberlo", principalmente es por esta misma razón: ¡no es una duplicación real!
Por lo tanto, trate de mantenerlo limpio cuando sea realmente necesario y no tema la duplicación si no es necesario.
fuente
code duplication is not always harmful
Es un mal consejo.