Я использую много-много утверждений и строю как «отладочную» версию, так и «релизную» версию. Моя отладочная версия работает намного медленнее, чем моя релизная версия, со всеми проверками, которые она делает.
Я часто запускаю под Valgrind , и мой код имеет нулевые утечки памяти. Нуль. Гораздо проще сохранить программу без утечек, чем взять программу с ошибками и устранить все утечки.
Кроме того, мой код компилируется без предупреждений, несмотря на то, что у меня установлен компилятор для дополнительных предупреждений. Иногда предупреждения являются глупыми, но иногда они указывают на ошибку, и я исправляю ее без необходимости искать ее в отладчике.
Я пишу чистый C (я не могу использовать C ++ в этом проекте), но я делаю C очень последовательным образом. У меня есть объектно-ориентированные классы, с конструкторами и деструкторами; Я должен назвать их вручную, но последовательность помогает. И если я забуду вызвать деструктора, Вальгринд бьет меня по голове, пока я не исправлю это.
В дополнение к конструктору и деструктору я пишу функцию самопроверки, которая просматривает объект и решает, является ли он нормальным или нет; например, если дескриптор файла равен нулю, но связанные данные файла не обнуляются, это указывает на какую-то ошибку (либо дескриптор был засорен, либо файл не был открыт, но в этих полях объекта есть мусор). Кроме того, большинство моих объектов имеют поле «подпись», которое должно быть установлено на определенное значение (специфичное для каждого отдельного объекта). Функции, которые используют объекты, обычно утверждают, что объекты являются нормальными.
Каждый раз, когда у меня malloc()
появляется память, моя функция заполняет память значениями 0xDC
. Структура, которая не полностью инициализирована, становится очевидной: число слишком велико, указатели недействительны (0xDCDCDCDC
), и когда я смотрю на структуру в отладчике, становится очевидно, что она неинициализирована. Это намного лучше, чем заполнение нулями памяти при вызове malloc()
. (Конечно, заполнение 0xDC
только в отладочной сборке; сборка выпуска не должна тратить это время впустую.)
Каждый раз, когда я освобождаю память, я стираю указатель. Таким образом, если у меня есть глупая ошибка, когда код пытается использовать указатель после освобождения памяти, я немедленно получаю исключение нулевого указателя, которое указывает мне прямо на ошибку. Мои функции-деструкторы не берут указатель на объект, они берут указатель на указатель и засоряют указатель после разрушения объекта. Кроме того, деструкторы стирают свои объекты перед их освобождением, поэтому, если какой-то кусок кода имеет копию указателя и пытается использовать объект, проверка работоспособности запускается мгновенно.
Valgrind сообщит мне, записывает ли какой-либо код конец буфера. Если бы у меня этого не было, я бы поставил «канареечные» значения после концов буферов и проверил бы их проверку работоспособности. Эти канарские значения, как и значения сигнатур, будут только для отладочной сборки, поэтому в версии выпуска не будет раздувания памяти.
У меня есть коллекция модульных тестов, и когда я делаю какие-либо серьезные изменения в коде, мне очень удобно запускать модульные тесты, и у меня есть уверенность, что я ничего не сломал. Конечно, я запускаю модульные тесты как на отладочной версии, так и на версии выпуска, поэтому все мои утверждения имеют шанс найти проблемы.
Установка всей этой структуры была небольшим дополнительным усилием, но она окупается каждый день. И я чувствую себя довольно счастливым, когда assert срабатывает и указывает мне прямо на ошибку, вместо того, чтобы запускать ошибку в отладчике. В долгосрочной перспективе просто все время поддерживать в чистоте.
Наконец, я должен сказать, что мне действительно нравятся венгерские обозначения. Я работал в Microsoft несколько лет назад, и, как и Джоэл, я выучил Apps венгерский, а не сломанный вариант. Это действительно делает неправильный код неправильным .