Ловля багов 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 были ошибками проектиро─ вания (в том числе касающимися числа штри─ ховок). А самые сложные глюки - это комбинации двух и более. (о практике см. в следующей статье)