В компьютерном программировании дублирующий код — это последовательность исходного кода , которая встречается более одного раза, либо в одной программе, либо в разных программах, принадлежащих или поддерживаемых одной и той же организацией. Дублирующий код обычно считается нежелательным по ряду причин. [1] Минимальное требование обычно применяется к количеству кода, которое должно появиться в последовательности, чтобы она считалась дублирующей, а не случайно похожей. Последовательности дублирующего кода иногда называют клонами кода или просто клонами, автоматизированный процесс поиска дубликатов в исходном коде называется обнаружением клонов.
Две последовательности кода могут быть дубликатами друг друга, не будучи идентичными посимвольно, например, будучи идентичными посимвольно только тогда, когда игнорируются пробельные символы и комментарии, или будучи идентичными посимвольно или посимвольно с редкими вариациями. Даже последовательности кода, которые идентичны только функционально, могут считаться дублирующим кодом.
Вот некоторые способы создания дублирующего кода:
Также может случиться, что требуется функциональность, очень похожая на ту, что есть в другой части программы, и разработчик независимо пишет код, очень похожий на то, что существует в другом месте. Исследования показывают, что такой независимо переписанный код обычно не является синтаксически похожим. [2]
Автоматически сгенерированный код, где дублирование кода может быть желательно для увеличения скорости или простоты разработки, является еще одной причиной дублирования. Обратите внимание, что фактический генератор не будет содержать дубликатов в своем исходном коде, только в выводе, который он производит.
Дублирующий код чаще всего устраняется путем перемещения кода в его собственный блок ( функцию или модуль) и вызова этого блока из всех мест, где он изначально использовался. Использование более открытого стиля разработки, в котором компоненты находятся в централизованных местах, также может помочь с дублированием.
Код, включающий дублирующиеся функции, сложнее поддерживать, поскольку:
С другой стороны, если одна копия кода используется для разных целей и не документирована должным образом, существует опасность того, что она будет обновлена для одной цели, но это обновление не будет необходимым или соответствующим для других ее целей.
Эти соображения не актуальны для автоматически сгенерированного кода, если в исходном коде имеется только одна копия функциональности.
Раньше, когда объем памяти был более ограничен, дублирующийся код имел дополнительный недостаток, занимая больше места, но сегодня это вряд ли станет проблемой.
Когда код с уязвимостью программного обеспечения копируется, уязвимость может продолжать существовать в скопированном коде, если разработчик не знает о таких копиях. [3] Рефакторинг дублирующегося кода может улучшить многие метрики программного обеспечения, такие как строки кода , цикломатическая сложность и связанность . Это может привести к сокращению времени компиляции, снижению когнитивной нагрузки , уменьшению человеческих ошибок и уменьшению количества забытых или упущенных фрагментов кода. Однако не все дублирование кода можно рефакторить. [4] Клоны могут быть наиболее эффективным решением, если язык программирования предоставляет неадекватные или слишком сложные абстракции, особенно если они поддерживаются такими методами пользовательского интерфейса, как одновременное редактирование . Кроме того, риски поломки кода при рефакторинге могут перевесить любые преимущества обслуживания. [5] Исследование Вагнера, Абдулхалека и Кайи пришло к выводу, что, хотя необходимо проделать дополнительную работу для синхронизации дубликатов, если вовлеченные программисты знают о дублирующем коде, не было вызвано значительно большего количества ошибок, чем в недублированном коде. [6] [ оспаривается – обсудить ]
Для обнаружения дублирующего кода было предложено несколько различных алгоритмов. Например:
Рассмотрим следующий фрагмент кода для вычисления среднего значения массива целых чисел .
extern int array_a []; extern int array_b []; int sum_a = 0 ; для ( int i = 0 ; i < 4 ; i ++ ) sum_a += array_a [ i ]; int среднее_a = сумма_a / 4 ; int сумма_b = 0 ; для ( int i = 0 ; i < 4 ; i ++ ) sum_b += array_b [ i ]; int среднее_b = сумма_b / 4 ;
Два цикла можно переписать как одну функцию:
int calc_average_of_four ( int * array ) { int sum = 0 ; for ( int i = 0 ; i < 4 ; i ++ ) sum += array [ i ]; вернуть сумму / 4 ; }
или, что обычно предпочтительнее, параметризацией количества элементов в массиве.
Использование указанной выше функции даст исходный код, не имеющий дублирования циклов:
внешний int массив1 []; внешний int массив2 []; int среднее1 = calc_average_of_four ( array1 ); int среднее2 = calc_average_of_four ( array2 );
Обратите внимание, что в этом тривиальном случае компилятор может выбрать встраивание обоих вызовов функции, так что результирующий машинный код будет идентичен для обоих примеров, дублированных и недублированных, выше. Если функция не встраивается, то дополнительные накладные расходы вызовов функций , вероятно, займут больше времени для выполнения (порядка 10 инструкций процессора для большинства высокопроизводительных языков). Теоретически, это дополнительное время выполнения может иметь значение.