Подтвердить что ты не робот

Управление очень повторяющимся кодом и документацией в Java

Очень повторяющийся код, как правило, плохой, и есть шаблоны проектирования, которые могут помочь свести это к минимуму. Однако иногда это просто неизбежно из-за ограничений самого языка. Возьмите следующий пример из 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;
}

Вышеприведенный фрагмент кода появляется в исходном коде 8 раз с очень небольшим изменением в сигнатуре документа/метода, но точно таким же телом метода, по одному для каждого из типов корневого массива int[], short[], char[], byte[], boolean[], double[], float[] и Object[].

Я считаю, что если кто-то прибегает к размышлению (что само по себе совершенно другое), это повторение неизбежно. Я понимаю, что в качестве класса полезности такая высокая концентрация повторяющегося кода Java очень нетипична, но даже с лучшей практикой повторение происходит! Рефакторинг не всегда работает, потому что это не всегда возможно (очевидный случай, когда повторение содержится в документации).

Очевидно, что поддерживать этот исходный код - это кошмар. Небольшая опечатка в документации или незначительная ошибка в реализации умножается на количество повторений. Фактически, лучший пример включает этот точный класс:

Блог Google Research - Extra, Extra - Читать все об этом: почти все двоичные поиски и слияния (Broker) (Джошуа Блох, инженер-программист)

Ошибка является удивительно тонкой, что происходит в том, что многие считают простым и простым алгоритмом.

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

Вышеприведенная строка появляется 11 раз в исходном коде!

Итак, мои вопросы:

  • Как эти повторяющиеся Java-код/​​документация обрабатываются на практике? Как они разрабатываются, поддерживаются и тестируются?
    • Вы начинаете с "оригинала" и делаете его как можно более зрелым, а затем копируете и вставляете по мере необходимости и надеетесь, что не допустили ошибку?
    • И если вы допустили ошибку в оригинале, тогда просто исправьте ее повсюду, если вам не удобно удалять копии и повторять весь процесс репликации?
    • И вы применяете этот же процесс для тестового кода?
  • Может ли Java использовать некоторую предварительную обработку исходного кода с ограниченным использованием для такого рода вещей?
    • Возможно, у Sun есть свой препроцессор, который помогает писать, поддерживать, документировать и тестировать этот повторяющийся код библиотеки?

Комментарий запросил другой пример, поэтому я вытащил его из Google Collections: com.google.common.base.Predicates строки 276-310 (AndPredicate) по линиям 312-346 (OrPredicate).

Источник для этих двух классов идентичен, за исключением:

  • AndPredicate vs OrPredicate (каждый из них появляется 5 раз в своем классе)
  • "And(" vs Or(" (в соответствующих методах toString())
  • #and vs #or (в комментариях @see Javadoc)
  • true vs falseapply; ! можно переписать из выражения)
  • -1 /* all bits on */ vs 0 /* all bits off */ in hashCode()
  • &= vs |= in hashCode()
4b9b3361

Ответ 1

Для людей, которые абсолютно нуждаются в производительности, боксе, распаковке и генерируемых коллекциях, а также о том, что у них нет больших проблем.

Такая же проблема возникает при вычислении производительности, где вам нужен один и тот же комплекс для работы как для float, так и для double (скажем, какой-то метод, показанный в Goldberd " Что каждый компьютерный ученый должен знать о номерах с плавающей запятой).

Есть причина, по которой Trove TIntIntHashMap запускает круги вокруг Java HashMap<Integer,Integer> при работе с подобным количеством данных.

Теперь как написано исходный код коллекции Trove?

Конечно, с помощью инструментария с исходным кодом:)

Существует несколько библиотек Java для более высокой производительности (намного выше, чем у Java по умолчанию), которые используют генераторы кода для создания повторяющегося исходного кода.

Мы все знаем, что "инструментарий с исходным кодом" - это зло, а генерация кода - дерьмо, но все же, что люди, которые действительно знают, что делают (то есть люди, которые пишут такие вещи, как Trove), делают это:)

Для чего стоит генерировать исходный код, содержащий большие предупреждения вроде:

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

Ответ 2

Если вы абсолютно должны дублировать код, следуйте отличным примерам, которые вы указали, и сгруппируйте весь этот код в одном месте, где его легко найти и исправить, когда вам нужно внести изменения. Документируйте дублирование и, что более важно, причину дублирования, чтобы все, кто приходит после вас, знали обо всех.

Ответ 3

Из Wikipedia Не повторяйте себя (DRY) или Duplication is Evil (DIE)

В некоторых контекстах усилия, необходимые для обеспечения соблюдения DRY-философии, могут быть больше, чем усилия по сохранению отдельных копий данных. В некоторых других контекстах дублируемая информация является неизменной или хранится под контролем, достаточным для того, чтобы DRY не требовался.

Вероятно, нет ответа или техники для предотвращения подобных проблем.

Ответ 4

Даже у странных языков штанов, таких как Haskell, есть повторяющийся код (см. мой пост по haskell и сериализации)

Кажется, есть три варианта этой проблемы:

  • Использовать отражение и потерять производительность.
  • Используйте предварительную обработку, например, Template Haskell или Caml4p, эквивалентную вашему языку, и живите с nastiness.
  • Или мои личные любимые макросы использования, если ваш язык поддерживает его (схема и lisp)

Я рассматриваю макросы, отличные от предварительной обработки, потому что макросы обычно находятся на том же языке, что и цель, поскольку предварительная обработка - это другой язык.

Я думаю, что макросы Lisp/Scheme решат многие из этих проблем.

Ответ 5

Я получаю, что Sun должен документировать, как это для кода библиотеки Java SE, и, возможно, другие сторонние разработчики библиотек.

Однако, я думаю, что это полная потеря для копирования и вставки документации по всему файлу, подобному этому, в коде, который используется только в доме. Я знаю, что многие люди не согласятся, потому что это сделает их дома JavaDocs менее чистыми. Однако компромисс заключается в том, что их код более чистый, что, на мой взгляд, более важно.

Ответ 6

Явные примитивные типы Java висят, особенно когда речь идет о массивах. Если вы конкретно спрашиваете о коде, в котором используются примитивные типы, я бы сказал, просто попробуйте их избежать. Метод Object [] достаточен, если вы используете типы в штучной упаковке.

В общем, вам нужно много модульных тестов, и на самом деле не нужно ничего делать, кроме как прибегать к размышлениям. Как вы сказали, это еще одна тема целиком, но не слишком бойтесь отражения. Сначала напишите DRYest-код, затем профайлируйте его и определите, действительно ли показатель производительности отражения достаточно плох, чтобы гарантировать запись и поддержание дополнительного кода.

Ответ 7

Вы можете использовать генератор кода для создания вариантов кода с использованием шаблона. В этом случае источник java является продуктом генератора, а реальным кодом является шаблон.

Ответ 8

Учитывая два фрагмента кода, которые, как утверждается, похожи, большинство языков имеют ограниченные возможности для построения абстракций, которые унифицируют фрагменты кода в монолит. Чтобы абстрагироваться, когда ваш язык не может этого сделать, вы должны выйти за пределы языка: - {

Самый общий механизм "абстракции" - это полный макропроцессор, который может применять произвольные вычисления к "телу макроса" при его создании (подумайте Сообщение или строка -отражающая система, которая поддерживает Тьюринга). M4 и GPM являются типичными примерами. Препроцессор C не является одним из них.

Если у вас есть такой макропроцессор, вы можете построить "абстракцию" в качестве макроса и запустить макропроцессор в своем "абстрактном" исходном тексте, чтобы создать фактический исходный код, который вы компилируете и выполняете.

Вы также можете использовать более ограниченные версии идей, которые часто называются "генераторами кода". Обычно они не способны к Тьюрингу, но во многих случаях они работают достаточно хорошо. Это зависит от того, насколько сложна ваша "макро-инстанцирование". (Причина, по которой люди влюблены в механизм шаблонов С++, несмотря на его уродство, это способность Тьюринга, и поэтому люди могут делать с ней действительно уродливые, но удивительные задачи генерации кода). В другом ответе здесь упоминается Trove, что является гарантией в более ограниченной, но все же очень полезной категории.

Действительно, общие макропроцессоры (например, M4) управляют только текстом; что делает их мощными, но они плохо обрабатывают структуру языка программирования, и действительно неудобно писать генератор в таком mcaro-процессоре, который может не только генерировать код, но и оптимизировать сгенерированный результат. Большинство генераторов кода, с которыми я сталкиваюсь, "подключают эту строку в этот шаблон строки" и поэтому не могут оптимизировать сгенерированный результат. Если вы хотите генерировать произвольный код и высокую производительность для загрузки, вам нужно что-то, что поддерживает Turing, но понимает структуру сгенерированного кода, чтобы он мог легко манипулировать (например, оптимизировать) его).

Такой инструмент называется Программа Transformation System. Такой инструмент анализирует исходный текст так же, как это делает компилятор, а затем переносит на него анализы/преобразования для достижения желаемого эффекта. Если вы можете поместить маркеры в исходный текст вашей программы (например, структурированные комментарии или аннотации в langauges, которые есть у них), направляя программу transformaiton tool, что делать, тогда вы можете использовать ее для выполнения такого создания абстракции, генерации кода и/или оптимизации кода. (Одно предложение о подключении к компилятору Java - это вариант этой идеи). Используя общую систему трансформации puprose (например, DMS Software Reengineering Tookit, вы можете сделать это практически для любого языка.

Ответ 9

Многие подобные повторения теперь можно избежать благодаря дженерикам. Они - находка при написании того же кода, где изменяются только типы.

К сожалению, я думаю, что общие массивы все еще не очень хорошо поддерживаются. На данный момент, по крайней мере, используйте контейнеры, которые позволят вам воспользоваться преимуществами дженериков. Полиморфизм также является полезным инструментом для сокращения такого рода дублирования кода.

Чтобы ответить на ваш вопрос о том, как обрабатывать код, который должен быть полностью дублирован... Пометьте каждый экземпляр с помощью легкодоступных для поиска комментариев. Есть некоторые препроцессоры Java, которые добавляют макросы C-стиля. Я думаю, что я помню, что у netbeans есть один.