Программирование методом копирования-вставки

Программирование методом копирования-вставки — процесс создания программного кода с часто повторяющимися частями, произведёнными операциями копировать-вставить[1][2]. Обычно этот термин используется в уничижительном значении для обозначения недостаточных навыков программирования либо отсутствия выразительной среды разработки, где обычно возможно использовать подключаемые библиотеки.

Программирование методом копирования-вставки — распространённый антипаттерн, приводящий к появлению дублированного кода, зачастую большого объёма и сложного для чтения. Повторяемые фрагменты кода размножают ошибку, допущенную в оригинальном коде, а многочисленные повторы усложняют исправление этой ошибки во всех копиях[1][3].

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

Плагиат

Копирование-вставка часто используется неопытными или начинающими программистами, которые затрудняются писать код с нуля и предпочитают искать ранее реализованные решения или их части и использовать их как основу для своей задачи[4].

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

Существует различие между программированием методом копировать-вставить и программированием типа карго-культ. В первом случае речь идёт о множественном дублировании фрагментов программы[5], тогда как во втором — как копирование решения задачи без осмысления принципов работы кода, так и копирование частей кода без необходимости[5][6].

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

Такой код случайно может стать обфусцированным, так как имена переменных, классов, функций и т. п. после копирования, как правило, не меняются, даже если их смысл в новом контексте другой[4].

Дублирование

undefined

Будучи разновидностью дублирования кода, C&P-программирование обладает рядом проблем, особенно если копия теряет связь с оригиналом. Тогда при необходимости изменений усилия тратятся на поиск всех дубликатов. Хорошие комментарии могут частично ускорить этот процесс, но не отменяют необходимости множества правок. Комментарии, описывающие, где искать дубликаты, быстро устаревают[7].

Эрик Аллен в книге «Типичные ошибки проектирования» называет ошибку программирования методом копирования термином «Фальшивая черепица». Вынесение дубликата в отдельный метод (основной «рецепт» решения проблемы) может оказаться непростой задачей[8].

Использование библиотек

Программирование методом копирования-вставки встречается и среди опытных разработчиков, имеющих библиотеки хорошо протестированных и готовых к применению сниппетов и общих алгоритмов, настраиваемых для конкретных задач[2].

Вместо создания множества клонированных вариантов обобщённого алгоритма объектно-ориентированное программирование предлагает абстрагировать алгоритм в инкапсулированный класс для повторного использования. Такой класс предоставляет возможность гибкого расширения посредством наследования и перегрузки, экономя усилия программиста, поскольку работать приходится только с одним исходным кодом[9]. С расширением функциональности увеличиваются и библиотеки, сохраняя обратную совместимость. При исправлении бага во внедрённом алгоритме все продукты, использующие эту библиотеку, автоматически получают исправление.

Ветвление

Ветвление — стандартная практика работы в больших командах разрабочиков, позволяющая вести параллельную разработку и сократить цикл вывода на рынок. Классическое ветвление имеет следующие черты:

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

Как способ выделения нового продукта, копипаста даёт следующие преимущества:

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

Недостатки:

  • Если новый и исходный продукт различаются меньше, чем ожидалось, может возникнуть необходимость поддерживать две базы кода (что удваивает издержки) для по сути схожих продуктов. Это приводит к большому объёму рефакторинга и ручному объединению в будущем;
  • Две кодовые базы увеличивают время внесения общих изменений, что способно нивелировать выигранное время. В реальности такой подход часто приводит к убыткам.

Ещё одна альтернатива — модульный подход:

  • Общий для нескольких продуктов код изначально выносится в библиотеки/модули;
  • Использование библиотеки становится основой для нового продукта;
  • Для множества производных продуктов такой подход даёт эффект увеличения темпа разработки после второго продукта[10].

Повторяющиеся задачи или вариации задачи

Особенно вредна C&P-практика, ведущая к появлению дублированного кода, выполняющего одну и ту же задачу с вариациями. Каждый экземпляр — это копия предыдущего с небольшими отличиями. В результате:

  • Копипаста приводит к разрастанию методов;
  • Каждый экземпляр — это отдельный дубликат с уже описанными выше проблемами, но зачастую в большем масштабе (десятки, а иногда сотни дубликатов). Исправление ошибок усложняется[11];
  • В подобном коде тяжело ориентироваться, различия между копиями сложно выявить, что усложняет внесение изменений;
  • Процедурное программирование настоятельно не рекомендует используемый здесь подход. Правильнее выносить повторяющуюся логику в функцию или подпрограмму, вызываемую в цикле. Такой подход называют «хорошо декомпозированным кодом», он проще для понимания и расширения[12];
  • Основное эмпирическое правило: «не повторяйся». Дэвид Парнас сформулировал его так: «Копирование и вставка кода — результат ошибки проектирования»[13].

Умышленный выбор подхода

Иногда копипаста в программировании используется сознательно, например, для шаблонов: объявления класса, подключения стандартных библиотек, или при копировании шаблона (с пустыми блоками или функциями-заглушками) в качестве основы для заполнения.

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

Пример

Пример корректного использования копипасты — шаблон цикла for:

for (int i=0; i!=n; ++i) {}

а также функция на его основе:

void foo(int n) {
    for (int i=0; i!=n; ++i) {
    }
}

Фрагмент цикла можно генерировать сниппетом, определяя тип переменных:

for ($type $loop_var = 0; $loop_var != $stop; ++$loop_var) {
}

Многие программисты дублируют похожие строки (например, вызов функции для двух объектов с похожими именами), потому что быстрее продублировать строку, чем написать новую. Однако вероятность ошибки — особенно в последней строке — не уменьшается[14], а часто даже возрастает[15].

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

mArray[12] = "a";
mArray[13] = "b";
mArray[14] = "c";
mArray[14] = "d";

Существуют исследования, оправдывающие копирование-вставку как модель взаимодействия — например, язык программирования Subtext[16]: в этой модели копипаста — основной способ работы и потому не считается антипаттерном.

Примечания

Литература