Duplicación de código sin abstracción obvia

14

¿Alguna vez ha encontrado un caso de duplicación de código en el que, al mirar las líneas de código, no puede ajustar una abstracción temática que describa fielmente su papel en la lógica? ¿Y qué hiciste para abordarlo?

Es una duplicación de código, por lo que, idealmente, necesitamos hacer un poco de refractorización, como por ejemplo hacer que sea su propia función. Pero dado que el código no tiene una buena abstracción para describirlo, el resultado sería una función extraña para la que ni siquiera podemos encontrar un buen nombre, y cuyo papel en la lógica no es obvio solo con mirarlo. Eso, para mí, perjudica la claridad del código. Podemos preservar la claridad y dejarla como está, pero luego perjudicamos la mantenibilidad.

¿Cuál crees que es la mejor manera de abordar algo como esto?

EpsilonVector
fuente

Respuestas:

18

A veces, la duplicación de código es el resultado de un "juego de palabras": dos cosas se ven iguales, pero no lo son.

Es posible que el exceso de abstracción pueda romper la verdadera modularidad de su sistema. Bajo el régimen de modularidad, debe decidir "¿qué es probable que cambie?" y "¿qué es estable?". Lo que sea estable se coloca en la interfaz, mientras que lo que es inestable se encapsula en la implementación del módulo. Luego, cuando las cosas cambian, el cambio que necesita hacer está aislado en ese módulo.

La refactorización es necesaria cuando lo que creía que era estable (por ejemplo, esta llamada a la API siempre tomará dos argumentos) debe cambiar.

Entonces, para estos dos fragmentos de código duplicados, preguntaría: ¿un cambio requerido en uno necesariamente significa que el otro también debe cambiarse?

La forma en que respondas esa pregunta podría darte una mejor idea de lo que podría ser una buena abstracción.

Los patrones de diseño también son herramientas útiles. Quizás su código duplicado esté atravesando alguna forma, y ​​el patrón iterador debería aplicarse.

Si su código duplicado tiene múltiples valores de retorno (y es por eso que no puede hacer un método de extracción simple), entonces quizás debería hacer una clase que contenga los valores devueltos. La clase podría llamar a un método abstracto para cada punto que varía entre los dos fragmentos de código. Luego haría dos implementaciones concretas de la clase: una para cada fragmento. [Este es efectivamente el patrón de diseño del Método de plantilla, que no debe confundirse con el concepto de plantillas en C ++. Alternativamente, lo que está viendo podría resolverse mejor con el patrón Estrategia.]

Otra forma natural y útil de pensarlo es con funciones de orden superior. Por ejemplo, hacer lambdas o usar clases internas anónimas para que el código pase a la abstracción. En general, puede eliminar la duplicación, pero a menos que realmente haya una relación entre ellos [si uno cambia, también lo debe hacer el otro], entonces podría estar dañando la modularidad, no evitándola.

Macneil
fuente
4

Cuando te encuentras con una situación como esta, es mejor pensar en abstracciones "no tradicionales". Quizás tenga mucha duplicación dentro de una función y factorizar una función antigua simple no encaja muy bien porque tiene que pasar demasiadas variables. Aquí, una función anidada de estilo D / Python (con acceso al ámbito externo) funcionaría muy bien. (Sí, podría hacer una clase para mantener todo ese estado, pero si solo la está usando en dos funciones, esta es una solución fea y detallada para no tener funciones anidadas). Tal vez la herencia no encaja del todo, pero un Mixin funcionaría bien. Quizás lo que realmente necesitas es una macro. Tal vez deberías considerar alguna metaprogramación de plantilla o reflexión / introspección, o incluso programación generativa.

Por supuesto, desde un punto de vista pragmático, todo esto es difícil, si no imposible, si su idioma no los admite y no tiene suficientes capacidades de metaprogramación para implementarlos limpiamente dentro del lenguaje. Si este es el caso, no sé qué decirte, excepto "obtener un mejor idioma". Además, aprender un lenguaje de alto nivel con muchas capacidades de abstracción (como Ruby, Python, Lisp o D) podría ayudarlo a programar mejor en lenguajes de nivel inferior donde algunas de las técnicas aún podrían ser utilizables, pero menos obvias.

dsimcha
fuente
+1 para muchas técnicas excelentes comprimidas en un espacio reducido. (Bueno, también habría sido +1 por las técnicas descritas.)
Macneil
3

Personalmente lo ignoro y sigo adelante. Lo más probable es que si es un caso extraño, es mejor duplicarlo, ¡podría pasar años refactorizando y el próximo desarrollador echará un vistazo y deshacerá su cambio!

Toby
fuente
2

Sin una muestra de código, es difícil decir por qué su código no tiene una abstracción fácilmente identificable. Con esa advertencia, aquí hay un par de ideas:

  • en lugar de crear una nueva función para contener el código común, divida la funcionalidad en varias partes distintas;
  • agrupe piezas pequeñas en base a tipos de datos comunes o comportamiento abstracto;
  • reescribe el código duplicado dadas las nuevas piezas;
  • Si el nuevo código aún desafía una abstracción clara, divídalo en pequeño y repita el proceso.

La mayor dificultad en este ejercicio es que su función probablemente incorpore demasiados comportamientos no relacionados en un nivel de abstracción dado, y necesita manejar algunos de ellos en niveles inferiores. Usted supone correctamente que la claridad es clave para mantener el código, pero aclarar el comportamiento del código (su condición actual) es muy diferente de aclarar la intención del código.

Haga que el resumen de las piezas de código más pequeñas haga que sus firmas de función identifiquen el qué, y las piezas más grandes deberían ser más fáciles de clasificar.

Huperniketes
fuente