Последствия использованиятехнологии Copy-Paste припрограммировании на Си++ и как сэтим бытьАвтор: Андрей КарповДата: 21.01...
При этом здесь нет смысла говорить о дублировании кода. Как не думай, проще здесь написатьневозможно. Подобных примеров мо...
Дальше сценарий будет аналогичен. Программист пишет длинное условие в 3D SDK Crystal Space:inline_ bool Contains(const LSS...
{    ...    static EPixelFormat ms_pfList[] =     { PF_Lub, PF_Lus, PF_Li, PF_Lf, PF_Ld };    const int fsize = sizeof(ms_...
Даже программисты в компании Intel - тоже всего лишь программисты, а не полубоги. Неудачноекопирование в проекте TickerTap...
void RB_CalcColorFromOneMinusEntity( unsigned char *dstColors ) {    ...    unsigned char invModulate[3];    ...    invMod...
Style & w3Style =     _pUserLang->_styleArray.getStyler(STYLE_WORD3_INDEX);    styleUpdate(w3Style, _pFgColour[2], _pBgCol...
Я могу еще привести в этой статье дефектный код, но это уже становится не интересно. Всемиэтими примерами я лишь хотел пок...
Обращаюсь к вам читатели. Мне будет интересно, если вы поделитесь мыслями по этому поводуи предложите другие способы избеж...
Upcoming SlideShare
Loading in …5
×

Последствия использования технологии Copy-Paste при программировании на Си++ и как с этим быть

378 views

Published on

Я занимаюсь созданием анализатора PVS-Studio, выявляющего ошибки в исходном коде приложений на языке C/C++/C++0x. В связи с этим мне приходится просматривать большой объем исходного кода различных приложений, где с помощью PVS-Studio были обнаружены подозрительные участки кода. У меня накопилось достаточно примеров, в которых хорошо видно, когда ошибка появилась на свет из-за копирования участка кода и его модификации. Конечно, это не новая идея, что использовать Copy-Paste при программировании плохо. Однако попробуем не отделываться рекомендацией "не копируйте код" и подойдем к этой теме более внимательно.

Published in: Technology, Education
  • Be the first to comment

  • Be the first to like this

Последствия использования технологии Copy-Paste при программировании на Си++ и как с этим быть

  1. 1. Последствия использованиятехнологии Copy-Paste припрограммировании на Си++ и как сэтим бытьАвтор: Андрей КарповДата: 21.01.2011Я занимаюсь созданием анализатора PVS-Studio, выявляющего ошибки в исходном кодеприложений на языке C/C++/C++0x. В связи с этим мне приходится просматривать большой объемисходного кода различных приложений, где с помощью PVS-Studio были обнаруженыподозрительные участки кода. У меня накопилось достаточно примеров, в которых хорошо видно,когда ошибка появилась на свет из-за копирования участка кода и его модификации. Конечно, этоне новая идея, что использовать Copy-Paste при программировании плохо. Однако попробуем неотделываться рекомендацией "не копируйте код" и подойдем к этой теме более внимательно.Обычно когда говорят о Copy-Paste в программировании, то имеют в виду следующую ситуацию.Целиком копируется функция или большой фрагмент кода, а затем скопированный кодподвергается модификации. Такие действия создают в программе большое количество похожегокода, что затрудняет его сопровождение. Приходится менять одни и те же фрагменты алгоритма вразличных функциях и очень легко забыть что-то исправить.В данном случае действительно уместно говорить о том, что код лучше не копировать. Еслихочется создать функцию с похожим поведением, то полезно произвести рефакторинг и выделитьобщий код в отдельные методы/классы [1]. Или воспользоваться шаблонами и лямбда-функциями. Мы не будем подробнее рассматривать, как можно избежать дублирования кода, таккак это не относится к основному вопросу. Главное по возможности избежать дублирования кодав различных функциях. Про это много писали и с полезными рекомендациями знакомобольшинство программистов.Сосредоточимся теперь на том моменте, который обычно умалчивается в книгах и статьях понаписанию качественного кода. На самом деле без Copy-Paste программировать не получается.Мы все копируем небольшие кусочки кода, когда нам надо написать что-то подобное:GetMenu()->CheckMenuItem(IDC_ LINES_X, MF_BYCOMMAND | nState);GetMenu()->CheckMenuItem(IDC_ LINES_Y, MF_BYCOMMAND | nState);Признайтесь себе честно, что нам бывает лень набрать строчку, которая отличается только тем,что вместо символа X надо будет написать символ Y. И это правильно и логично. Скопировать иотредактировать будет быстрее, чем набрать вторую стоку заново, даже с учетом использованияспециальных инструментов, таких как Visual Assist и IntelliSence.
  2. 2. При этом здесь нет смысла говорить о дублировании кода. Как не думай, проще здесь написатьневозможно. Подобных примеров можно привести огромное количество, взяв любую программу.Не нравится, что здесь пример касается GUI, так и в других задачах мы встретим аналогичное:int texlump1 = Wads.CheckNumForName ("TEXTURE1", ns_global, wadnum);int texlump2 = Wads.CheckNumForName ("TEXTURE2", ns_global, wadnum);Беда в том, что при таком "микрокопировании" вероятность появления ошибки также достаточновысока. А поскольку таких маленьких копирований кода намного больше, чем копированиябольших блоков, то это действительно важная проблема. Как быть с этой проблемой непонятно,поэтому про нее стараются умолчать. Нельзя запретить программистам копировать код.Много из подобных ошибок обнаруживается при первом же запуске программы и быстробезболезненно исправляются. Но многие остаются и живут в коде годами, дожидаясь своего часа.Обнаружить в коде такие ошибки бывает непросто, так как рассматривать похожие строки кодасложно и внимание человека быстро притупляется. При этом наличие ошибок, возникших из-заCopy-Paste, практически не зависит от профессионализма программиста. Опечататься ипросмотреть что-то может любой человек. Дефекты такого рода попадаются даже в оченьизвестных и качественных программных продуктах.Чтобы лучше пояснить, о каких же все-таки ошибках идет речь, рассмотрим несколько примеровкода, взятых из open-source проектов. На правах рекламы: приведенные здесь ошибки былиобнаружены мной с помощью анализатора общего назначения, входящего в состав PVS-Studio [2].Код взят из программы записи и редактирования звука - Audacity.sampleCount VoiceKey::OnBackward (...) { ... int atrend = sgn( buffer[samplesleft - 2]-buffer[samplesleft - 1]); int ztrend = sgn( buffer[samplesleft - WindowSizeInt-2]- buffer[samplesleft - WindowSizeInt-2]); ...}Программист мужественно и корректно написал инициализацию переменной atrend. Началписать инициализацию переменной ztrend. Написал "sgn(buffer[samplesleft - WindowSizeInt-2]".Потом вздохнул и скопировал кусочек строки. А отредактировать забыл. Как результат функцияsgn получит в качестве аргумента значение 0.
  3. 3. Дальше сценарий будет аналогичен. Программист пишет длинное условие в 3D SDK Crystal Space:inline_ bool Contains(const LSS& lss){ // We check the LSS contains the two // spheres at the start and end of the sweep return Contains(Sphere(lss.mP0, lss.mRadius)) && Contains(Sphere(lss.mP0, lss.mRadius));}Здесь так и хочется скопировать "Contains(Sphere(lss.mP0, lss.mRadius))" и заменить имя mP0 наmP1. Но это так легко случайно забыть сделать.Наверное, вы иногда замечали, что окна программ вдруг неожиданно начинают вести себястранным образом. Например, многие программисты вспомнят окно поиска в первой редакцииVisual Studio 2010. Думаю, такие странности бывает из-за удачного стечения обстоятельств и коданаподобие этого:void COX3DTabViewContainer::OnNcPaint(){ ... if(rectClient.top<rectClient.bottom && rectClient.top<rectClient.bottom) { dc.ExcludeClipRect(rectClient); } ...}Этот код взят из известного набора классов Ultimate ToolBox. Нормально будет нарисован контролили нет, будет зависеть от его расположения.А в eLynx Image Processing SDK скопировали целую строку, и этим растиражировали опечатку.void uteTestRunner::StressBayer(uint32 iFlags)
  4. 4. { ... static EPixelFormat ms_pfList[] = { PF_Lub, PF_Lus, PF_Li, PF_Lf, PF_Ld }; const int fsize = sizeof(ms_pfList) / sizeof(ms_pfList); static EBayerMatrix ms_bmList[] = { BM_GRBG, BM_GBRG, BM_RGGB, BM_BGGR, BM_None }; const int bsize = sizeof(ms_bmList) / sizeof(ms_bmList); ...}Из-за забытого разыменования указателя переменная fsize равна 1. А потом этот кодадаптировали для инициализации bsize. Не верю, что можно два раза подряд так ошибиться,если не копировать код.В проекте EIB Suite копировалась и редактировалась строка "if (_relativeTime <= 143)". Вот только впоследнем условие ее изменить так и забыли:string TimePeriod::toString() const{ ... if (_relativeTime <= 143) os << ((int)_relativeTime + 1) * 5 << _(" minutes"); else if (_relativeTime <= 167) os << 12 * 60 + ((int)_relativeTime - 143) * 30 << _(" minutes"); else if (_relativeTime <= 196) os << (int)_relativeTime - 166 << _(" days"); else if (_relativeTime <= 143) os << (int)_relativeTime - 192 << _(" weeks"); ...}А значит код "os << (int)_relativeTime - 192 << _(" weeks");" никогда не получит управление.
  5. 5. Даже программисты в компании Intel - тоже всего лишь программисты, а не полубоги. Неудачноекопирование в проекте TickerTape:void DXUTUpdateD3D10DeviceStats(...){ ... else if( DeviceType == D3D10_DRIVER_TYPE_SOFTWARE ) wcscpy_s( pstrDeviceStats, 256, L"WARP" ); else if( DeviceType == D3D10_DRIVER_TYPE_HARDWARE ) wcscpy_s( pstrDeviceStats, 256, L"HARDWARE" ); else if( DeviceType == D3D10_DRIVER_TYPE_SOFTWARE ) wcscpy_s( pstrDeviceStats, 256, L"SOFTWARE" ); ...}Два раза повторяется условие "DeviceType == D3D10_DRIVER_TYPE_SOFTWARE".Вообще в зарослях условных операторов очень легко просмотреть ошибку. В реализации Multi-threaded Dynamic Queue в независимости от того что вернет функция IsFixed(), мы сделаем одно итоже:BOOL CGridCellBase::PrintCell(...){ ... if(IsFixed()) crFG = (GetBackClr() != CLR_DEFAULT) ? GetTextClr() : pDefaultCell->GetTextClr(); else crFG = (GetBackClr() != CLR_DEFAULT) ? GetTextClr() : pDefaultCell->GetTextClr(); ...}Кстати, копировать код легко и приятно! Не жалко и лишнюю сточку написать. :)
  6. 6. void RB_CalcColorFromOneMinusEntity( unsigned char *dstColors ) { ... unsigned char invModulate[3]; ... invModulate[0] = 255 - backEnd.currentEntity->e.shaderRGBA[0]; invModulate[1] = 255 - backEnd.currentEntity->e.shaderRGBA[1]; invModulate[2] = 255 - backEnd.currentEntity->e.shaderRGBA[2]; invModulate[3] = 255 - backEnd.currentEntity->e.shaderRGBA[3]; ...}И не важно, что массив invModulate состоит всего из трех элементов. Код взят из проекталегендарной игры Wolfenstein 3D.И напоследок пример посложнее. Код взят из весьма полезного инструмента Notepad++.void KeyWordsStyleDialog::updateDlg(){ ... Style & w1Style = _pUserLang->_styleArray.getStyler(STYLE_WORD1_INDEX); styleUpdate(w1Style, _pFgColour[0], _pBgColour[0], IDC_KEYWORD1_FONT_COMBO, IDC_KEYWORD1_FONTSIZE_COMBO, IDC_KEYWORD1_BOLD_CHECK, IDC_KEYWORD1_ITALIC_CHECK, IDC_KEYWORD1_UNDERLINE_CHECK); Style & w2Style = _pUserLang->_styleArray.getStyler(STYLE_WORD2_INDEX); styleUpdate(w2Style, _pFgColour[1], _pBgColour[1], IDC_KEYWORD2_FONT_COMBO, IDC_KEYWORD2_FONTSIZE_COMBO, IDC_KEYWORD2_BOLD_CHECK, IDC_KEYWORD2_ITALIC_CHECK, IDC_KEYWORD2_UNDERLINE_CHECK);
  7. 7. Style & w3Style = _pUserLang->_styleArray.getStyler(STYLE_WORD3_INDEX); styleUpdate(w3Style, _pFgColour[2], _pBgColour[2], IDC_KEYWORD3_FONT_COMBO, IDC_KEYWORD3_FONTSIZE_COMBO, IDC_KEYWORD3_BOLD_CHECK, IDC_KEYWORD3_BOLD_CHECK, IDC_KEYWORD3_UNDERLINE_CHECK); Style & w4Style = _pUserLang->_styleArray.getStyler(STYLE_WORD4_INDEX); styleUpdate(w4Style, _pFgColour[3], _pBgColour[3], IDC_KEYWORD4_FONT_COMBO, IDC_KEYWORD4_FONTSIZE_COMBO, IDC_KEYWORD4_BOLD_CHECK, IDC_KEYWORD4_ITALIC_CHECK, IDC_KEYWORD4_UNDERLINE_CHECK); ...}Надо сломать глаза, чтобы рассмотреть здесь ошибку. Поэтому сокращу код для ясности:styleUpdate(... IDC_KEYWORD1_BOLD_CHECK, IDC_KEYWORD1_ITALIC_CHECK, ...);styleUpdate(... IDC_KEYWORD2_BOLD_CHECK, IDC_KEYWORD2_ITALIC_CHECK, ...);styleUpdate(... IDC_KEYWORD3_BOLD_CHECK, IDC_KEYWORD3_BOLD_CHECK, ...);styleUpdate(... IDC_KEYWORD4_BOLD_CHECK, IDC_KEYWORD4_ITALIC_CHECK, ...);Рука разработчик дрогнула и он скопировал не то имя ресурса.
  8. 8. Я могу еще привести в этой статье дефектный код, но это уже становится не интересно. Всемиэтими примерами я лишь хотел показать, что такие ошибки присутствуют в разнообразнейшихпроектах и допускаются как новичками, так и профессионалами. Перейдем, наконец, кобсуждению вопроса, что же с этим всем делать.Если честно, полноценного ответа я не знаю. По крайней мере, в книгах про подобные ситуации яне читал. А вот на практике я часто встречал последствия мелкого Copy-Paste в программах. В томчисле и в своих собственных. Придется импровизировать, давая ответ на вопрос.Будем исходить из следующего положения:Программисты копируют участки кода и будут копировать, так как это удобно. Следовательно,такие ошибки всегда будут встречаться в программах.Из этого вывод:Предотвратить такие ошибки полностью невозможно, но можно постараться сократитьвероятность их создания.Я вижу два пути как можно сократить количество ошибок данного рода. Во-первых, рациональноиспользовать такие инструменты, как статические анализаторы кода. Они позволяют обнаружитьмногие ошибки данного класса. Причем сделают это на самых ранних этапах. Дешевле и прощеобнаружить и исправить ошибку сразу после написания кода, чем работать с этой же ошибкой,обнаруженной в ходе тестирования.Второй способ, который поможет в некоторых случаях сократить количество ошибок, этодисциплинировать себя и форматировать копируемый код специальным образом. Поясню напримере. int ztrend = sgn( buffer[samplesleft - WindowSizeInt-2]-buffer[samplesleft- WindowSizeInt-2]);Вот так, ошибку заметить намного сложнее, чем если бы код выглядел так: int ztrend = sgn( buffer[samplesleft - WindowSizeInt-2] - buffer[samplesleft - WindowSizeInt-2]);Следует форматировать код так, чтобы места, которые должны отличаться выстраивалисьвизуально в столбик. Так сделать ошибку будет гораздо сложнее. Понятно, что во многих случаяхэто не спасает и выше я приводил такие примеры. Однако хоть что-то лучше, чем совсем ничего.К сожалению, других способов хоть как-то сократить количество ошибок связанных с Copy-Paste яне знаю. Еще можно использовать инструменты поиска дублирующегося и похожего кода, но этовполне можно отнести к совету использования статических анализаторов.
  9. 9. Обращаюсь к вам читатели. Мне будет интересно, если вы поделитесь мыслями по этому поводуи предложите другие способы избежать ошибок Copy-Paste. Возможно, прозвучат интересныеидеи и многим это принесет значительную пользу.Свои идеи Вы можете присылать на адрес karpov[@]viva64.com и я буду рад, если мне удастсярасширить эту статью.Библиографический список 1. Steve McConnell, "Code Complete, 2nd Edition" Microsoft Press, Paperback, 2nd edition, Published June 2004, 914 pages, ISBN: 0-7356-1967-0. (Part 24.3. Reasons to Refactor) 2. Презентация "PVS-Studio, комплексное решение для разработки современных ресурсоемких приложений". http://www.viva64.com/ru/pvs-studio-presentation/

×