Gestión de código y documentación altamente repetitivos en Java


El código altamente repetitivo generalmente es algo malo, y hay patrones de diseño que pueden ayudar a minimizar esto. Sin embargo, a veces es simplemente inevitable debido a las limitaciones del idioma en sí. Tomemos 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 documentación / firma del método, 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 uno 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, tal alta concentración de código Java repetitivo es altamente atípica, pero incluso con la mejor práctica, la repetición sucede! Refactorización no siempre funciona porque no siempre es posible (el caso obvio es cuando la repetición está en el documentación).

Obviamente mantener este código fuente es una pesadilla. Un pequeño error tipográfico en la documentación, o un error menor en la implementación, se multiplica por cuantas repeticiones se hicieron. De hecho, el mejor ejemplo pasa a involucrar a esta clase exacta:

Google Research Blog-Extra, Extra-Leer Todo sobre Él: Casi Todas las Búsquedas Binarias y Mergesorts están Rotos (por Joshua Bloch, Ingeniero de Software)

El error es sorprendentemente sutil, ocurriendo en lo que muchos pensaron que era solo un algoritmo simple y directo.

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

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

Así que mis preguntas son:

  • ¿Cómo se manejan estos tipos de código/documentación Java repetitiva en la práctica? ¿Cómo se desarrollan, mantienen y prueban?
    • Comienzas con "el original", y lo haces lo más maduro posible, y luego copias y pegas según sea necesario y esperas que no hayas hecho un error?
    • Y si cometiste un error en el original, entonces simplemente arréglalo en todas partes, a menos que te sientas cómodo con eliminar las copias y repetir todo el proceso de replicación?
    • ¿Y aplica este mismo proceso para el código de prueba también?
  • ¿Java se beneficiaría de algún tipo de preprocesamiento de código fuente de uso limitado para este tipo de cosas?
    • Tal vez Sun tenga su propio preprocesador para ayudar a escribir, mantener, documentar y probar este tipo de ¿de código de biblioteca repetitivo?

Un comentario solicitó otro ejemplo, así que saqué este de Google Collections: com.Google.común.basar.Predicados líneas 276-310 (AndPredicate) vs líneas 312-346 (OrPredicate).

La fuente para estas dos clases son idénticas, excepto para:

  • AndPredicate vs OrPredicate (cada uno aparece 5 veces en su clase)
  • "And(" vs Or(" (en los respectivos métodos toString())
  • #and vs #or (en el @see Javadoc comentarios)
  • true vs false (en apply; ! puede ser reescrito de la expresión)
  • -1 /* all bits on */ vs 0 /* all bits off */ en hashCode()
  • &= vs |= en hashCode()
Author: polygenelubricants, 2010-02-25

9 answers

Para las personas que absolutamente necesitan rendimiento, boxeo y unboxing y colecciones generificadas y lo que sea, son grandes no-no.

El mismo problema ocurre en la computación de rendimiento donde se necesita el mismo complejo para trabajar tanto para float y double (digamos algunos de los métodos que se muestran en Goldberd's "Lo que todo científico de la computación debe saber sobre los números de coma flotante" paper).

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

Ahora, ¿cómo se escribe el código fuente de la colección Trove?

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

Hay varias bibliotecas Java de mayor rendimiento (mucho más altas que las Java predeterminadas) que utilizan generadores de código para crear el código fuente repetido.

Todos sabemos que la "instrumentación de código fuente" es malvada y que la generación de código es basura, pero aún así es como la gente que realmente sabe lo que está haciendo (es decir, el tipo de personas que escriben cosas como Trove) lo hacen:)

Por si sirve de algo 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
 * 
 */
 31
Author: SyntaxT3rr0r,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2013-07-22 10:11:15

Si es absolutamente necesario duplicar el código, siga los grandes ejemplos que ha dado y agrupe todo ese código en un lugar donde sea fácil de encontrar y corregir cuando tenga que hacer un cambio. Documente la duplicación y, lo que es más importante, la razón de la duplicación para que todos los que vengan después de usted sean conscientes de ambos.

 16
Author: Bill the Lizard,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2010-02-25 20:10:52

De Wikipedia No Te Repitas (SECO) o la Duplicación es Mala (MORIR)

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

Probablemente no hay respuesta o técnica para prevenir problemas como ese.

 6
Author: stacker,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2010-02-25 20:20:25

Incluso los lenguajes fancy pants como Haskell tienen código repetitivo (ver mi post en haskell y serialización)

Parece Que hay tres opciones para este problema:

  1. Utilice la reflexión y pierda el rendimiento
  2. Utilice preprocesamiento como Plantilla Haskell o equivalente Caml4p para su lenguaje y vivir con maldad
  3. O mis macros de uso favoritos personales si su lenguaje lo soporta (scheme, y lisp)

Considero las macros diferentes de preprocesamiento porque las macros están generalmente en el mismo idioma que el destino es donde como preprocesamiento es un idioma diferente.

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

 4
Author: Adam Gent,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2017-09-17 06:54:42

Entiendo que Sun tiene que documentar así para el código de la biblioteca Java SE y tal vez otros escritores de bibliotecas de terceros también lo hagan.

Sin embargo, creo que es un desperdicio copiar y pegar documentación en un archivo como este en código que solo se usa en casa. Sé que muchas personas no estarán de acuerdo porque hará que sus JavaDocs internos se vean menos limpios. Sin embargo, la compensación es que hace que su código sea más limpio, lo que, en mi opinión, es más importante.

 2
Author: jerry,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2010-02-25 20:23:25

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

En general, se necesitan muchas pruebas unitarias y realmente no hay nada más que hacer, aparte de recurrir a la reflexión. Como dijiste, es otro tema por completo, pero no tengas demasiado miedo a la reflexión. Escribe el código más seco que puedas primero, luego perfílelo y determine si el impacto del rendimiento de reflexión es lo suficientemente malo como para justificar la escritura y el mantenimiento del código adicional.

 2
Author: noah,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2010-02-25 20:26:36

Puede usar un generador de código para construir variaciones del código usando una plantilla. En ese caso, la fuente java es un producto del generador y el código real es la plantilla.

 2
Author: msalib,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2010-02-25 20:55:31

Dados dos fragmentos de código que se afirman ser similares, la mayoría de los lenguajes tienen facilidades limitadas para construir abstracciones que unifican los fragmentos de código en un monolito. Para abstraer cuando tu idioma no puede hacerlo, tienes que 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 instancian (piense en Post o reescritura de cadenas sistema, que es Turing capaz). M4 y GPM son ejemplos por excelencia. El preprocesador C no es uno de estos.

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

También puede usar versiones más limitadas de las ideas, a menudo llamadas "generadores de código". Estos generalmente no son capaces de Turing, pero en muchos casos funcionan lo suficientemente bien. Depende de lo sofisticada que sea su "creación de instancias macro". (La razón por la que la gente está enamorada del mecanismo de la plantilla C++ es que a pesar de su fealdad, es capaz de Turing y, por lo tanto, la gente puede hacer tareas de generación de código realmente feas pero asombrosas con él). Otra respuesta aquí menciona Trove, que aparentemente está en la categoría más limitada pero aún muy útil.

Los macro procesadores realmente generales (como M4) manipulan solo texto; eso los hace poderosos, pero no maneje bien la estructura del lenguaje de programación, y es realmente incómodo escribir un generaor en un procesador 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 son "plug this string into this string template" y por lo tanto no pueden hacer ninguna optimización de un resultado generado. Si desea la generación de código arbitrario y alto rendimiento para arrancar, necesita algo que sea capaz de Turing, pero entiende la estructura de la código generado para que pueda manipularlo fácilmente (por ejemplo, optimizarlo).

Esta herramienta se denomina Sistema de Transformación de Programas. Tal herramienta analiza el texto fuente como lo hace un compilador, y luego lleva análisis / transformaciones en él para lograr el efecto deseado. Si puede poner marcadores en el texto fuente de su programa (por ejemplo, comentarios estructurados o anotaciones en idiomas que los tienen) dirigiendo la herramienta de transformación del programa qué hacer, entonces puede usarla para llevar a cabo tal creación de instancias de abstracción, generación de código y / o optimización de código. (La sugerencia de un poster de conectarse al compilador Java es una variación de esta idea). El uso de un sistema general de transformación puprose (como DMS Software Reengineering Tookit significa que puede hacer esto para esencialmente cualquier lenguaje.

 2
Author: Ira Baxter,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2010-03-08 15:47:19

Una gran cantidad de este tipo de repetición ahora se puede evitar gracias a los genéricos. Son un regalo del cielo cuando se escribe el mismo código donde solo cambian los tipos.

Lamentablemente, creo que los arrays genéricos todavía no están muy bien soportados. Por ahora, al menos, utilice contenedores que le permitan aprovechar los genéricos. El polimorfismo también es una herramienta útil para reducir este tipo de duplicación de código.

Para responder a su pregunta sobre cómo manejar el código que absolutamente debe duplicarse... Etiqueta cada uno instancia con comentarios fácilmente buscables. Hay algunos preprocesadores java por ahí, que añadir macros de estilo C. Creo que recuerdo a Netbeans teniendo uno.

 1
Author: patros,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2010-02-25 20:08:29