Разработка больших проектов на ассемблере — Архив WASM.RU

Все статьи

Разработка больших проектов на ассемблере — Архив WASM.RU

Глава 0: Disclaimer

Исследуемые здесь принципы разработки больших проектов должны, пожалуй, работать для любого языка. В этом плане они "кросс-платформенны".

Сразу оговорюсь, что я не глаголю аксиому. Это лишь моё мнение, мои размышления, мой опыт программирования на асме. Где-то - моя биография. Я приветствую иные мнения. И буду внимательно читать Ваши отзывы.

Итак, приступим.

Путь адепта

Происходит такая оказия, что обычный программёр узнаёт о существовании языка ассемблера. Здесь как в опере. Если понял и прочувствовал его силу, красоту и мощь, то полюбил навсегда. Если же чего-то не хватило, то возненавидел навсегда. Ибо всякий раз эти дурацкие "mov, int и sbb" будут напоминать ему о поражении. Мы же останемся с тем, который полюбил ЯА.

Обычно в начале он великий энтузиаст. Он летит на крыльях новых возможностей. Он узнаёт, что теперь может контролировать каждый байт код своей программы. Он с умным видом открывает программы в HEX редакторе и просматривает их содержимое, хотя пока что понимает здесь лишь "MZ" и "PE". Он может теперь разговаривать с Президентом компьютера, Процессором, на его собственном языке за чашкой чая. И различные придворные порты, шины, диски и платы ему внимают. Он уже скоро вскричит: "I AM GOD HERE!" Да тут ещё выходит "Матрица" на экраны. А сколько радости, когда списал у Iczelion из урока листинг "Hello, world" и откомпилировал в экзешник величиной всего 1,5 килобайта. И он даже корректно работает. А какое счастье посещает его, когда он узнаёт, что можно всё это добро засунуть в 1 килобайт. И там ещё куча пустых нулей остаётся.

Но жизнь - штука суровая. После того, как новый адепт начинает отходить от примеров учителя, после того, как он обнаруживает, что не всегда всё идёт так, как он хочет, после того, как он неделю копает отладчиком свою программу, пытаясь найти эту дурацкую ошибку, а потом обнаруживает, что просто забыл поставить одну букву в своём исходнике (напр. "je" -> "jne"), после того, как он походил тернистыми путями Intel'а и понабивал шишки в ямах Microsoft'а, после многих перипетий адепт постепенно начинает более или менее реально воспринимать жизнь и великое искусство программирования. Но, как говорил Рей Бредбери, "Единственный способ заставить писателя не писать, это кольт 45-го калибра".

Посему писатели пишут. Проекты становятся всё более крупными и интересными. Ходить по всему листингу скролбаром, отыскивая код процедуры или место на котором закончил в последний раз; мучительно вспоминать, что ты вчера хотел реализовать; всё заново переписывать из-за того, что выбранный путь оказался неверным; просто искать способы быстро выполнять стандартные операции, всё это приводит адепта к просветлению и осознанию полезности удобной среды разработки.

Глава 1: Среда разработки

В этом разделе я много писать не буду. Хорошая статья на указанную тему уже есть на WASM.RU, и там всё подробно написано. Скажу лишь, что для себя самой удобной, хотя и не совершенной средой, нахожу RadASM от KetilO. И вот почему:

  • Частые операции типа assemble, build, save, debug выполняются теми клавишами, которые я настрою
  • Имеется удобная настраиваемая подсветка синтаксиса
  • Весь проект объединён в группу файлов
  • Автоматически отслеживается версия
  • Удобная навигация по файлам
  • Быстрый переход на определение функции и назад
  • И многое другое

Однако главное, чтобы среда была максимально удобна Вам. Ибо прилежный адепт не должен много времени отвлекаться на бытовые мелочи.

Проблемы разработки

Начинающий адепт, недавно познавший написание программ уровня "Hello, world", получив озарение свыше или задание от начальника на написание программы, сразу бросается к клавиатуре. Он стремится выплеснуть в код всё своё искусство, накопленное за эти две недели, которые он изучает ассемблер. "Сейчас я сделаю вам всем шедевр в несколько килобайт! Не пройдёт и два месяца". Клавиатура стонет и скрипит под натиском обрушивающегося на неё дождя ударов пальцами. И в то же время она радуется, что он не обезьяна, а то бы ещё и пальцами ног набирал для скорости.

Но вот каркас программы написан, а что дальше? А дальше он начинает писать одно. Что-то не получается. Переключается на другую сферу в программе. Потом делает третью часть. О, нашёл проблему в первой - возвращается к ней. На этот момент он уже забыл, каким способом хотел организовывать работу с памятью в своей программе. Приходится изобретать велосипед снова. А как удобно представить свои данные? Лучше так. А может быть по-другому? Или так? А что там было в третьей части? Да это не важно. Вот в статье он прочитал об оригинальном способе представления данных. И снова всё надо переписывать. И новые структуры появляются. "А это что за структура? Да это ведь для моего первого варианта работы с памятью два месяца назад!" Но теперь уже он не нужен.

Кроме того, он ведь не просто пишет программы. Он пишет на ассемблере! Самые быстрые программы. А как раз недавно он прочитал классную статью по оптимизации кода по скорости и размеру. Поэтому во время написания программы адепт её сразу оптимизирует. Но это оказывается БОЛЬШОЙ ошибкой.

Ваш покорный слуга, например, долго пребывал в глубокой уверенности, что в масме диалоги не работают, их нельзя создавать. Или масм у Стива кривой? В наших ошибках всегда виноват кто-нибудь другой. Но вот же есть в примерах диалоги. Дрожащими пальцами компилирую его. Всё работает. На основе этого файла начинаю собирать свой диалог. В какой-то момент он перестаёт работать. А я не заметил после чего. Приходится всё начинать сначала. Примерно через месяц или два я понял свою ошибку. Её озвучил кто-то из великих программистов: "Многие беды программирования происходят от преждевременной оптимизации". Я не сохранял всякие IF, ELSE в первоначальном виде, а переводил их в CMP и переделывал структуру диалоговой процедуры. Читая Iczelion'а, я не сразу обратил внимание, что диалоговая процедура отличается от оконной. Что она должна возвращать определённое булевское значение. В результате я возвращал неправильные значения и диалоговое окно не создавалось, так как считало, что произошла ошибка создания окна.

Зато про сегментные регистры (EBX, ESI, EDI) я прочитал и заметил сразу. Iczelion говорит, что их нельзя использовать. А если и используешь, то надо восстанавливать их значение перед возвратом в систему. Глупый! Ведь это же их надо тогда в стек складывать. А потом восстанавливать. Это ж сколько лишних байт и тактов процессора! Но я ведь умнее этого наивного Iczelion'а! Я попробовал их не восстанавливать. Всё прошло нормально в Win98SE. Ну и стал так писать везде.

А как-то злосчастным утром я попробовал свою одну прогу запустить в Win2000. А она возьми да и зависни! Как!? Пошёл домой. Проверил: всё работает. Прихожу на тот комп - прога дохнет. Причём вываливается внутри системы, а не моей программы. Я и на другой машине попробовал. Тот же результат: в двухтысячной вылетаем по GPF. Вот тут Микрософту досталось за кривую систему. "Да все они там ламера!" Как настоящий кул-хацкер, я в 2000-ой взялся за отладчик. Отыскал, где начинается проблема. В моей части кода всё ОК. Пошёл дальше в систему. А тут через несколько инструкций идёт обращение к памяти по адресу ESI. А я его в своей программе обнуляю для собственных нужд…

Это было мне хорошим уроком. Пришлось несколько программ переделывать, чтобы эти регистры не менялись.

Итак, в результате описанных выше способов кодирования, программа быстро превращается в непроходимую кашу. Это ужасно в любом языке. Особенно в ЯА, где разобраться в получившейся каше гораздо сложнее. Порой проще заново начать всю программу. А месяцы идут своим чередом. И кто это нам грозился написать программу за два месяца?

Боюсь, что многие, включая вашего покорного слугу, узнают себя в этих строках на каком-то этапе.

Как же действительно оптимально разрабатывать большой проект? Я не говорю здесь о проектах, которые затеваются для себя, в образовательных целях. Такие могут длиться годами, да так и не найти завершения. Меня интересуют принципы разработки проектов, которые нужны людям, которые будут кому-то полезны, у которых есть определённая "целевая аудитория". На самом деле, эти принципы известны уже давно и с успехом используются во многих софтверных компаниях. Однако большой проект - он и в Африке большой проект, и даже у одного разработчика. Поэтому давайте рассмотрим основные этапы разработки продукта. Эти этапы были позаимствованы на сайте фирмы ABBYY, которая известна нам по таким проектам как FineReader и Lingvo. Однако моей целью не было повторение этапов разработки проектов, используемых в больших компаниях, но адаптация тех принципов к ситуации, когда проектом занимаются от одного до десяти разработчиков, которые к тому же могут быть разделены расстоянием (жить в разных городах). Вот перечень этапов:

  • Выработка целей и требований к предмету разработки
  • Разработка предварительного (эскизного) проекта
  • Разработка детального проекта
  • Реализация
  • Тестирование

Глава 2: Техническое задание

Это и есть пункт первый - "Выработка целей и требований к предмету разработки". По большому счёту это задание должен писать конечный пользователь или заказчик программы. Желательно, вместе с программистом-разработчиком или координатором проекта.

В техническом задании на разработку программы указывается назначение программы. На этом этапе решаются следующие вопросы.

  • Для чего она разрабатывается?
  • Есть ли реальная потребность в такой программе?
  • Какие возможности будут у разрабатываемого проекта?
  • Чем он выгодно будет отличаться от конкурентов?
  • Кто входит в "целевую аудиторию" программы?
  • Какой уровень компьютерных знаний должен быть у пользователей?
  • Как много времени потребуется на обучение пользователя эффективной работе с этой программой?

Разработка эскизного проекта

Когда техническое задание определено, наступает этап разработки предварительного проекта.

Составляется общая архитектура системы, последовательность запуска и работы модулей, определяется общий "сценарий" работы программы и действия пользователя в ней. Общая система делится на подсистемы, отвечающие за определённые функции в проекте. Продумывается интерфейс взаимодействия этих подсистем. При этом по мере возможности минимизируются взаимосвязи между ними. Определяется, что будет поступать на вход этих подсистем и что мы должны получить в результате. Это позволит в будущем проектировать и кодировать подсистемы параллельно, что особенно важно при разработке каждой подсистемы отдельным специалистом или группой. Но даже одиночному разработчику это поможет просто и удобно организовать разработку модулей. И не придётся долго и мучительно вспоминать, что делал в последний раз, где остановился, и для чего всё это было вообще затеяно?

Разрабатывается эскизный проект интерфейса программы в целом и отдельных окон, диалогов и логических связей между ними. Проект интерфейса должен обеспечивать удобную работу пользователя с программой и реализацию всех возможностей, заявленных в техническом задании. Причём интерфейс должен быть интуитивно понятным. Возможности, опции и настройки программы должны быть классифицированы и размещены по степени важности, логике совместного использования и частоте обращения. Например, открывание и сохранение файлов обычно происходит довольно часто и используется при каждом запуске. Оно должно быть всегда "под рукой". А настройка тонкостей интерфейса или значений по умолчанию - обычно только при первом запуске. Это можно сделать в каком-нибудь отдалённом диалоговом окне. С другой стороны, настройки, общие по логике, могут быть разделены на основании частоты использования и глубины знаний пользователя. Например, установки размера символов текста, цвета символов, гарнитуры шрифта (внешнего вида шрифта) можно разместить недалеко в легкодоступном диалоговом окне. И там же сделать кнопку "Advanced" для "продвинутых" пользователей, которые хотят настраивать интерлиньяж (расстояние между строками), кернинг (расстояние между буквами), угол наклона букв относительно горизонтали строки, кодировку и т.п.

Подбираются соответствующие технологии и принципы, которые наиболее удобно позволят реализовать требования к программе и её функциональность. Определяются ориентировочные требования к компьютеру, на котором будет работать программа, в плане как аппаратного (процессор, память и др.), так и программного обеспечения (минимальная версия Windows, Internet Explorer, DirectX и т.п.). При этом, обратите внимание, мы ещё не написали ни строчки кода! Мы даже ещё не обговорили, на каком языке это всё будет написано. На данном этапе, это лишь абстрактная система. Её можно реализовать практически в любом языке.

И последнее замечание: к разработке эскизного проекта не рекомендуется привлекать более двух человек, иначе разработка проекта может затянуться до того, как они перестреляют друг друга, доказывая, что Си лучше Паскаля. Хотя ассемблер лучше любого другого языка.

Глава 3: Разработка детального проекта

На этом этапе общая идея о программе сформирована. Мы уже представляем, что хотим получить, как будет выглядеть внешне программа, и как будет работать в ней пользователь. Она разделена на модули и подробно описана.

Теперь мы уже должны определиться с языком программирования. На этом этапе разрабатываются форматы представления данных и, если требуется, собственные форматы файлов. Каждая выделенная подсистема разрабатывается детально отдельной группой или разработчиком, компетентным в сфере задач, выполняемых модулем. Продумываются алгоритмы и методы реализации требуемой функциональности. Для каждого модуля расписываются все функции, в какой последовательности они вызываются и за что отвечают. Могут потребоваться блок-схемы или другие визуальные способы отображения алгоритма работы подсистемы. На этом этапе уже можно начать писать программу. Но лишь следующим способом:

;шапка модуля
InitModule proc arg1:DWORD, arg2:DWORD
;эта процедура прежде проверяет наличие требуемых возможностей из структуры параметров компьютера
;устанавливаем фрейм обработчика исключений
;потом выделяем память для того и этого, согласно параметров структуры по адресу [arg1]
;и т.д.
	nop
	ret
InitModule endp

Так мы постепенно формируем костяк кода. В каждую процедуру, по мере разработки вписываем, что она должна выполнять в комментариях и в соответствующей последовательности, чтобы потом долго не мучиться при кодировании. Там же в комментариях можно писать идеи частей кода, которые приходят в голову по мере детализации, но только в комментариях. Не захламляйте свой код. Потом вы придумаете что-нибудь лучше или иначе. Пока блок закомментирован, можно его легко удалить. Компилятор и не узнает о ваших старых невоплощённых идеях, и не останутся случайно неиспользуемые переменные из-за того, что было сначала так, а потом обошлись без этой переменной, но убрать её из секции кода забыли. В RadASM-е легко комментируются и очищаются от комментариев блоки любой длины. Не торопитесь вписывать в свою программу код. Китайская пословица гласит: "Трижды подумай, потом действуй".

Кодирование

Да, вы дождались. Наконец-то мы добрались до кодирования! Как мы с удивлением осознаём, среди этапов разработки программы кодирование оказывается чуть ли не на последнем месте. Да, это так. Оглянитесь назад и посмотрите, сколько важной работы надо выполнить прежде, чем стоит приступать к реализации самой программы. Отсутствие этих этапов может оказаться губительным для большого проекта, и в конечном итоге времени на проект вы потратите гораздо больше, а результат будет хуже.

Главным критерием при разработке кода программы является удобочитаемость и ясность исходных текстов. Именно это, а не оптимальность кода. Если вы сделали такой оптимальный код, который никто, кроме вас не поймёт, то труд ваш был напрасен. Уже через месяц отвлечения от проекта вы займёте плотные ряды тех, кто не может прочесть и понять ваш собственный код.

Теперь о любимой всеми нами оптимизации. НЕ ОПТИМИЗИРУЙТЕ ВАШИ ПРОГРАММЫ! По крайней мере, на стадии разработки. Это можно сделать потом, после того, как программа готова и протестирована. Опять же, можно в функции оставлять заметки по оптимизации и куски компактного кода, но только в комментариях.

Практика показывает, что реально оптимизация может пригодиться в таких случаях, когда выполняются сложные вычисления или циклы. Изредка стоит оптимизировать выборку сообщений процедурой окна. И, тем не менее, не торопитесь. Вашу оптимизацию всё-равно вряд ли кто заметит в большинстве случаев, а вот непроходимость дебрей кода Вам обеспечена сразу. Оптимизировать всегда успеете. Напишите бета-версию неоптимизированную. Пусть её так погоняют. К тому же, именно первая версия самая трудоёмкая. Потом уже идут только надстройки, добавления, исправления.

В окончательной версии можно попробовать осторожно оптимизировать. За одно сразу увидите, насколько Ваша оптимизация реально приносит пользу. И когда проходите по своему исходному тексту в поисках возможностей оптимизации по скорости или размеру, не удаляйте неоптимизированный (но обычно более удобочитаемый) текст, но оставляйте его рядом в комментариях. Это позволит легко вернуть назад в случае ошибки или другой проблемы, а также поможет легче разобраться, что делает оптимизированный код. А как тяжело искать ошибки в оптимизированном коде!

Не бойтесь комментариев. Они очень важны. Они в значительной степени повышают читабельность текстов. Они хранят важную информацию о том, что тут у нас в исходном тексте происходит и зачем это нужно. Именно из-за комментариев в значительной степени исходные тексты так дороги и так охраняются. Листинг любой программы можно получить и от дизассемблера. Но вот разобраться в инструкциях и понять назначение кода, не так просто. И, в конце концов, вы начинаете в полученный листинг вписывать комментарии при разборе другой программы. И это правильно. Ведь человек думает не так, как компьютер, и комментарии призваны создать этот мостик между выражением чего-либо с точки зрения компьютера и человека.

Всё вышесказанное относится к коду, написанному на любом языке, но в особенной степени это относится к программам, написанным на ассемблере.

Ещё одним важным аспектом кодирования является выбор языка программирования. В принципе, мы должны были с этим определиться на предыдущем этапе. И я всё это время говорил, полагая, что мы пишем на ассемблере. Однако этого ещё не достаточно. Дело в том, что есть разные ассемблеры. (Я подразумеваю masm, fasm, tasm, spasm, hla и другие, а не .8086, .286, .386, .486 и т. д.) Выбирать стоит тот ассемблер, который всем в группе разработки наиболее понятен. Все должны писать на одном ассемблере, чтобы блоки легко между собой сочетались. Могут быть группы, в которых все - ярые поклонники tasm или fasm. Я не большой специалист в разных ассемблерах, но при выборе применяю следующие критерии: ассемблер должен быть максимально удобочитаемым (желательно с высокоуровневыми директивами типа ".if .else .endif"), но при этом не должен теряться контроль над каждым байтом кода. Иначе, какой смысл писать на ассемблере? Берите уж тогда C++.

Для себя я выбрал masm. На мой взгляд, он в наибольшей степени отвечает приведённым здесь требованиям. В случаях, когда нужен простой IF, нет смысла вручную городить "CMP EAX,EDX JNE @1" и вводить новый ярлык. Пусть компилятор сделает это сам. У него лучше получается. Он впишет тот же самый код, байт в байт, только самостоятельно. А мы в листинге напишем гораздо более удобную конструкцию ".if eax==edx .endif", которая при чтении очень красноречива. Подобным образом можно организовывать и циклы. Если компилятор не добавляет в конструкцию цикла что-нибудь лишнее, то можно воспользоваться стандартным вариантом. Но если требуется особенный цикл или цикл с оптимизацией, то всегда можно реализовать его просто командами ассемблера. Однако и в этом случае стоит вписать директивы цикла и закомментировать их, чтобы не мешали, но намекали на то, что здесь по логике закодирован цикл.

Таким образом, мы приходим к выводу, что главным критерием кода в листинге должна быть удобочитаемость. Это важно как для текущей разработки, так и для дальнейшего развития проекта.

В конечном итоге весь код собирается, организуется и компилируется в отдельные библиотеки или исполнимые файлы. На поверхностный взгляд всё сошлось и работает довольно устойчиво в течение уже 5 минут. Можно отдавать на тестирование.

Глава 4: Разработка документации

Этот этап сложно выделить в отдельный временной промежуток. В некоторой степени он может быть распределён по остальным. На первом этапе мы пишем техническое задание. На втором - эскизный проект-описание системы. Параллельно с кодированием и тестированием можно выполнять разработку ReadMe, описывающей и обучающей документации в каком-либо формате помощи (help).

Тестирование

Для успешного тестирования требуется составить план тестов. В такой план обычно входят следующие виды тестов:

  • Тестирование соответствия возможностей программы тому, что заявлено в техническом задании.
  • Тесты на устойчивость. Сюда входит тестирование на всех операционных системах, поддерживаемых программой, тестирование при различном аппаратном окружении, тестирование устойчивости работы программы в экстремальных условиях (когда забита до отказа память, когда система занята множеством тяжёлых процессов, когда идёт интенсивная запись на диск и т.п.).
  • Тестирование освоения программы неподготовленным пользователем.
  • Сравнительное тестирование с конкурирующими продуктами.
  • Тестирование читабельности и адекватности пользовательской документации.

Для тестирования подбирается определённая команда. В группе распределяются обязанности и требования. Во время тестирования находятся недоработки и сбои. Эти сбои следует сразу анализировать и исправлять. Кроме того, код, связанный с найденной ошибкой надо проверять дополнительно на подобные ошибки. При тестировании также часто возникают предложения по дальнейшему улучшению программы. Их нельзя бросаться и внедрять в текущую версию, ибо к совершенству можно стремиться всю жизнь. Улучшения и нововведения должны включаться в техническое задание на следующую версию. А в текущей версии должно быть только то, что заложено в техническом задании.

Заключение

Вот мы и дошли до конца технологического цикла разработки программы. Когда все этапы выполнены, можно подготавливать реализацию продукта и его внедрение в рабочий процесс, для которого он создавался.

Кроме того, любой современный серьёзный продукт обеспечивается службой технической поддержки. Поэтому в ReadMe стоит включить хотя бы адрес электронной почты службы поддержки этого продукта. Служба технической поддержки в некоторой степени служит "лицом" производителя. На вопрос о том, обратится ли к вам клиент, при другой подобной оказии в значительной степени зависит от уровня обслуживания клиента как до, так и после приобретения продукта.

Благодарю вас за внимание и желаю успешного кодирования и интересных проектов.

2002-2013 (c) wasm.ru