Программирование методом копирования-вставки
Программирование методом копирования-вставки — процесс создания программного кода с часто повторяющимися частями, произведёнными операциями копировать-вставить[1][2]. Обычно этот термин используется в уничижительном значении для обозначения недостаточных навыков программирования либо отсутствия выразительной среды разработки, где обычно возможно использовать подключаемые библиотеки.
Программирование методом копирования-вставки — распространённый антипаттерн, приводящий к появлению дублированного кода, зачастую большого объёма и сложного для чтения. Повторяемые фрагменты кода размножают ошибку, допущенную в оригинальном коде, а многочисленные повторы усложняют исправление этой ошибки во всех копиях[1][3].
Существуют ситуации, когда копипаста в программировании может быть оправдана или необходима: шаблоны, размотка цикла, а также при использовании некоторых парадигм программирования или работе в редакторе исходного кода с сниппетами.
Плагиат
Копирование-вставка часто используется неопытными или начинающими программистами, которые затрудняются писать код с нуля и предпочитают искать ранее реализованные решения или их части и использовать их как основу для своей задачи[4].
Программисты, часто копирующие чужой код, могут не понимать его полностью или частично. Проблема возникает скорее из-за их неопытности и недостатка настойчивости, чем из-за самого факта копирования. Код берётся у знакомых, коллег, с интернет-форумов, от преподавателей или из книг по программированию. Результат может быть несвязным набором стилей и содержать ненужный код для уже неактуальных проблем.
Существует различие между программированием методом копировать-вставить и программированием типа карго-культ. В первом случае речь идёт о множественном дублировании фрагментов программы[5], тогда как во втором — как копирование решения задачи без осмысления принципов работы кода, так и копирование частей кода без необходимости[5][6].
Дополнительная проблема — возможное копирование уже содержащихся в коде ошибок. Приёмы проектирования, использованные в разных исходных кодах, могут быть неприемлемы при объединении в новой среде.
Такой код случайно может стать обфусцированным, так как имена переменных, классов, функций и т. п. после копирования, как правило, не меняются, даже если их смысл в новом контексте другой[4].
Дублирование
Будучи разновидностью дублирования кода, 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]: в этой модели копипаста — основной способ работы и потому не считается антипаттерном.
Примечания
Литература
- 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. Рефакторинг // Совершенный код. Мастер-класс / Под ред. В. Г. Вшивцева. — 2-е изд. — СПб. : Питер, 2005. — P. 553. — ISBN 5-7502-0064-7.
- Аллен, Эрик. Глава 7. Фальшивая черепица // Типичные ошибки проектирования. Библиотека программиста. — 1-е изд. — СПб. : Питер, 2003. — P. 73—82. — ISBN 5-88782-304-6.