Pregunta

El código muy repetitivo es generalmente algo malo y existen patrones de diseño que pueden ayudar a minimizarlo.Sin embargo, a veces es simplemente inevitable debido a las limitaciones del propio idioma.Tome el siguiente ejemplo de java.util.Arrays:

/**
 * Assigns the specified long value to each element of the specified
 * range of the specified array of longs.  The range to be filled
 * extends from index <tt>fromIndex</tt>, inclusive, to index
 * <tt>toIndex</tt>, exclusive.  (If <tt>fromIndex==toIndex</tt>, the
 * range to be filled is empty.)
 *
 * @param a the array to be filled
 * @param fromIndex the index of the first element (inclusive) to be
 *        filled with the specified value
 * @param toIndex the index of the last element (exclusive) to be
 *        filled with the specified value
 * @param val the value to be stored in all elements of the array
 * @throws IllegalArgumentException if <tt>fromIndex &gt; toIndex</tt>
 * @throws ArrayIndexOutOfBoundsException if <tt>fromIndex &lt; 0</tt> or
 *         <tt>toIndex &gt; a.length</tt>
 */
public static void fill(long[] a, int fromIndex, int toIndex, long val) {
    rangeCheck(a.length, fromIndex, toIndex);
    for (int i=fromIndex; i<toIndex; i++)
        a[i] = val;
}

El fragmento anterior aparece en el código fuente 8 veces, con muy poca variación en la firma del método/documentación, pero exactamente el mismo cuerpo del método, uno para cada uno de los tipos de matriz raíz int[], short[], char[], byte[], boolean[], double[], float[], y Object[].

Creo que, a menos que se recurra a la reflexión (que es un tema completamente diferente en sí mismo), esta repetición es inevitable.Entiendo que, como clase de utilidad, una concentración tan alta de código Java repetitivo es muy atípica, pero incluso con las mejores prácticas, la repetición sucede!La refactorización no siempre funciona porque no siempre es posible (el caso obvio es cuando la repetición está en la documentación).

Obviamente mantener este código fuente es una pesadilla.Un ligero error tipográfico en la documentación, o un error menor en la implementación, se multiplica por la cantidad de repeticiones que se hayan realizado.De hecho, el mejor ejemplo involucra exactamente esta clase:

Blog de investigación de Google - Extra, Extra - Lea todo al respecto:Casi todas las búsquedas y combinaciones binarias no funcionan (por Joshua Bloch, ingeniero de software)

El error es sorprendentemente sutil y ocurre en lo que muchos pensaban que era simplemente un algoritmo simple y directo.

    // int mid =(low + high) / 2; // the bug
    int mid = (low + high) >>> 1; // the fix

La línea de arriba aparece 11 veces en el código fuente!

Entonces mis preguntas son:

  • ¿Cómo se manejan en la práctica este tipo de código/documentación Java repetitivo?¿Cómo se desarrollan, mantienen y prueban?
    • ¿Empiezas con "el original", lo haces lo más maduro posible y luego copias y pegas según sea necesario y esperas no cometer un error?
    • Y si cometió un error en el original, entonces simplemente corríjalo en todas partes, a menos que se sienta cómodo eliminando las copias y repitiendo todo el proceso de replicación.
    • ¿Y también aplica este mismo proceso para el código de prueba?
  • ¿Java se beneficiaría de algún tipo de preprocesamiento de código fuente de uso limitado para este tipo de cosas?
    • ¿Quizás Sun tenga su propio preprocesador para ayudar a escribir, mantener, documentar y probar este tipo de código de biblioteca repetitivo?

Un comentario solicitó otro ejemplo, así que saqué este de las Colecciones de Google: com.google.common.base.Predicados líneas 276-310 (AndPredicate) frente a las líneas 312-346 (OrPredicate).

La fuente de estas dos clases es idéntica, excepto por:

  • AndPredicate vs OrPredicate (cada uno aparece 5 veces en su clase)
  • "And(" vs Or(" (en el respectivo toString() métodos)
  • #and vs #or (en el @see Comentarios de Javadoc)
  • true vs false (en apply; ! se puede reescribir fuera de la expresión)
  • -1 /* all bits on */ vs 0 /* all bits off */ en hashCode()
  • &= vs |= en hashCode()
¿Fue útil?

Solución

Para las personas que tienen una necesidad imperiosa de rendimiento, el boxeo y unboxing y generified colecciones y otras cosas son grandes no-no.

El mismo problema ocurre en el rendimiento del equipo donde se necesita el mismo complejo de trabajar tanto para float y double (decir algo del método mostrado en de Goldberd " Lo que todo científico de la computación debe saber acerca de los números de punto flotante " de papel).

Hay una razón por la Trove 's TIntIntHashMap ejecuta círculos alrededor HashMap<Integer,Integer> de Java cuando se trabaja con una cantidad similar de datos .

Ahora, ¿cómo son el código fuente de la colección Trove escrito?

Mediante el uso de código fuente instrumentación por supuesto:)

Hay varias bibliotecas de Java para obtener un rendimiento más alto (mucho más alto que el valor por defecto los de Java) que el uso de generadores de código para crear el código fuente repetida.

Todos sabemos que el "código fuente de instrumentación" es malo y que la generación de código es una porquería, pero aún así es como las personas que realmente saben lo que están haciendo (es decir, el tipo de gente que cosas como la escritura Trove) hacerlo :)

Por lo que vale generamos código fuente que contiene grandes advertencias como:

/*
 * This .java source file has been auto-generated from the template xxxxx
 * 
 * DO NOT MODIFY THIS FILE FOR IT SHALL GET OVERWRITTEN
 * 
 */

Otros consejos

Si es absolutamente necesario duplicar el código, siga los grandes ejemplos que has dado y agrupar todos ese código en un lugar donde es fácil de encontrar y corregir cuando se tiene que hacer un cambio. Documentar la duplicación y, más importante aún, el razón de la duplicación de manera que todos los que vienen después de que es consciente de ambos.

Wikipedia No te repitas (DRY) o duplicación es mal ( DIE)

En algunos contextos, el esfuerzo necesario para hacer cumplir la filosofía seco puede ser mayor que el esfuerzo por mantener copias separadas de los datos. En algunos otros contextos, información duplicada es inmutable o mantenerse bajo un control lo suficientemente apretado para que DRY no es necesario.

Probablemente no hay respuesta o técnica para evitar problemas por el estilo.

Incluso los lenguajes elegantes como Haskell tienen código repetitivo (ver mi publicación sobre haskell y serialización)

Parece que hay tres opciones para este problema:

  1. Usa la reflexión y pierde rendimiento.
  2. Utilice preprocesamiento como Template Haskell o Caml4p equivalente para su idioma y viva con maldad
  3. O mis macros de uso favoritas si su idioma lo admite (esquema y ceceo)

Considero que las macros son diferentes al preprocesamiento porque las macros generalmente están en el mismo idioma que el destino, mientras que el preprocesamiento es un idioma diferente.

Creo que las macros Lisp/Scheme resolverían muchos de estos problemas.

Me conseguir que el Sol tiene al documento como este para el código de la biblioteca Java SE y tal vez otros autores biblioteca tercio del partido hacen así.

Sin embargo, creo que es una absoluta pérdida de copiar y pegar documentación a lo largo de un archivo de este tipo en código que sólo se utiliza en casa. Sé que muchas personas no estarán de acuerdo, ya que hará sus casas en JavaDocs ven menos limpio. Sin embargo, el comercio fuera es que se hace el código más limpio, lo cual, en mi opinión, es más importante.

tipos primitivos de Java que tornillo, especialmente cuando se trata de matrices. Si estás preguntando específicamente sobre el código que implica tipos primitivos, entonces yo diría que sólo tratar de evitarlos. El método Object [] es suficiente si utiliza los tipos de caja.

En general, se necesita una gran cantidad de pruebas unitarias y realmente no hay nada más que hacer, aparte de tener que recurrir a la reflexión. Como usted ha dicho, es otro tema por completo, pero no ser demasiado miedo de reflexión. Escribir el código DRYest primero puede, a continuación, el perfil it y determinar si el impacto en el rendimiento de reflexión es muy malo como para justificar escribir y mantener el código adicional.

Se puede usar un generador de código para construir variantes del código utilizando una plantilla. En ese caso, la fuente de Java es un producto del generador y el código real es la plantilla.

Dados dos fragmentos de código que se afirman que son similares, la mayoría de los lenguajes tienen facilidades limitadas para construir abstracciones que unifiquen los fragmentos de código en un monolito.Para abstraer cuando su idioma no puede hacerlo, debe salir del idioma :-{

El mecanismo de "abstracción" más general es un procesador macro completo que puede aplicar cálculos arbitrarios al "cuerpo macro" mientras lo crea una instancia (piense en Reescritura de publicaciones o cadenas sistema, que es capaz de Turing). M4 y GPM son ejemplos por excelencia.El preprocesador C no es uno de estos.

Si tiene un procesador de macros de este tipo, puede construir una "abstracción" como una macro y ejecutar el procesador de macros en su texto fuente "abstraído" para producir el código fuente real que compila y ejecuta.

También puedes utilizar versiones más limitadas de las ideas, a menudo llamadas "generadores de código".Por lo general, no son compatibles con Turing, pero en muchos casos funcionan bastante bien.Depende de qué tan sofisticada deba ser su "creación de instancias macro".(La razón por la que la gente está enamorada del mecanismo de plantillas de C++ es que, a pesar de su fealdad, es Turing es capaz y, por lo tanto, la gente puede realizar tareas de generación de código realmente desagradables pero sorprendentes con él).Otra respuesta aquí menciona Trove, que aparentemente se encuentra en la categoría más limitada pero aún muy útil.

Los macroprocesadores realmente generales (como el M4) manipulan solo texto;eso los hace poderosos pero no manejan bien la estructura del lenguaje de programación, y es realmente incómodo escribir un generador en un procesador tan mcaro que no solo puede producir código, sino también optimizar el resultado generado.La mayoría de los generadores de código que encuentro "conectan esta cadena a esta plantilla de cadena" y, por lo tanto, no pueden optimizar el resultado generado.Si desea generar código arbitrario y un alto rendimiento para arrancar, necesita algo que sea compatible con Turing pero que comprenda la estructura del código generado para que pueda manipularlo fácilmente (por ejemplo, optimizarlo).

Esta herramienta se llama Sistema de transformación de programas.Una herramienta de este tipo analiza el texto fuente tal como lo hace un compilador y luego realiza análisis/transformaciones sobre él para lograr el efecto deseado.Si puede colocar marcadores en el texto fuente de su programa (por ejemplo, comentarios estructurados o anotaciones en idiomas que los tengan) indicando a la herramienta de transformación del programa qué hacer, entonces puede usarlo para llevar a cabo dicha instanciación de abstracción, generación de código y /u optimización de código.(La sugerencia de un cartel de conectarse al compilador de Java es una variación de esta idea).Utilizando un sistema general de transformación de pulpa (como Kit de herramientas de reingeniería de software DMS significa que puedes hacer esto prácticamente para cualquier idioma.

Una gran cantidad de este tipo de repetición puede ahora ser evitado gracias a los genéricos. Son un regalo del cielo al escribir el mismo código en el que sólo el cambio de tipos.

Lamentablemente, sin embargo, creo que las matrices genéricas todavía no son muy bien soportados. Por ahora, al menos, utilizar recipientes que le permite tomar ventaja de los genéricos. El polimorfismo es también una herramienta útil para reducir este tipo de duplicación de código.

Para responder a su pregunta acerca de cómo manejar código que es absolutamente necesario ser duplicada ... Tag cada caso con los comentarios fácilmente investigables. Hay algunos preprocesadores java por ahí, que se suman las macros de estilo C. Creo recordar netbeans tener uno.

Licenciado bajo: CC-BY-SA con atribución
No afiliado a StackOverflow
scroll top