DM Guide 19

Версия от 23:02, 14 ноября 2015; IXVI (обсуждение | вклад) (→‎О порядке написания кода: последняя правка. Хорошо бы какой-нибудь кодер проверил и упорядочил главы и содержание. Я не кодер.)

В разработке…


Jobeng.png
Данная статья помечена как неоконченная. Это означает, что статья находится на доработке, поэтому является неверной или неактуальной.

Вы можете помочь проекту Onyxyeye@256x256.png Onyx и сообществу Animus-logo.png SS13 в целом — зайдите на наш Bus Mainframes.gif Портал сообщества.


Это 19 глава перевода оригинального руководства по Dream Maker от разработчиков.

Остальные главы руководства и статьи о программировании на коде BYOND от любителей на русском.

Оригинальная 19 глава руководства на английском языке


Chapter 19

Управление кодом в Больших Проектах

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

Также разделение кода по различным файлам делает ваши наработки более мобильными - их можно вставлять в другие проекты\сборки\заготовки. Такие файлы кода часто называют библиотеками (как dll) и вы можете найти множество других библиотек, а также делиться своими собственными.

Включение файлов (оператор #include)

Содержимое одного файла может быть свободно включено в другой, благодарю использованию команды #include
Есть две формы записи данного оператора, в зависимости от способа поиска включаемого содержимого. В первом случае компилятор будет искать файл в директории /library, а в другом случае он обратится к текущей директории и если не найдёт искомый файл, то к /library. Например

#include <libfile>
#include "dmfile"

libfile - это включаемая библиотека. "dmfile" - это файл исходного кода. Если один и тот же файл включается несколько раз, то проводится только первый запрос. Делается это для предотвращения повторов данной библиотеки кода, которую запрашивают несколько файлов в одном проекте.

Все проекты по умолчанию включают файл <stddef.dm> в начало кода. Это библиотека, описывающая несколько постоянных переменных и определителей.

Помимо включения файлов типа .dm , команда #include также используется для прикрепления карт уровней (например.dmm) к проекту. Синтакс(написание) оператора точно такое же. Карты уровней вставляются в главную карту мира и распределяются по z-уровню. Икс и Игрик координаты автоматически определяются по размеру наибольшей из вставляемых карт.

Рисунок 19.28: Перед Пре-процессингом

Когда вы используете Dream Maker для управления своим проектом, вам крайне редко придётся использовать сами операторы #include и #define FILE_DIR macros. В Dream Maker эти функции реализуются через сам интерфейс.

Как упоминалось ранее, кодовые файлы и файлы карт добавляются через чек-бокс в диспетчере файлов. Привязка файлов происходит также. Все файлы в директории вашего проекта и внутри неё автоматически размещаются без добавления путей к ним. И путь к каждому файлу в Dream Maker упрощается до названия самого файла, например: world/icons/mobs/warrior.dmi = warrior.dmi

В некоторых случаях применение команды #include размещает файл на исполнение выше, чем это указанно в коде. Подробнее об этом в разделе 19.3.1.

Препроцессор

Команда #include - одна из нескольких "препроцессорных" команд. Опознать их можно по символу "#" и последовательному расположению в коде (Препроцессор Dream Maker в данном случае подобен компиляторам C и C++. Также эти люди называют команды "препроцессорными директивами). Препроцессор управляет данными командами, пока файл кода начинает исполняться и может изменять поведение файлов, в зависимости от распознавания их компилятором. Другие препроцессорные команды изложены ниже:


оператор #define

Команда #define создаёт макрос(префикс), или иначе говоря переменную в препроцессоре. Любые участки с данным префиксом в коде будут замещены содержанием данного макроса. В местах, где макрос является частью другого слова или используется в текстовом описании, команда не сработает. Примеры:

#define Name Value

Name - название макроса(префикса). Value - значение макроса(переменной).

Команды препроцессора заканчивают исполнение по завершении строчки кода, в которой они описаны. Если вы хотите расписать свой код на несколько строк, используйте символ \ в конце строки.

Название макроса может состоять из заглавных и строчных букв, цифр и символа подчёркивания, но не может начинаться с цифры. По умолчанию НАЗВАНИЕ МАКРОСОВ часто набирают исключительно заглавными буквами.

Также можно придавать макросу значения аргументов, для более точного применения их в коде:

#define Name(Arg1,Arg2,...) Value

Arg1 - название первого аргумента. Arg2 - название второго аргумента, и т.д. Перечисляемые аргументы будут найдены в коде, и им будет подставлено значение(value) данного макроса. Считается что это процедурный уровень, но так как он оперирует текстовыми значениями, его возможности более обширны и гибки.

Использовать макросы в выражениях следует с осторожностью - компилятор может не успеть посчитать вставленный из макроса текст, особенно в сочетании с другими текстовыми значениями кода. Для безопасности вы можете взять значения макроса в круглые скобки #define Macro (Value) - чтобы значение не пересекалось с другим исполняемым кодом.

Вот пример кода, применяемый для решения проблем с оператором сдвигом разряда << ,без затрагивания остальной части кода после вставления макроса.

#define FLAG1 (1<<0)
#define FLAG2 (1<<1)
#define FLAG3 (1<<2)

Специальные макросы

Есть несколько макросов, имеющих определённые специальные значения.

FILE_DIR

Макрос FILE_DIR - определяет поисковый путь для ресурс-паков, включённых в ваш проект. Может быть использван(переопределён) многократно, каждый новый заданный путь просто будет добавлен в другим путям поискам.

  1. define FILE_DIR Path

Path - путь к ресурс-пакам. Используя данный макрос, вы сможете сократить пути поиска ваших ресурсов компилятором. Просто заранее введите возможные пути расположения файлов, и в дальнейшем набирайте только имя искомого файла(ресурс-пака).

Как пример - задание макросов для поиска спрайтов(icons) и звуков(sounds):

#define FILE_DIR icons
#define FILE_DIR sounds

DEBUG

Макрос DEBUG включает добавление дополнительной информации в файл .dmb ,что плохо сказывается на скорости исполнения процедур кода, но построчно записывает информацию о выполнении всего кодового файла - в том числе где возникают ошибки, баги и отчего крашится код.

#define DEBUG

Просто добавьте эту строчку в ваш код, и режим Debug будет включен.


__FILE__

Макрос __FILE__ показывает файл в котором распологается исполняемый в данный момент код. Полезный макрос для создания Debug-сообщений.


__LINE__

Макрос __LINE__ показывает строчку кода, которая исполняется в данный момент. Тоже полезен для выведения Debug-сообщений. Вот пример:

proc/MyProc()

  //Сейчас крашнется.
  world.log << "[__FILE__]:[__LINE__]: We got this far!"
  //Нет? Странно...

DM_VERSION

Макрос DM_VERSION показывает номер версии вашего компилятора. Используется для определения устаревшей версии кода, и замены некоторых операторов на новые.


#undef

Макрос #undef убирает заранее заданный макрос. Его можно расположить после исполненного макроса для переназначения значений, а также в конце кодового файла, чтобы данный макрос не перешёл на исполнение в другие файлы.


Компиляция по условиям (Условная компиляция)

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

Вот данные макросы условий для компиляции.

#ifdef

Команда #ifdef разрешает компилировать последующий код, только если определённому макросу было задано определённое значение. Выполнение данной команды завершается оператором #endif

#ifdef Macro

//Условие для дальнейшего выполнения кода.

#endif

Также существует команда #ifndef - она срабатывает если значение определённого макроса не было задано.

Например макрос DEBUG можно включать для определённого участка кода при помощи #ifdef :

#ifdef DEBUG
mob/verb/GotoMob(mob/M in world)
set category = "Debugging"
usr.loc = M.loc
#endif

#if

Команда #if - более общая версия #ifdef, так как включает определения не только макросов но и констант. То есть если выражение совпадает (true), то код внутри #if будет скомпилирован, если выражение не совпадает (false), то код не будет выполняться. В случае невыполнения условий применяется команда #elif, а в случае невыполнения никаких условий из заданных вами, используется команда #else.

#if Условие

//Код условия.

#elif Условие

//Код условия.

#else

//Код условия.

#endif

Условия могут состоять из базовых (простых) операторов, или логических (boolean) операторов. После проверки данного макроса выдаётся результат = 1 , или = 0

defined (Macro) Macro - название макроса. Выдаёт 1 (Returns 1) если макрос определён и 0, если не был. Часто команда #if используется для блокировки выполнения куска кода - в случае если происходит краш, или нужно выключить какую-либо фичу без выпиливания куска кода. Вот пример выполнения кода:

#if 0
//Неактивный код.
#endif

Так как Dream Maker поддерживает вложенность команд одна в другую, также возможно взять ненужный\неиспользуемый на данный момент кусок кода в /* комментарий */ - используя разметку /* */ Оператор #if привычен для использования программистами С, так как в этом языке компилятор плохо поддерживает вложенность команд.

#error

The #error command stops compilation and displays the specified error message. Library writers can use this to tell the user of the library if something is wrong.

#error Message

The following example will refuse to compile if the DM macro is not defined.

#ifndef DM
#error You need to define DM as the name of your key!
#endif

Типичные проблемы в больших проектах

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

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

Понятие модуля применимо не только к какой-либо части кода, а чаще подразумевает файл или группу файлов. Открытая часть модуля - процедуры, переменные и объекты, свободные для использования в других местах помимо самого модуля. Закрытая часть модуля - это собственно тело самого модуля, сам код, который однокомпонентен.

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


О порядке написания кода

Во многих случаях, последовательность или порядок кода в Dream Maker не имеет особого значения(sic!). Например процедура, переменная или тип объекта могут быть заданы до или после их прямой компиляции. Это непривычная особенность, отличающая код DM от других, где порядок написания имеет важное значение при исполнении программ.

Последовательность кода важна в нескольких случаях:

  • Препроцессор читает код исключительно последовательно - с первой строчки до последней. Поэтому макросы (значения макросов) , которые запрашивают препроцессорные команды, должны быть записаны в коде ранее препроцессорных команд. В данном случае более удобно использовать переменные(variables).
  • Последовательность кода важна при наследовании процедур или переменных объекта. Если какая-либо процедура повторяется несколько раз, то она наследует последовательность выполнения первого раза.

Например, при добавлении дополнительных функций к процедуре client.Topic() в разных местах в коде случается эффект сочетания зачений в родительском элементе:

client/Topic(T)

if(T == "вступление")
usr << "И вот как-то раз..."
else ..()

client/Topic(T)

if(T == "подсказка")
usr << "Буквально позавчера."
else ..()

В таком написании, эти два определения процедуры client/Topic(T) могут быть размещены в коде в любом месте. Если один из них будет выполнен и приведёт к значению ..() ,то другой уже не наступит. Вот в таких случаях и бывает полезно обращаться к процедуре-родителю

Отладка кода

Или иначе дебаггинг(debugging).

Манеры хорошего кодера

Ошибка начинающих программистов в том, что они слишком полагаются на компилятор. Опытные баголовы и багоюзеры знают, что если код скомпилирован, то не факт что он работает как написано. Уязвимости бывают везде.

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

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

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

"Неуловимые" баги

Существует два типа багов: Краш-баги которые останавливают процесс целиком (proc crashers) и мелкие баги(silent errors), которые ломают отдельные фичи. : Пример Краш-бага - обращение к переменной объекта, отсутствие ответа от объекта, придание переменной по умолчанию равной нулю, что приводит к завершению оператора. Когда крашится процедура, как правило сохраняются логи ошибки в world.log . При запуске созданного вами проекта в клиенте Dream Seeker, эта информация будет показана в отдельном окошке. При запуске проекта на сервере Dream Daemon - логи сохраняются в выводе серверной информации, либо в отдельном файле.

Самое главное - найти в логах название процедуры, которая вызвала падение выполнения кода, а также значение переменных src и usr на момент выполнения процедуры. Процедуры могут быть показаны цепочкой вызовов - будут показаны обращающиеся к этой и вызванной этой процедурой процедуры.

world.log << "[__LINE__]: myvar = [myvar]"

Отладка кода проводится как при отдельных случаях падения кода(и тогда удобнее пользоваться примерами приведёнными выше), так и по всем исполняемым строкам и файлам при финальной диагностике. В последнем случае вы можете пользоваться следующими макросами:

#ifdef DEBUG
#define debug world.log
#else
#define debug null
#endif

Вывод информации на отладку будет осуществляться, но он будет игнорироваться до тех пор, пока вы не используете макрос #DEBUG.

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