Урок 07. Проблемы выявления 64-битных ошибок
Существуют различные подходы к выявлению ошибок в программном коде. Рассмотрим основные методологии и их эффективность в выявлении 64-битных ошибок.
Обзор кода
Самым старым, проверенным и надежным подходом к поиску дефектов является совместный обзор кода (англ. code review). Эта методика основана на совместном чтении кода с выполнением ряда правил и рекомендаций, хорошо описанных в книге Стива Макконнелла "Совершенный код" (Steve McConnell, "Code Complete"). К сожалению, эта практика неприменима для крупномасштабной проверки современных программных систем в силу их большого объема.
Обзор кода в данном случае скорее можно рассматривать как хороший способ обучения и защиты от 64-битных ошибок в новом разрабатываемом коде. Для выявления же уже имеющихся ошибок данный способ неприемлем из-за его стоимости. Вам придется совместно внимательно прочитать весь код программного проекта, чтобы обнаружить все 64-битные ошибки.
Статический анализ кода
На помощь разработчикам, которые осознают необходимость регулярного просмотра кода, но не имеют достаточного количества времени, приходят средства статического анализа кода. Их основной задачей является сокращение объема кода, требующего внимания человека, и тем самым сокращение времени его просмотра. К статическим анализаторам кода относится достаточно большой класс программ, реализованных для различных языков программирования и имеющих разнообразный набор функций, от простейшего контроля выравнивания кода, до сложного анализа потенциально опасных мест. Преимуществом статического анализа является его хорошая масштабируемость. С его помощью можно в разумные сроки проанализировать проект любого объема. А систематическое использование анализаторов позволяет выявлять многие ошибки еще на этапе написания кода.
Метод статического анализа является наиболее оптимальным решением для выявления 64-битных ошибок. В дальнейшем, рассматривая паттерны 64-битных ошибок, мы будем показывать, как данные ошибки можно диагностировать, используя статический анализатор Viva64, входящий в состав PVS-Studio. В следующем уроке вы также подробнее познакомитесь с методологией статического анализа и инструментом PVS-Studio.
Метод белого ящика
Под тестированием методом белого ящика будем понимать выполнение максимально доступного количества различных веток кода с использованием отладчика или иных средств. Чем большее покрытие кода было достигнуто, тем более полно выполнено тестирование. Под тестированием по методу белого ящика также иногда понимают простую отладку приложения с целью поиска известной ошибки. Полноценное тестирование методом белого ящика всего кода программ уже давно стало невозможным в силу огромного размера современных программ. Сейчас тестирование по методу белого ящика удобно применять на этапе, когда ошибка найдена, и необходимо понять причину ее возникновения. У тестирования методом белого ящика существуют оппоненты, отрицающие полезность отладки программ в реальном времени. Основной мотив заключается в том, что возможность наблюдать ход работы программы и при этом вносить изменения в ее состояние порождает недопустимый подход в программировании, основанный на большом количестве исправлений кода методом проб и ошибок. Мы не будем касаться данных споров, но заметим, что тестирование по методу белого ящика в любом случае очень дорогой способ повышения качества больших программных систем.
Читателю должно быть очевидно, что полная отладка приложения для выявления 64-битных ошибок также нереальна, как и полный обзор программного кода.
Дополнительно стоит заметить, что при отладке 64-битных приложений, обрабатывающих большие массивы данных, способ пошаговой отладки может стать неприменимым. Отладка таких приложений может занимать намного больше времени. Стоит заранее обдумать возможность использования систем протоколирования ("логирования") для отладки приложений или предусмотреть иные методы.
Метод черного ящика (юнит-тесты)
Намного лучше себя зарекомендовал метод черного ящика. К этому типу тестирования можно отнести юнит-тестирование (unit tests). Основная идея метода заключается в написании набора тестов для отдельных модулей и функций, проверяющего все основные режимы их работы. Ряд источников относят юнит-тестирование к методу белого ящика, поскольку оно основывается на знании устройства программы. Мы придерживаемся позиции, что тестируемые функции и модули следует рассматривать как черные ящики, так как юнит-тесты не должны учитывать внутреннее устройство функции. Обоснованием этому может служить такая методология, когда тесты разрабатываются до начала написания самих функций, что способствует повышению контроля их функциональности с точки зрения спецификации.
Юнит-тестирование хорошо зарекомендовало себя как при разработке простых, так и сложных проектов. Одним из преимуществ юнит-тестирования является то, что легко можно проверить корректность вносимых в программу исправлений прямо в ходе разработки. Стараются делать так, чтобы все тесты проходили в течение нескольких минут, что позволяет разработчику, который внес изменения в код, сразу заметить ошибку и исправить ее. Если прогон всех тестов невозможен, то обычно длительные тесты выносят отдельно и запускают, например, ночью. Это также способствует оперативному обнаружению ошибок, по крайней мере, на следующее утро.
C использованием юнит-тестов для поиска 64-битных ошибок связан ряд неприятных моментов. Стремясь сократить время выполнения тестов, при их разработке стараются использовать небольшой объем вычислений и объем обрабатываемых данных. Например, разрабатывая тест на функцию поиска элемента в массиве, не имеет большого значения, будет она обрабатывать 100 элементов или 10 000 000. Сотни элементов будет достаточно, а вот по сравнению с обработкой 10 000 000 элементов скорость выполнения теста может быть существенно выше. Но если вы хотите разработать полноценные тесты, чтобы проверить эту функцию на 64-битной системе, вам потребуется обработать более 4 миллиардов элементов! Вам кажется, что если функция работает на 100 элементах, она будет работать и на миллиардах? Нет. Приведем пример.
bool FooFind(char *Array, char Value, size_t Size){ for (unsigned i = 0; i != Size; ++i) if (i % 5 == 0 && Array[i] == Value) return true; return false;}#ifdef _WIN64 const size_t BufSize = 5368709120ui64;#else const size_t BufSize = 5242880;#endifint _tmain(int, _TCHAR *) { char *Array = (char *)calloc(BufSize, sizeof(char)); if (Array == NULL) std::cout
Мой блог находят по следующим фразам