![]() |
Модераторы: Daevaorn |
![]() ![]() ![]() |
|
Thunderbolt |
|
||||||||||||||||||||||||||||
![]() DevRel ![]() Профиль Группа: Участник Сообщений: 122 Регистрация: 7.11.2007 Где: Тула Репутация: 10 Всего: 16 |
Я занимаюсь созданием анализатора PVS-Studio, выявляющего ошибки в исходном коде приложений на языке C/C++/C++0x. В связи с этим мне приходится просматривать большой объем исходного кода различных приложений, где с помощью PVS-Studio были обнаружены подозрительные участки кода. У меня накопилось достаточно примеров, в которых хорошо видно, когда ошибка появилась на свет из-за копирования участка кода и его модификации. Конечно, это не новая идея, что использовать Copy-Paste при программировании плохо. Однако попробуем не отделываться рекомендацией "не копируйте код" и подойдем к этой теме более внимательно.
Обычно когда говорят о Copy-Paste в программировании, то имеют в виду следующую ситуацию. Целиком копируется функция или большой фрагмент кода, а затем скопированный код подвергается модификации. Такие действия создают в программе большое количество похожего кода, что затрудняет его сопровождение. Приходится менять одни и те же фрагменты алгоритма в различных функциях и очень легко забыть что-то исправить. В данном случае действительно уместно говорить о том, что код лучше не копировать. Если хочется создать функцию с похожим поведением, то полезно произвести рефакторинг и выделить общий код в отдельные методы/классы [1]. Или воспользоваться шаблонами и лямбда-функциями. Мы не будем подробнее рассматривать, как можно избежать дублирования кода, так как это не относится к основному вопросу. Главное по возможности избежать дублирования кода в различных функциях. Про это много писали и с полезными рекомендациями знакомо большинство программистов. Сосредоточимся теперь на том моменте, который обычно умалчивается в книгах и статьях по написанию качественного кода. На самом деле без Copy-Paste программировать не получается. Мы все копируем небольшие кусочки кода, когда нам надо написать что-то подобное:
Признайтесь себе честно, что нам бывает лень набрать строчку, которая отличается только тем, что вместо символа 'X' надо будет написать символ 'Y'. И это правильно и логично. Скопировать и отредактировать будет быстрее, чем набрать вторую стоку заново, даже с учетом использования специальных инструментов, таких как Visual Assist и IntelliSence. При этом здесь нет смысла говорить о дублировании кода. Как не думай, проще здесь написать невозможно. Подобных примеров можно привести огромное количество, взяв любую программу. Не нравится, что здесь пример касается GUI, так и в других задачах мы встретим аналогичное:
Беда в том, что при таком "микрокопировании" вероятность появления ошибки также достаточно высока. А поскольку таких маленьких копирований кода намного больше, чем копирования больших блоков, то это действительно важная проблема. Как быть с этой проблемой непонятно, поэтому про нее стараются умолчать. Нельзя запретить программистам копировать код. Много из подобных ошибок обнаруживается при первом же запуске программы и быстро безболезненно исправляются. Но многие остаются и живут в коде годами, дожидаясь своего часа. Обнаружить в коде такие ошибки бывает непросто, так как рассматривать похожие строки кода сложно и внимание человека быстро притупляется. При этом наличие ошибок, возникших из-за Copy-Paste, практически не зависит от профессионализма программиста. Опечататься и просмотреть что-то может любой человек. Дефекты такого рода попадаются даже в очень известных и качественных программных продуктах. Чтобы лучше пояснить, о каких же все-таки ошибках идет речь, рассмотрим несколько примеров кода, взятых из open-source проектов. Приведенные здесь ошибки были обнаружены мной с помощью анализатора общего назначения, входящего в состав PVS-Studio [2]. Код взят из программы записи и редактирования звука - Audacity.
Программист мужественно и корректно написал инициализацию переменной 'atrend'. Начал писать инициализацию переменной 'ztrend'. Написал "sgn(buffer[samplesleft - WindowSizeInt-2]". Потом вздохнул и скопировал кусочек строки. А отредактировать забыл. Как результат функция 'sgn' получит в качестве аргумента значение 0. Дальше сценарий будет аналогичен. Программист пишет длинное условие в 3D SDK Crystal Space:
Здесь так и хочется скопировать "Contains(Sphere(lss.mP0, lss.mRadius))" и заменить имя 'mP0' на 'mP1'. Но это так легко случайно забыть сделать. Наверное, вы иногда замечали, что окна программ вдруг неожиданно начинают вести себя странным образом. Например, многие программисты вспомнят окно поиска в первой редакции Visual Studio 2010. Думаю, такие странности бывает из-за удачного стечения обстоятельств и кода наподобие этого:
Этот код взят из известного набора классов Ultimate ToolBox. Нормально будет нарисован контрол или нет, будет зависеть от его расположения. А в eLynx Image Processing SDK скопировали целую строку, и этим растиражировали опечатку.
Из-за забытого разыменования указателя переменная 'fsize' равна 1. А потом этот код адаптировали для инициализации 'bsize'. Не верю, что можно два раза подряд так ошибиться, если не копировать код. В проекте EIB Suite копировалась и редактировалась строка "if (_relativeTime <= 143)". Вот только в последнем условие ее изменить так и забыли:
А значит код "os << (int)_relativeTime - 192 << _(" weeks");" никогда не получит управление. Даже программисты в компании Intel - тоже всего лишь программисты, а не полубоги. Неудачное копирование в проекте TickerTape:
Два раза повторяется условие "DeviceType == D3D10_DRIVER_TYPE_SOFTWARE". Вообще в зарослях условных операторов очень легко просмотреть ошибку. В реализации Multi-threaded Dynamic Queue в независимости от того что вернет функция IsFixed(), мы сделаем одно и тоже:
Кстати, копировать код легко и приятно! Не жалко и лишнюю сточку написать. ![]()
И не важно, что массив 'invModulate' состоит всего из трех элементов. Код взят из проекта легендарной игры Wolfenstein 3D. И напоследок пример посложнее. Код взят из весьма полезного инструмента Notepad++.
Надо сломать глаза, чтобы рассмотреть здесь ошибку. Поэтому сокращу код для ясности:
Рука разработчик дрогнула и он скопировал не то имя ресурса. Я могу еще привести в этой статье дефектный код, но это уже становится не интересно. Всеми этими примерами я лишь хотел показать, что такие ошибки присутствуют в разнообразнейших проектах и допускаются как новичками, так и профессионалами. Перейдем, наконец, к обсуждению вопроса, что же с этим всем делать. Если честно, полноценного ответа я не знаю. По крайней мере, в книгах про подобные ситуации я не читал. А вот на практике я часто встречал последствия мелкого Copy-Paste в программах. В том числе и в своих собственных. Придется импровизировать, давая ответ на вопрос. Будем исходить из следующего положения: Программисты копируют участки кода и будут копировать, так как это удобно. Следовательно, такие ошибки всегда будут встречаться в программах. Из этого вывод: Предотвратить такие ошибки полностью невозможно, но можно постараться сократить вероятность их создания. Я вижу два пути как можно сократить количество ошибок данного рода. Во-первых, рационально использовать такие инструменты, как статические анализаторы кода. Они позволяют обнаружить многие ошибки данного класса. Причем сделают это на самых ранних этапах. Дешевле и проще обнаружить и исправить ошибку сразу после написания кода, чем работать с этой же ошибкой, обнаруженной в ходе тестирования. Второй способ, который поможет в некоторых случаях сократить количество ошибок, это дисциплинировать себя и форматировать копируемый код специальным образом. Поясню на примере.
Вот так, ошибку заметить намного сложнее, чем если бы код выглядел так:
Следует форматировать код так, чтобы места, которые должны отличаться выстраивались визуально в столбик. Так сделать ошибку будет гораздо сложнее. Понятно, что во многих случаях это не спасает и выше я приводил такие примеры. Однако хоть что-то лучше, чем совсем ничего. К сожалению, других способов хоть как-то сократить количество ошибок связанных с Copy-Paste я не знаю. Еще можно использовать инструменты поиска дублирующегося и похожего кода, но это вполне можно отнести к совету использования статических анализаторов. Обращаюсь к вам читатели. Мне будет интересно, если вы поделитесь мыслями по этому поводу и предложите другие способы избежать ошибок Copy-Paste. Возможно, прозвучат интересные идеи и многим это принесет значительную пользу. Свои идеи Вы можете присылать на адрес karpov[@]viva64.com и я буду рад, если мне удастся расширить эту статью. Библиографический список
--------------------
Карпов Андрей, DevRel в PVS-Studio. |
||||||||||||||||||||||||||||
|
|||||||||||||||||||||||||||||
boostcoder |
|
|||
![]() pattern`щик ![]() ![]() ![]() ![]() Профиль Группа: Завсегдатай Сообщений: 5458 Регистрация: 1.4.2010 Репутация: 49 Всего: 110 |
нее, это все понятно. не понятно одно: как Вы это все отыскали?! но не верю я что у вас нет занятий поважнее
![]() |
|||
|
||||
ksili |
|
|||
![]() Эксперт ![]() ![]() ![]() ![]() Профиль Группа: Завсегдатай Сообщений: 2069 Регистрация: 3.11.2005 Где: Красноярск Репутация: 1 Всего: 17 |
boostcoder, он этим зарабатывает на жизнь, так что придется поверить
![]() ![]() -------------------- Ничто так не развивает аналитическое мышление, как отладка сложной программы без возможности пошагового выполнения (с) |
|||
|
||||
boostcoder |
|
|||
![]() pattern`щик ![]() ![]() ![]() ![]() Профиль Группа: Завсегдатай Сообщений: 5458 Регистрация: 1.4.2010 Репутация: 49 Всего: 110 |
ksili, а где за такое платят?
|
|||
|
||||
ksili |
|
|||
![]() Эксперт ![]() ![]() ![]() ![]() Профиль Группа: Завсегдатай Сообщений: 2069 Регистрация: 3.11.2005 Где: Красноярск Репутация: 1 Всего: 17 |
boostcoder, почитай его профиль. И посмотри его темы на форуме.
-------------------- Ничто так не развивает аналитическое мышление, как отладка сложной программы без возможности пошагового выполнения (с) |
|||
|
||||
GoldFinch |
|
||||
![]() ![]() ![]() ![]() Профиль Группа: Завсегдатай Сообщений: 2141 Регистрация: 30.11.2008 Репутация: 15 Всего: 26 |
Как этого избежать
- не использовать идентификаторы различающиеся цифрами. Давать им различные информативные имена, даже someFoo, anotherFoo лучше чем foo1, foo2. Всякие Form1 можно использовать только если Form2 никогда не будет. - выносить код в функции, можно локальные если язык позволяет. Если два выражения делают какое-то совместное действие - то их можно объединить в функцию, дать ей подходящее имя и возможно повторно использовать. Инлайнить должен компилятор, а не программист. - Не делать данные кодом. Обрабатывать однородные данные в одном месте. Вместо
делать таблицу
|
||||
|
|||||
Thunderbolt |
|
|||
![]() DevRel ![]() Профиль Группа: Участник Сообщений: 122 Регистрация: 7.11.2007 Где: Тула Репутация: 10 Всего: 16 |
Кстати, вот еще из свежего - Intel IPP Samples for Windows — работа над ошибками
P.S. Мой блог н Хабре: http://andrey2008.habrahabr.ru/blog/ --------------------
Карпов Андрей, DevRel в PVS-Studio. |
|||
|
||||
![]() ![]() ![]() |
Правила форума "С++:Общие вопросы" | |
|
Добро пожаловать!
Если Вам понравилась атмосфера форума, заходите к нам чаще! С уважением, Earnest Daevaorn |
0 Пользователей читают эту тему (0 Гостей и 0 Скрытых Пользователей) | |
0 Пользователей: | |
« Предыдущая тема | C/C++: Общие вопросы | Следующая тема » |
|
По вопросам размещения рекламы пишите на vladimir(sobaka)vingrad.ru
Отказ от ответственности Powered by Invision Power Board(R) 1.3 © 2003 IPS, Inc. |