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

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

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

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

Плагиат править

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

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

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

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

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

Дублирование править

 
Случайно забытые исправления — одна из наиболее неприятных и распространённых проблем при программировании методом копирования-вставки

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

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

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

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

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

Ветвление править

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

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

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

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

Недостатки:

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

Еще одной альтернативой C&P-подходу является модульный подход:

  • Изначально в библиотеки или модули выносится код, который будет общим для обоих продуктов;
  • Использование созданных библиотек является основой для разработки нового продукта;
  • Если предусматривается существование третьей, четвертой, пятой и т. п. производной версии продукта, то такой подход гораздо сильнее копирования-вставки, поскольку резко сокращает цикл разработки любого дополнительного продукта после второго[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";

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

См. также править

Примечания править

Литература править