Управление очень повторяющимся кодом и документацией на Java - PullRequest
70 голосов
/ 25 февраля 2010

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

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

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

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

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

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

В комментарии был запрошен еще один пример, поэтому я извлек его из коллекции Google: com.google.common.base.Predicates строки 276-310 (AndPredicate) против строк 312-346 (* 1058) *).

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

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

Ответы [ 9 ]

32 голосов
/ 26 февраля 2010

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

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

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

Теперь, как пишется исходный код коллекции 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
 * 
 */
16 голосов
/ 25 февраля 2010

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

6 голосов
/ 25 февраля 2010

С Википедия Не повторяйте себя (СУХОЙ) или Дублирование - зло (УМИРАЕТ)

В некоторых контекстах усилия, необходимые для реализации философии СУХОЙ, могут быть больше, чем попытки сохранить отдельные копии данных. В некоторых других контекстах дублирующаяся информация является неизменной или находится под достаточно строгим контролем, чтобы исключить СУХОЙ.

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

4 голосов
/ 28 апреля 2010

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

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

  1. Используйте рефлексию и теряйте производительность
  2. Используйте предварительную обработку, такую ​​как шаблон Haskell или эквивалент Caml4p для вашего языка, и живите с мерзостью
  3. Или мои личные любимые макросы использования, если ваш язык поддерживает это (схема и шрифт)

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

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

2 голосов
/ 06 марта 2010

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

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

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

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

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

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

2 голосов
/ 25 февраля 2010

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

2 голосов
/ 25 февраля 2010

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

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

2 голосов
/ 25 февраля 2010

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

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

1 голос
/ 25 февраля 2010

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

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

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

...