c++ - ¿Alguna razón válida para la duplicación de código?
design language-agnostic (20)
Actualmente estoy revisando un proyecto de C ++ muy antiguo y veo mucha duplicación de código allí.
Por ejemplo, hay una clase con 5 manejadores de mensajes MFC cada uno con 10 líneas idénticas de código. O hay un fragmento de 5 líneas para una transformación de cadena muy específica cada aquí y allí. Reducir la duplicación de código no es un problema en estos casos.
Pero tengo la extraña sensación de que podría estar malinterpretando algo y que originalmente había una razón para esta duplicación.
¿Cuál podría ser una razón válida para duplicar el código?
A veces, los métodos y las clases que tienen dominio no tienen nada en común, pero la implementación se parece mucho. En estos casos, a menudo es mejor hacer la duplicación de código como cambios futuros con mayor frecuencia, lo que no ramificará estas implementaciones en algo que no sea el mismo.
Además de carecer de experiencia, es posible que aparezcan los duplicados de código:
No hay tiempo para refactorizar adecuadamente
La mayoría de nosotros trabajamos en un mundo real donde las limitaciones reales nos obligan a pasar rápidamente a problemas reales en lugar de pensar en la bondad del código. Así que copiamos y pegamos y avanzamos. Conmigo, si más tarde veo que el código está duplicado varias veces más, es señal de que tengo que dedicarle más tiempo y convergir todas las instancias a una.
La generalización del código no es posible / no es ''bonita'' debido a restricciones de idioma
Digamos que en el interior de una función tiene varias afirmaciones que difieren mucho de una instancia a otra del mismo código duplicado. Por ejemplo: tengo una función que dibuja 2d array de miniaturas para el video, y está incrustado con el cálculo de cada posición de miniatura. Para calcular la prueba de aciertos (calcular el índice de miniaturas desde la posición de clic), estoy usando el mismo código pero sin pintar.
No estás seguro de que haya una generalización en absoluto
Duplicar el código al principio, y luego observar cómo evolucionará. Como estamos escribiendo software, podemos permitir modificaciones "lo más tarde posible" al software, ya que todo es "suave" y modificable.
Agregaré más si recuerdo algo más.
Agregado después ...
Desenrollar
Con el tiempo, antes de que los compiladores fueran inteligentes como Einstein y Hawking combinados, tenía que desenrollar los bucles o el código en línea para ser más rápidos. El desenrollado del bucle hará que su código se duplique, y probablemente sea más rápido en algunos porcentajes, de todos modos el compilador no lo hizo por usted.
Algo que encontramos que nos obligó a duplicar el código fue nuestro código de manipulación de píxeles. Trabajamos con imágenes MUY grandes y la sobrecarga de llamada de función se estaba comiendo del orden del 30% de nuestro tiempo por píxel.
La duplicación del código de manipulación de píxeles nos proporcionó un recorrido de imágenes un 20% más rápido a costa de la complejidad del código.
Este es obviamente un caso muy raro, y al final, infló nuestra fuente significativamente (una función de 300 líneas ahora es 1200 líneas).
Cuando comencé a programar, escribí una aplicación donde tenía un montón de funcionalidades similares que incluía una pequeña y ordenada función de 20-30 líneas ... Estaba muy orgulloso de mí mismo por escribir una pieza de código tan elegante.
Poco después, el cliente cambió el proceso en casos muy específicos, luego de nuevo, luego de nuevo, de nuevo, y de nuevo, y nuevamente ... (muchas muchas veces más) Mi código elegante se convirtió en un buggy muy difícil, hackoso, y alto lío de mantenimiento.
Un año después, cuando me pidieron que hiciera algo muy similar, deliberadamente decidí ignorar DRY. Reuní el proceso básico y generé todo el código duplicado. El código duplicado fue documentado y guardé la plantilla utilizada para generar el código. Cuando el cliente solicitó un cambio condicional específico (como, si x == y ^ z + b, luego 1 + 2 == 3.42) fue un pedazo de pastel. Fue increíblemente fácil de mantener y cambiar.
En retrospectiva, probablemente podría haber resuelto muchos de estos problemas con punteros de función y predicados, pero utilizando el conocimiento que tenía en ese momento, sigo creyendo en este caso específico, esta fue la mejor decisión.
Dado que existe el "Patrón de estrategia", no hay una razón válida para el código duplicado. No se debe duplicar una sola línea de código, todo lo demás es un error épico.
En proyectos grandes (aquellos con una base de código tan grande como un GB) es bastante posible perder la API existente. Esto normalmente se debe a documentación insuficiente o a la incapacidad del programador para ubicar el código original; por lo tanto, código duplicado.
Se reduce a la pereza, o mala práctica de revisión.
EDITAR:
Una posibilidad adicional es que puede haber código adicional en esos métodos que se eliminó en el camino.
¿Has mirado el historial de revisión en el archivo?
Es posible que desee hacerlo para asegurarse de que los cambios futuros en una parte no cambien involuntariamente la otra parte. por ejemplo, considere
Do_A_Policy()
{
printf("%d",1);
printf("%d",2);
}
Do_B_Policy()
{
printf("%d",1);
printf("%d",2);
}
Ahora puede evitar la "duplicación de código" con una función como esta:
first_policy()
{
printf("%d",1);
printf("%d",2);
}
Do_A_Policy()
{
first_policy()
}
Do_B_Policy()
{
first_policy()
}
Sin embargo, existe el riesgo de que algún otro programador desee cambiar Do_A_Policy () y lo haga cambiando first_policy () y provocará el efecto secundario de cambiar Do_B_Policy (), un efecto secundario del que el programador puede no estar enterado. entonces este tipo de "duplicación de código" puede servir como un mecanismo de seguridad contra este tipo de cambios futuros en el programa.
Hace mucho tiempo, cuando solía hacer programación de gráficos, en algunos casos especiales, utilizaba código duplicado de esta manera para evitar las declaraciones JMP de bajo nivel generadas en el código (mejoraría el rendimiento al evitar el salto a la etiqueta / función) . Fue una forma de optimizar y hacer un pseudo "inline".
Sin embargo, en este caso, no creo que sea por eso que lo estaban haciendo, je.
La única cosa "válida" de la que veo que surge esto es cuando esas líneas de código eran diferentes, luego convergían a la misma cosa a través de ediciones posteriores. Ya me había pasado esto antes, pero no con demasiada frecuencia.
Esto es, por supuesto, cuando es el momento de factorizar este segmento común de código en nuevas funcionalidades.
Dicho esto, no puedo pensar en ninguna forma razonable de justificar el código duplicado. Mira por qué es malo.
Es malo porque un cambio en un lugar requiere un cambio en varios lugares. Esto es un aumento de tiempo, con la posibilidad de errores. Al factorizarlo, usted mantiene el código en una única ubicación de trabajo. Después de todo, cuando escribe un programa no lo escribe dos veces, ¿por qué una función sería diferente?
La razón válida que se me ocurre es: si el código se vuelve mucho más complejo para evitar la duplicación. Básicamente, ese es el lugar donde haces casi lo mismo en varios métodos, pero no exactamente igual. Por supuesto, puede refactorizar y agregar parámetros especiales, incluidos punteros a diferentes miembros que deben modificarse. Pero el nuevo método refactorizado puede volverse demasiado complicado.
Ejemplo (pseudocódigo):
procedure setPropertyStart(adress, mode, value)
begin
d:=getObject(adress)
case mode do
begin
single:
d.setStart(SingleMode, value);
delta:
//do some calculations
d.setStart(DeltaSingle, calculatedValue);
...
end;
procedure setPropertyStop(adress, mode, value)
begin
d:=getObject(adress)
case mode do
begin
single:
d.setStop(SingleMode, value);
delta:
//do some calculations
d.setStop(DeltaSingle, calculatedValue);
...
end;
Podría refactorizar la llamada al método (setXXX) de alguna manera, pero dependiendo del idioma podría ser difícil (especialmente con la herencia). Es una duplicación de código ya que la mayoría del cuerpo es igual para cada propiedad, pero puede ser difícil refactorizar las partes comunes.
En resumen: si el método refactorizado es más complicado, iría con la duplicación de código aunque sea "malo" (y seguirá siendo malo).
No conozco muchas buenas razones para la duplicación de código, pero en lugar de saltar en primer lugar para refactorizar, probablemente sea mejor refactorizar los bits del código que realmente cambias, en lugar de alterar una gran base de código que no utilizas. sin embargo, entiendo completamente
No hay una buena razón para la duplicación de código.
Ver el patrón de diseño Refactor Mercilessly .
El programador original tenía prisa por cumplir con un plazo o perezoso. Siéntase libre de refactorizar y mejorar el código.
No tengo problemas con el código duplicado cuando es producido por un generador de código fuente.
Para ese tipo de duplicación de código (muchas líneas duplicadas muchas veces), diría:
- o pereza (simplemente pegue algún código aquí y allá, sin tener que preocuparse por el impacto que podría tener en otras partes de la aplicación, mientras escribo una nueva función y su uso en dos lugares podría, supongo, tener algún impacto)
- o no conoce ninguna buena práctica (código de reutilización, separando tareas diferentes en diferentes funciones / métodos)
Probablemente la primera solución, sin embargo, de lo que generalmente he visto :-(
La mejor solución que he visto en contra de eso: haz que tus desarrolladores comiencen manteniendo alguna aplicación anterior, cuando los contraten, eso les enseñará que este tipo de cosas no son buenas ... Y entenderán por qué, cuál es el parte más importante.
Dividir el código en varias funciones, volver a usar el código de la manera correcta y todo lo que a menudo viene con la experiencia, o no ha contratado a las personas adecuadas ;-)
Parece que el autor original o no tenía experiencia y / o fue presionado a tiempo. Los programadores más experimentados reúnen cosas que se reutilizan porque más tarde habrá menos mantenimiento, una forma de pereza.
Lo único que debe verificar es si hay efectos secundarios, si el código copiado accede a algunos datos globales, puede ser necesario refactorizar un poco.
editar: en la época en que los compiladores eran malos y los optimizadores aún más complicados, podía suceder que debido a algún error en el compilador, uno tuviera que hacer un truco así para evitar un error. Tal vez es algo como eso? ¿Qué edad tiene la edad?
Pereza, esa es la única razón por la que se me ocurre.
En una nota más seria. La única razón válida que puedo pensar es en los cambios al final del ciclo del producto. Estos tienden a someterse a un escrutinio mucho mayor y el cambio más pequeño tiende a tener la mayor tasa de éxito. En esa circunstancia limitada, es más fácil obtener un cambio de duplicación de código en lugar de refactorizar un cambio más pequeño.
Todavía deja un mal sabor en mi boca.
Si las diferentes tareas son similares por accidente, repetir las mismas acciones en dos lugares no es necesariamente una duplicación. Si las acciones en un lugar cambian, ¿es probable que también cambien en otros lugares? Entonces esta es una duplicación que debes evitar o refactorizar.
Además, a veces, incluso cuando la lógica se duplica, el costo de reducir la duplicación es demasiado alto. Esto puede suceder especialmente cuando no es solo duplicación de código: por ejemplo, si tiene un registro de datos con ciertos campos que se repite en diferentes lugares (definición de tabla de DB, clase de C ++, entrada de texto), la forma habitual de reducir esto la duplicación es con generación de código. Esto agrega complejidad a su solución. Casi siempre, esta complejidad vale la pena, pero a veces no es así - es su compromiso de hacer.
Todas las respuestas parecen correctas, pero creo que hay otra posibilidad. Tal vez haya consideraciones de rendimiento ya que las cosas que dices me recuerdan "código en línea". Siempre es más rápido ingresar funciones que llamarlas. ¿Quizás el código que ves ha sido preprocesado primero?
Una buena lectura acerca de esto es el diseño de software de c ++ a gran escala de John Lakos.
Tiene muchos puntos buenos acerca de la duplicación de código, donde podría ayudar u obstaculizar un proyecto.
El punto más importante es preguntar cuándo se decide eliminar la duplicación o el código duplicado:
Si este método cambia en el futuro, ¿quiero cambiar el comportamiento en el método duplicado o lo necesito para mantenerme como está?
Después de todo, los métodos contienen lógica (comercial) y, a veces, querrás cambiar la lógica para cada persona que llama, a veces no. Depende de las circunstancias.
Al final, se trata de mantenimiento, no de fuente bonita.
en mi humilde opinión, no hay lugar para la duplicación de código. echa un vistazo, por ejemplo, en este artículo de Wikipedia
o, veamos la cita de Larry Wall:
"Te alentaremos a desarrollar las tres grandes virtudes de un programador: la pereza, la impaciencia y la arrogancia".
es bastante claro que la duplicación de código no tiene nada que ver con la "pereza". jaja;)