Авторская (единоличная) разработка достаточно большого и сложного программного продукта практически неосуществима. Рано или поздно возникает необходимость разделить, распараллелить часть задач между исполнителями, чтобы эффективно и своевременно развивать и поддерживать продукт. Однако увеличение числа исполнителей вносит ряд определённых проблем. Как правило, разработчики слабо взаимодействуют друг с другом при выполнении мелких повседневных задач — необходимые им функции они могут реализовывать независимо от остальной команды. Таким образом, возникают ситуации, когда один и тот же по назначению программный код присутствует в проекте несколько раз, то есть дублируется.
Какой именно код считать дублирующимся? Есть несколько вариантов ответов на этот вопрос. Согласно Бакстер (Baxter) [3] дубликаты кода, называемые в её работе клонами (software clones), это «фрагменты программы, идентичные другим фрагментам». Кринке (Krinke) использует для этого термин «сходный код» (similarcode); Дукасси (Ducasse) говорит о дублирующемся коде (duplicatedcode), Комондор (Komondoor) и Хорвиц (Horwitz) также используют термин «дублирующийся код» (duplicatedcode), а клонами (clones) они называют отдельные экземпляры дубликатов кода.
В данной работе понятия «дублирующийся код», «дубликаты» и «клоны» будут использоваться в качестве синонимов для описания явления, когда одни фрагменты исходного кода программы идентичны или подобны (визуально или функционально) другим фрагментам этой же программы.
Дублирование программного кода имеет несколько форм.
- Текстовое дублирование. Этот вид, пожалуй, наиболее простой и легко находимый из остальных. Он проявляется, когда в программе есть секции кода, которые идентичны или очень похожи. Есть несколько способов избежать этого вида дублирования, включая: выделение методов (extracting methods) и повторное их использование (reusing), превращение метода в шаблонный метод (template method) и другие базовые виды рефакторинга;
- Функциональное дублирование. Это особый вид текстового дублирования, когда в исходном коде есть различные функции или методы, предназначенные для одного и того же. Его можно часто встретить в проектах, которые разделены между группами разработчиков, недостаточно хорошо взаимодействующих друг с другом;
- Временное дублирование. Оно немного более трудное, менее ясное, и не так легко обнаруживаемое. Временное дублирование наблюдается, когда при выполнении программы одна и та же работа делается повторно — в то время, когда так не должно быть. Это не проблема в том же понимании, как предыдущие две формы дублирования, так как это не приводит к наличию лишнего кода. Но это может стать проблемой при масштабировании системы, когда повторяющаяся работа начинает отнимать много времени.
В дальнейшем мы будем рассматривать только текстовое и, отчасти, функциональное дублирование.
В литературе приводится следующая типизация клонов (табл.1.1).
Название типа | Описание |
Тип 1 | Абсолютно идентичный фрагмент кода, не содержащий никаких различий |
Тип 2 | Идентичный фрагмент кода, но отформатированный, содержащий комментарии, или изменённые имена переменных |
Тип 3 | Функционально идентичный фрагмент кода с небольшими изменениями, направленными на реализацию некоторых новых возможностей |
Тип 4 | Функционально идентичный фрагмент кода, вероятно созданный автором, не подозревающим о существовании оригинального кода |
Пример клонов 2-го типа в АБС RS-Bank\Pervasive:
RS-Bank5.50.001.30.10\BFCLN.ALL\dub_clnt.c
// функция высвобождения
static void CL_ClearFunc(void * item)
{
if (item) free (item);
}
RS-Bank5.50.001.30.10\BFCLN.ALL\dub_grp.c
// функция высвобождения
void DBGRP_ClearFunc(void * item)
{
if (item) free (item);
}
Пример клонов 3-го типа в ядре Linux
File: linux-2.6.19/drivers/scsi/arm/eesox.c
407: if (length >= 9 && strncmp (buffer, "EESOXSCSI", 9) == 0) {
408: buffer += 9;
409: length -= 9;
410:
411: if (length >= 5 && strncmp(buffer, "term=", 5) == 0) {
……
418: } else
419: ret = -EINVAL;
420: } else
421: ret = -EINVAL;
File: linux-2.6.19/drivers/scsi/arm/cumana 2.c
322: if (length >= 11 && strcmp (buffer, "CUMANASCSI2") == 0) {
323: buffer += 11;
324: length -= 11;
325:
326: if (length >= 5 && strncmp(buffer, "term=", 5) == 0) {
……
333: } else
334: ret = -EINVAL;
335: } else
336: ret = -EINVAL;
Пример клонов 4-го типа в ядре Linux
File: linux-2.6.19/drivers/cdrom/sbpcd.c
4859: if (cmd_type==READ_M2)
4860: {
4861: for (xa_count=0;xa_count<CD_XA_HEAD;
xa_count++)
4862: sprintf(&msgbuf[xa_count*3], " %02X",
…);
4863: msgbuf[xa_count*3]=0;
4864: msg(DBG_XA1,"xa head:%s\n", msgbuf);
4865: }
File: linux-2.6.19/drivers/cdrom/sbpcd.c
2386: for (i=0;i<response_count;i++)
2387: {
2388: sprintf(&msgbuf[i*3], " %02X", …);
2389: msgbuf[i*3]=0;
2390: msg(DBG_SQ1,"cc_ReadSubQ:%s\n",
msgbuf);
2391: }
«Двойной» код (duplicate code) может появляться и другими способами, например, когда программист берёт уже имеющийся код, копирует и видоизменяет его, достигая нужных целей. Повторное использование кода посредством копирования и вставки (copy/paste) является широко распространенной практикой программирования во избежание нежелательных эффектов, когда программист не хочет (или не может) предвидеть всех последствий от модификации имеющегося кода. Существенную роль здесь играют и текстовые редакторы, которые традиционно содержат функции для копирования и вставки текста. В больших системах такой подход вполне может стать стандартным способом построения новых модулей. Однако, как показано в [2] и [3], такой подход является выигрышным лишь в ближайшем рассмотрении. Все преимущества данной стратегии исчезают, когда возникает необходимость внесения изменений во все многочисленные копии, если оригинальный код требует правки или изменения. В некоторых случаях удаление клонов становится затруднительным (или даже невозможным) из-за связанных рисков, поскольку сложно предсказать, как это скажется на качестве программного продукта.
В качестве примера в [3] приводится разработка драйверов устройств для различных операционных систем, когда значительная часть кода является, по сути, шаблоном, и только небольшая его часть, непосредственно взаимодействующая с аппаратной частью устройства, нуждается в изменении при переходе от одной системы к другой. Как ни удивительно, но эта, казалось бы, хорошая практика повторного использования кода, только усугубляет проблемы технической поддержки программного обеспечения, поскольку впоследствии ошибки, обнаруженные в «проверенной» части кода, необходимо исправлять и во всех новых версиях драйвера.
Помимо вышеуказанных причин появления дубликатов кода в [3] приводятся:
- Стиль программирования;
- Реализация точных вычислений;
- Неспособность выделять (или использовать) абстрактные типы данных;
- Повышение производительности;
- Случайные ситуации (accidents).
Существование некоторых клонов оправдано соображениями производительности. Системы с жесткими временными ограничениями иногда оптимизируются вручную путём дублирования часто вызываемого кода, особенно когда компилятор не предлагает встраивания (in-lining) случайных выражений или вычислений. [2,3]
И, наконец, бывают ситуации, когда фрагменты кода случайно совпадают, хотя на самом деле не являются клонами. Глубокий анализ позволяет установить, что эти видимые дубликаты (apparent clones) на самом деле выполняют различные вычисления. К счастью, при увеличении размера фрагментов кода, количество таких мнимых клонов (accidental clones) значительно сокращается.
За исключением случайных клонов (accidental clones), присутствие дубликатов в коде приводит к необоснованному увеличению его объёма, что в свою очередь вынуждает программистов контролировать и отлаживать больше кода, чем нужно. Кроме того, возрастает время на компиляцию проекта. Следствием этого становятся увеличивающиеся затраты на поддержку и развитие программного продукта, и в целом, как правило, суммарное снижение его качества.
По разным оценкам [1], количество дубликатов в больших проектах варьируется в пределах от 5% до 50%. В связи с этим возникает задача своевременного обнаружения дублирующегося кода (detecting duplicated code) и его последующего удаления. На настоящий момент предложен ряд методов для выявления клонов, а часть их них получила практическую реализацию. Их рассмотрение и сравнительный анализ станут следующим этапом работы.