Управление очень повторяющимся кодом и документацией в 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 fromIndex, inclusive, to index
 * toIndex, exclusive.  (If fromIndex==toIndex, 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 fromIndex > toIndex
 * @throws ArrayIndexOutOfBoundsException if fromIndex < 0 or
 *         toIndex > a.length
 */
public static void fill(long[] a, int fromIndex, int toIndex, long val) {
    rangeCheck(a.length, fromIndex, toIndex);
    for (int i=fromIndex; i

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

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

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

Блог Исследования Google - Дополнительный, Дополнительный - Чтение Все Об Этом: Почти Все Двоичные поиски и Сортировки с объединением Повреждаются (Joshua Bloch, Разработчиком программного обеспечения)

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

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

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

Таким образом, мои вопросы:

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

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

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

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

70
задан polygenelubricants 2 March 2010 в 01:08
поделиться

9 ответов

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

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

Есть причина, по которой Trove TIntIntHashMap обходят Java HashMap при работе с аналогичным объемом данных.

Как теперь пишется исходный код коллекции 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
 * 
 */
32
ответ дан 24 November 2019 в 13:31
поделиться

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

16
ответ дан 24 November 2019 в 13:31
поделиться

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

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

2
ответ дан 24 November 2019 в 13:31
поделиться

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

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

2
ответ дан 24 November 2019 в 13:31
поделиться

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

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

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

1
ответ дан 24 November 2019 в 13:31
поделиться

Из Википедии Don't Repeat Yourself (DRY) or Duplication is Evil (DIE)

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

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

6
ответ дан 24 November 2019 в 13:31
поделиться

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

2
ответ дан 24 November 2019 в 13:31
поделиться

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

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

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

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

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

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

2
ответ дан 24 November 2019 в 13:31
поделиться

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

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

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

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

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

4
ответ дан 24 November 2019 в 13:31
поделиться
Другие вопросы по тегам:

Похожие вопросы: