Ловля багов
Alone Coder
Вдохновившись блогом PVS-Studio на Хаб─
ре, я решил попробовать найти самые типич─
ные ошибки, которые возникают при разрабо─
тке на ассемблере Z80.
Хотелось бы верить, что это поможет
увеличить эффективность кодинга на ZX
Spectrum. Кроме практических советов мож─
но, в конце концов, сделать и утилиты кон─
троля исходников (хотя бы и встроенные в
ассемблер), которые найдут самые типичные
ошибки. Причём не в том смысле,что кодиро─
вать как-то "нестандартно" будет ошибкой.
Просто будут выдаваться предупреждения,
если подозрительный код не пометить как-то
особо. Предупреждения давно показали себя
хорошим инструментом в компиляторах на бо─
лее других системах. Они позволяют, напри─
мер, найти ошибку"=" вместо "==" в C или
какие-нибудь триггеры по двум фронтам в
Верилоге (знающие люди поправят).
Уж не знаю, почему у нас так мало пишут
про работу с ошибками - наверно, подразу─
мевается, что крутые кодеры не делают оши─
бок. (Как крутые художники, которые якобы
рисуют без стёрки и сразу набело.) Это,ко─
нечно, неправда. Поиск ошибок занимает во
всяком случае не меньше времени, чем напи─
сание кода.Но если работу построить верно,
то можно отладку проводить без нервов и
почти её не замечать.
И как же это сделать?
Есть методы доказательного программи─
рования, описанные в умных книжках (типа
Robert L.Baber.Error-free Software. Know-
how and Know-why of Program Correctness ).
Там функции (не выше определённой сложно─
сти) проверяются хитроумными методами, но,
боюсь, не для нас с вами. Это хорошо для
тех случаев, когда программа уже на первом
запуске должна работать как часы, а время
на разработку выделено с запасом на все
доказательства и перепроверки (ибо доказа─
тельства функций чуть ли не сложнее самих
функций).Но у нас другой случай - мы можем
запускать программу когда угодно и сколько
угодно, и она ничего не испортит.
Разберём по ситуациям:
а)надо написать новую программу;
б)надо переделать свою программу;
в)надо переделать чужую программу.
Начнём са). Хорошо писать программу,
если заранее знаешь всю её структуру. Сам
по себе кодинг занимает мало времени по
сравнению с проектированием. Говорят, про─
фессиональный программист пишет 30 строк
отлаженного кода в день (были и более пес─
симистичные оценки, в том числе у всем из─
вестного Фредерика Брукса ). Если есть вся
структура и чётко ясно,какая процедура что
должна делать,то мы можем проверить каждую
процедуру по отдельности. Можно даже авто─
матизировать это функциональное тестирова─
ние,если мы собираемся программу регулярно
переделывать - тут мы переходим кб). Это
хорошо, когда программа большая, разработ─
чиков много и за всем не уследишь. Если
вместе с каждой процедурой есть проверяю─
щая процедура, а перед каждой сборкой все
эти проверяющие процедуры вызываются, то
можно контролировать, что никто ничего чу─
жого ненароком не сломал. Это ужев).
Но опять-таки: методы хороши, но не для
наших задач.Мы можем пересобирать програм─
му сколько угодно и когда угодно, хоть по─
сле каждого изменения. И не просто можем,а
так и надо делать. Маленький шажок, компи─
ляция, запускаем,ловим баг - и баг с веро─
ятностью 95% находится в новодобавленном
коде, даже не нужен отладчик.
Но чтобы сделать этот шажок, нам нужно,
чтобы исходный вариант программы был сам
по себе работоспособным. Отсюда следуют
выводы прямо по пунктама),б),в):
а): в первую очередь при разработке про─
граммы надо выстроить хоть какой-то рабо─
тоспособный скелет,который потом можно по─
степенно менять. Например, ACEdit в деви─
честве был листалкой текстов под 48К, сна─
чала даже и без дисковых операций.
б): прерывать разработку программы надо
обязательно на работоспособной версии,ина─
че впоследствии её не поднимешь или подни─
мешь с несоразмерно большим трудом. Жела─
тельно ещё и документировать, как её соби─
рать.Поленился документировать или сделать
автосборщик? Так умер проект Awaken.
в): если надо переделать чужую програм─
му, то сначала надо убедиться,что она ком─
пилируется и работает без переделок. Если
эта переделка сопряжена с переносом в дру─
гой ассемблер или декомпиляцией,то для на─
чала надо проверить,что после такого пере─
носа всё компилируется в блок кода,побайт─
но идентичный старому.
Сферический алгоритм такой переделки
программ:
1.Добиться, чтобы компилировалось (не
факт, что правильно).
2.Добиться, чтобы работало (в соответс─
твии со старым вариантом).
3.Добиться, чтобы работало как надо (с
новыми изменениями).
4.Добиться, чтобы работало как надо по─
лностью (желателен бетатест).
При переделке исходников со знакомых
систем и средств разработки первые два пу─
нкта выполняются уже в самом начале.
Конкретно последовательность работы при
декомпиляции (говорю по своему опыту с Pro
Tracker 3 и Perfect Commander ):
1.Декомпилировать (с чётким разделением
кода и данных). Я использую ZXD.
2.Пройти по всему исходнику сверху вниз
и сделать заметки.
3.Расставить нормальные метки и вычис─
ляемые выражения.Когда мы заменяем метку в
процедуре, мы можем по сообщениям об ошиб─
ках узнать, сколько к ней было обращений.
Это можно отметить и потом использовать в
оптимизации.
4.Проверить, что результат компиляции
байт в байт соответствует образцу. Я ис─
пользую утилиту File Comparer by MAYhEM
(FCmp_v2BнаSYS.TRD).
5.Убедиться, что при добавлении в нача─
ло какого-нибудьDS 300 программа всё ещё
работает.
6.Только потом начинать вносить измене─
ния (можно вIF'ах, через условную компи─
ляцию).
* * *
Итак, пусть мы научились работать мале─
нькими шажками. Выстраиваем скелет из зна─
комых процедур и знакомыми методами, потом
шаг за шагом добавляем функционал, интер─
фейс, оптимизируем и т.д. Этакий Agile. Но
несмотря на то, что 95% глюков находятся
сразу (в том числе компилятором), остаётся
5% таких, над которыми надо поломать голо─
ву.
Есть два основных способа ловли глюков:
1.Всё проверить. А именно то, что изме─
нено, и зависящий от этого код. И заодно
вызываемый оттуда код.
2.Сделать тесткейз (сиречь доломать).
Я обычно пользуюсь вторым. Нахожу тест─
кейз, который точно глючит (можно сделать
сцециальные условия прямо в программе,что─
бы выйти на ветку), его трассирую или тупо
смотрю переменные.
Например, мы пишем 3D движок. Сделали
кое-как, крутим-вертим,и тут БАЦ - что это
промелькнуло? Как это повторить? А это за─
висит от того,как мы крутили. Если крутили
автоматически, то нужно только найти номер
кадра (например, трассируем кадр за кадром
и смотрим). Если с клавиатуры, то придётся
смотреть параметры и особо проверять подо─
зрительные комбинации (например,с нулевыми
углами, с максимумами или минимумами мас─
штабов). Лично я предпочинаю крутить авто─
матически, пока не отлажу отрисовку. Упра─
вление с клавиатуры вставляю потом, только
для сложных траекторий и не всегда. Так, в
Critical Error, The Board и The Board II
было управление с клавиатуры, а в Mission
Highly Improbable и New View 48K были про─
писанные скрипты. Причём сначала я автома─
тически кручу, допустим, один полигон и по
одной оси (или двигая одну вершину),только
потом добавляю и проверяю другие движения,
комбинации полигонов и т.п. Чтобы при бу─
дущих оптимизациях не развалить то,что уже
отлажено,я сохраняю эти старые тесты в ве─
тках условной компиляции. Если программа
внезапно перестала работать, всегда можно
за секунды вернуться и проверить её на
простых случаях.
Кстати. Один добрый дядя (а может, и
злой) написал где-то в интернете, а я про─
цитирую вам: "You solve a problem, and
produce beautiful,understandable code.Then
you optimise and produce a big ugly chunk
of WTF.Keep the original code in comments"
("Вы решили задачу и получили красивый,по─
нятный код. Потом вы его оптимизировали и
получили огромный кусок непонятно чего.
Оставляйте оригинальный код в комментари─
ях"). Я часто пользуюсь этим принципом.
Составляю алгоритм на языке высокого уров─
ня (иногда даже отлаживаю на Delphi ),кла─
ду его в комментарии и по этим комментари─
ям пишу оптимизированный код. Я делал так
и на Си,когда писал Billiard: иногда ужас,
который сгенерил SDCC, можно ускорить в
разы ассемблерными вставками,но перед эти─
ми ассемблерными вставками лучше бы оста─
вить в комментариях оригинальный код. Если
же я изначально пишу на ассемблере, то у
меня есть текстовый документ, где в общих
чертах расписан метод и несколько вариан─
тов его реализации. Если я меняю один код
на другой, то я делаю это через условную
компиляцию, а потом,если понадобится чист─
ка, старый вариант тоже где-то сохраняю.
Да,не пытайтесь реализовать сразу окон─
чательный метод.У вас ещё скелет программы
не заработал, а уже "big ugly chunk of
WTF", который надо отладить целиком. Сна─
чала пусть заработает как-то. Например,
через процедуру точки в ПЗУ.Через атрибуты
вместо чанков. Без текстурирования.С ветв─
лением проверками, а не по таблице. И т.д.
Тут можно ещё вспомнить,что "преждевремен─
ная оптимизация - корень всех зол".
Я писал цветные чанки для New View 48K
сразу целиком и без отладки. Алгоритм уло─
жился в713 строк - казалось бы, немного.
В итоге при первой же попытке это запус─
тить мне пришлось найти и исправить19 ба─
гов, из которых 3 были ошибками проектиро─
вания (в том числе касающимися числа штри─
ховок).
А самые сложные глюки - это комбинации
двух и более.
(о практике см. в следующей статье)
Other articles: