Программирование методом копирования-вставки
Программи́рование ме́тодом копи́рования-вста́вки, C&P-программирование или копипаста в программировании — процесс создания программного кода с часто повторяющимися частями, произведёнными операциями копировать-вставить (англ. copy-paste)[1][2]. Обычно этот термин используется в уничижительном понимании для обозначения недостаточных навыков компьютерного программирования или отсутствия выразительной среды разработки, в которой, как правило, можно использовать подключаемые библиотеки.
Программирование методом копирования-вставки — распространённый антипаттерн, приводящий к появлению дублированного кода, обычно большого и сложного для восприятия. Повторяемые фрагменты кода размножают ошибку, допущенную в оригинальном коде, а многократные повторы усложняют исправление этой ошибки в копиях[1][3].
Существуют случаи, когда копипаста в программировании может быть приемлема или необходима: шаблоны, размотка цикла (когда нет автоматической поддержки компилятором), а также в случае применения некоторых парадигм программирования или в случае поддержки редакторами исходного кода в виде сниппетов.
Плагиат
Копирование-вставка часто используется неопытным или начинающим программистом, который находит сложным путь написания кода с нуля и предпочитает искать написанные ранее решения или частичные решения, которые можно использовать как основу для решения своей проблемы[4].
Программисты, которые часто копируют чужой программный код, зачастую не понимают его частично или полностью. Как таковая, проблема возникает больше из-за их неопытности и отсутствия настойчивости, чем от самого факта копирования. Скопированный код часто берется от друзей, коллег, с интернет-форумов, от преподавателей или из книг по программированию. Результат рискует быть несвязным набором стилей и может содержать избыточный код, решающий уже несуществующие проблемы.
Существует некоторое различие между программированием методом копировать-вставить и программированием типа карго-культ. Под первым больше подразумевают сам факт многократного дублирования частей кода программы[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. Следует отметить, что в этой модели, копипаста — основная модель взаимодействия и, следовательно, не рассматривается как антипаттерн.
Примечания
- Ethnographic Study of Copy and Paste.
- Tool for Tracking Copy-and-Paste Code.
- Survey on Software Clone Detection.
- Revisiting Novice Programmers Errors.
- Integrating Antipatterns.
- Cargo Cults in Java.
- Building ASP.NET.
- Типичные ошибки проектирования, 2003.
- Principles of Object-Oriented Programming.
- Code Reuse In OO Development.
- The Benefits of Coding Standards.
- CS 106X.
- Совершенный код, 2005.
- Карпов, 2011.
- Карпов, 2014.
- Subtext.
Литература
- Miryung Kim; Lawrence Bergman, Tessa Lau, David Notkin. Ethnographic Study of Copy and Paste Programming Practices in OOPL (англ.) (PDF) (2004). doi:10.1109/ISESE.2004.1334896. Дата обращения: 3 ноября 2013.
- Patricia Jablonski, Daqing Hou. CReN: A Tool for Tracking Copy-and-Paste Code Clones and Renaming Identifiers Consistently in the IDE (англ.) (PDF). Нью-Йорк, США: ACM New York (2005). doi:10.1145/1328279.1328283. Дата обращения: 3 ноября 2013.
- Chanchal Kumar Roy, James R. Cordy. A Survey on Software Clone Detection Research (англ.). Онтарио, Канада: Queen’s University, Kingston (26 сентября 2007). Дата обращения: 3 ноября 2013.
- Gavriel Yarmish. Danny Kopec. Revisiting Novice Programmers Errors (англ.) (PDF). Нью-Йорк, США: ACM New York (2007). doi:10.1145/1272848.1272896. Дата обращения: 4 ноября 2013.
- Jason Rogers, Chuck Pheatt. Integrating Antipatterns into the Computer Science Curriculum (англ.). Journal of Computing Sciences in Colleges С. 187. США: Consortium for Computing Sciences in Colleges (Май 2009). Дата обращения: 4 ноября 2013.
- Gordon Fletcher. Cargo Cults in Java (англ.) С. 3. Великобритания: University of Salford (2004). Дата обращения: 4 ноября 2011.
- Robert Pittenger. Building ASP.NET Web Pages Dynamically in the Code-Behind (англ.). codeproject.com (6 мая 2008). Дата обращения: 4 ноября 2013.
- Raymond Wallen. 4 major principles of Object-Oriented Programming (англ.) (недоступная ссылка). codebetter.com (19 июля 2005). Дата обращения: 4 ноября 2013. Архивировано 12 декабря 2013 года.
- Lisa Wold Eriksen. Code Reuse In Object Oriented Software Development (англ.). Norwegian University of Science and Technology, Department of Computer and Information Science (2004). Дата обращения: 4 ноября 2011. (недоступная ссылка)
- The Benefits of Coding Standards, Richard Sharpe. The Benefits of Coding Standards (англ.). JAX Magazine. Дата обращения: 6 января 2017. Архивировано 6 октября 2008 года.
- Stanford University, CS 106X ("Programming Abstractions") Course Handout: "Decomposition" . Stanford University (18 января 2008). Дата обращения: 6 января 2017. Архивировано 16 мая 2008 года.
- Андрей Карпов. Последствия использования технологии Copy-Paste при программировании на Си++ и как с этим быть . PVS-Studio, Статический анализатор кода для C/C++/C++11 (24 января 2011). Дата обращения: 3 ноября 2013.
- Андрей Карпов. Эффект последней строки . PVS-Studio, Статический анализатор кода для C/C++/C++11 (31 мая 2014). Дата обращения: 13 сентября 2014.
- Jonathan Edwards. Subtext: Uncovering the Simplicity of Programming (англ.) (PDF). MIT CSAIL (2005). Дата обращения: 3 ноября 2013.
- Макконнелл С. Глава 24. Рефакторинг // Совершенный код. Мастер-класс = Code Complete / Под ред. В. Г. Вшивцева. — 2-е изд. — СПб.: Русская редакция, Питер, 2005. — С. 553. — 896 с. — ISBN 5-7502-0064-7. Архивная копия от 15 декабря 2013 на Wayback Machine
- Аллен Э. Глава 7. Фальшивая черепица // Типичные ошибки проектирования. Библиотека программиста = Bug Patterns in Java. — 1-е изд. — СПб.: Питер, 2003. — С. 73—82. — 224 с. — ISBN 5-88782-304-6. Архивная копия от 12 декабря 2013 на Wayback Machine