Доступ к готовым решениям

Переход в группу "Пользователь"

300.00
Одноразовый платёж
Быстрый переход в группу "Пользователи", без надобности написания постов и ожидания.

Покупка дает возможность:
Быть полноправным участником форума
Нормальное копирование кода
Создавать темы
Скачивать файлы
Доступ к архиву Pawno-Info

Урок Как найти баг / недоработку / ошибку в коде

oxygenium

Изучающий
Professional
Пользователь
Регистрация
5 Ноя 2013
Сообщения
957
Лучшие ответы
18
Репутация
445
Награды
3
Всем привет. В этой теме я расскажу о простом способе поиска багов в Вашем коде. Для меня стал удивлением тот факт, что 99% пользователей, которые задают вопросы, не знают об этом простом способе.

Введение

И так, представим что Вы написали уникальную систему для вашей очередной уникальной копии Аризоны. Код скомпилировался без ошибок и варнингов, и вроде бы даже работает. Но работает он не так, как было задумано. Что же делать в таком случае? Все просто, добавлять отладку.

Вам понадобятся всего 2 функции -
print и printf. Что они делают? Выводят сообщение в консоль. Можно провести аналогию с функцией SendClientMessage. Только SendClientMessage выводит сообщение в чат, а print в консоль.

print

Пример: добавим в
public OnGameModeInit функцию print с произвольным текстом

C-like:
public OnGameModeInit()
{
    print("Моя доработка аризоны загружена успешно!");
    return 1;
}

Скомпилируем и запустим сервер. При запуске сервера открывается консоль, но думаю это вы и так знаете. В эту же консоль будет выводить текст функция
print.

По итогу в консоли мы увидим следующее:

Моя доработка аризоны загружена успешно!


При запуске сервера вызвался
public OnGameModeInit и наша функция print, которая и вывела сообщение в консоль.

Что? И как это поможет найти ошибку?

Тут мы плавно подходим к самому простому варианту поиска неработающего или работающего, но не так как нужно кода. Я бы конечно назвал это гордым словом breakpoint, но к сожалению на сегодняшний день у нас с вами нет таких мощных инструментов для отладки. Ближе к теме. И так, скажем у нас есть некая абстрактная команда, который выдает нам любой аксессуар, пусть это будет шляпа, или попугай - не важно. Весь код, написанный ниже и далее по теме будет максимально плохим во всех его проявлениях. Но - он будет максимально реалистичным, и если посмотреть темы с вопросами, то большинство кода там будет именно таким.

C-like:
cmd:datigrokuaksy(playerid, params[])
{
    if(sscanf(params, "dd", params[0], params[1]))
        return SCM(playerid, RANDOMCOLOR, "Введите: "BELIY_CVET"/datigrokuaksy {ID игрока} {ID аксессуара}");

    if(fulldostup(playerid)) return 0;

    AttachAksNaIgroka(params[0], params[1]);

    new stringer[4096];
    format(stringer, 2048, ""ZELENIY_CVET"Администратор %s выдал вам аксессуар \"ШЛЯПА\"", GetImyaIgroka(playerid));
    SCM(params[0], BELIY_CVET, stringer);
    return 1;
}
Здесь мы будем использовать
print после каждого условия, чтобы понять, выполняется оно или нет. Если сообщение в консоли появилось - условие прошло. Если нет - нет. Добавляем print.

C-like:
cmd:datigrokuaksy(playerid, params[])
{
    print("Команда вызвана");

    if(sscanf(params, "dd", params[0], params[1])) 
        return SCM(playerid, RANDOMCOLOR, "Введите: "BELIY_CVET"/datigrokuaksy {ID игрока} {ID аксессуара}");

    print("Проверка sscanf прошла");

    if(fulldostup(playerid)) return 0;

    print("Проверка на фулл доступ прошла");

    AttachAksNaIgroka(params[0], params[1]);

    print("Аксессуар был прицеплен к игроку");

    new stringer[4096];

    print("Переменная была создана"); // Тут print не обязательна, просто для примера

    format(stringer, 2048, ""ZELENIY_CVET"Администратор %s выдал вам аксессуар \"ШЛЯПА\"", GetImyaIgroka(playerid));

    print("Отформатировали строку"); // Тут print не обязательна, просто для примера

    SCM(params[0], BELIY_CVET, stringer);

    print("Команда выполнена успешно");
    return 1;
}
Мы добавили
print после каждой строчки кода и теперь в консоли мы сможем наблюдать за его выполнением.

Запускаем сервер, заходим в игру и вводим команду /datigrokuaksy. Мы видим сообщение в чате "Введите /datigrokuaksy {ID игрока} {ID аксессуара}".

В консоли мы увидим следующее:

Команда вызвана

А где остальные сообщения? - спросите вы. В этом случае все верно, потому что первом условии у нас есть
return, который пропускает весь код ниже. Тем самым можно понять, что условие со sscanf выполнилось. Теперь проверим, выполняется ли условие со sscanf с аргументами.

Вводим /datigrokuasky 24 10. И видим что в чате сообщения не было. А у игрока с ID 24 шляпы так и не появилось. Открываем консоль и видим там следующее:

Команда вызвана
Проверка sscanf прошла



Проверка sscanf прошла, а на фулл доступ не прошла. Как же так? Ведь я добавил себя в БД, во все define да и вообще сервер мой и я заплатил за его хостинг 80 рублей. А у меня не работает главная фишка Аризоны.

Теперь включаем логику - если в консоли не было сообщения о том, что проверка на фулл доступ прошла - значит проблема именно в нем. Смотрим в чем же у нас проблема. Функция
fulldostup возвращает 1, если доступ есть и 0, если доступа нет. В нашем случае доступ есть и функция вернула 1. Условие стало true и выполнился результат - return. Весь код ниже пропущен. В данном случае был пропущен "!" перед условием, добавляем и смотрим.

C-like:
cmd:datigrokuaksy(playerid, params[])
{
    print("Команда вызвана");

    if(sscanf(params, "dd", params[0], params[1]))
        return SCM(playerid, RANDOMCOLOR, "Введите: "BELIY_CVET"/datigrokuaksy {ID игрока} {ID аксессуара}");

    print("Проверка sscanf прошла");

    if(!fulldostup(playerid)) return 0; // Исправление здесь

    print("Проверка на фулл доступ прошла");

    AttachAksNaIgroka(params[0], params[1]);

    print("Аксессуар был прицеплен к игроку");

    new stringer[4096];

    print("Переменная была создана"); // Тут print не обязательна, просто для примера

    format(stringer, 2048, ""ZELENIY_CVET"Администратор %s выдал вам аксессуар \"ШЛЯПА\"", GetImyaIgroka(playerid));

    print("Отформатировали строку"); // Тут print не обязательна, просто для примера

    SCM(params[0], BELIY_CVET, stringer);

    print("Команда выполнена успешно");
    return 1;
}
Компилируем, перезапускаем сервер и заходим. Вводим команду /datigrokuasky 24 10 и счастливый игрок с ID 24 получает свою шляпу. Все ошибки исправлены и команда отработала так, как и было задумано.

В консоли видим следующее:

Команда вызвана
Проверка sscanf прошла
Проверка на фулл доступ прошла
Аксессуар был прицеплен к игроку
Переменная была создана
Отформатировали строку
Команда выполнена успешно



Теперь разберем более простой пример

C-like:
new a = 1;
new b = 4;

if((a + b) == 5) print("a + b = 5");
if((b - a) == 0) print("b - a = 0");
if(a == 1) print("Значение a = 1");
if(b == 2) print("Значение b = 2");
Если мы запустим этот код, то в консоли мы увидим следующее:

a + b = 5
Значение a = 1
И так, переменная a у нас равна 1, переменная b - 4.

Первое условие. Если a (1) + b (4) = 5, условие
true, т.к. 1 + 4 действительно равно 5. Выводим сообщение в консоль.
Второе условие. Если b (4) - a (1) = 0, условие
false, т.к. 4 - 1 = 3, а не 0. Соответственно условие не выполнено и функция print вызвана не будет.
Третье условие. Если а (1) = 1, условие
true, 1 действительно равен 1. Функция print будет вызвана.
Четвертое условие. Если b (4) = 2. 4 не равно 2, условие
false, print не будет вызвана.

На этом с функцией print закончим. Мы поняли, что она выводит сообщение в консоль и с помощью нее мы можем проверить, выполняется ли какое-нибудь условие или функция.


printf

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

Функция printf принимает 2 аргумента - это текст и специфицаторы, такие же, как и в функции format

%i, %dЦелое число
%sСтрока
%fЧисло с плавающей точкой, в pawn переменные данного типа начинаются с тега Float:
%cASCII символ, принимает в себя целое число, которое является номером символа. Подробней вы можете ознакомиться тут: https://ru.wikipedia.org/wiki/ASCII
%xЧисло в шестнадцатеричной системе счисления
%bЧисло в двоичной системе счисления
%%Знак процента (%)
%qЭкранирование специальных символов для SQLite (Начиная с версии 0.3.7)

Разберем простой пример

C-like:
new a = 1;
new b = 4;

printf("Значение a - %i, b - %i", a, b);
Результатом выполнения кода выше будет сообщение в консоль:

Значение а - 1, b - 4


Как мы видим, все просто. Синтаксис практически такой же, как у функции
format, исключая создание переменных. Теперь давайте усложним задачу и возьмем код из первого примера.

C-like:
cmd:datigrokuaksy(playerid, params[])
{
    print("Команда вызвана");

    if(sscanf(params, "dd", params[0], params[1]))
        return SCM(playerid, RANDOMCOLOR, "Введите: "BELIY_CVET"/datigrokuaksy {ID игрока} {ID аксессуара}");

    print("Проверка sscanf прошла");

    if(!fulldostup(playerid)) return 0;

    print("Проверка на фулл доступ прошла");

    AttachAksNaIgroka(params[0], params[1]);

    print("Аксессуар был прицеплен к игроку");

    new stringer[4096];

    print("Переменная была создана");

    format(stringer, 2048, ""ZELENIY_CVET"Администратор %s выдал вам аксессуар \"ШЛЯПА\"", GetImyaIgroka(playerid));

    print("Отформатировали строку");

    SCM(params[0], BELIY_CVET, stringer);

    print("Команда выполнена успешно");
    return 1;
}
Вводим команду и видим что ничего не работает. Смотрим в консоль:

Команда вызвана
Проверка sscanf прошла



Что? Я же выдавал себе фулл доступ. В чем же проблема?

Тут то нам и поможет функция printf, которая сможет показать нам результат выполнения функции. Как мы помним, функция fulldostup возвращает 0 или 1. 0 и 1 - это целое число. Спецификатор для него будет %i или %d, без разницы.

Из старой темы:

%i от %d отличается тем, что %i может принимать числа как в привычной нам десятеричной системой счисления (цифры от 0 до 9), так и в 16-ти и 8-миричных системах счисления.
Лично я использую %i, т.к. это у меня ассоциируется с integer. Но опять же, в реальных задачах никакой разницы нет.

Наша функция будет следующего вида:

C-like:
printf("Результат выполнения функции fulldostup для игрока ID %i, - %i", playerid, fulldostup(playerid));
Теперь такой момент. В каком месте нам ее разместить. Ответ - в любом, до условия, которое не выполняется. Условие с функцией
fulldostup у нас не выполняется, соответственно после него размещать нашу printf смысла просто нет - не выполнится условие, не выполнится и функция. Я размещу перед условием.

C-like:
cmd:datigrokuaksy(playerid, params[])
{
    print("Команда вызвана");

    if(sscanf(params, "dd", params[0], params[1]))
        return SCM(playerid, RANDOMCOLOR, "Введите: "BELIY_CVET"/datigrokuaksy {ID игрока} {ID аксессуара}");

    print("Проверка sscanf прошла"); // Последнее, что мы видим в консоли

    // Вот наша функция printf
    printf("Результат выполнения функции fulldostup для игрока ID %i, - %i", playerid, fulldostup(playerid));

    if(!fulldostup(playerid)) return 0; // Это условие уже не выполняется

    print("Проверка на фулл доступ прошла");

    AttachAksNaIgroka(params[0], params[1]);

    print("Аксессуар был прицеплен к игроку");

    new stringer[4096];

    print("Переменная была создана");

    format(stringer, 2048, ""ZELENIY_CVET"Администратор %s выдал вам аксессуар \"ШЛЯПА\"", GetImyaIgroka(playerid));

    print("Отформатировали строку");

    SCM(params[0], BELIY_CVET, stringer);

    print("Команда выполнена успешно");
    return 1;
}
Запускаем, вводим команду /datigrokuaksy 24 10, опять же ничего не происходит. Смотрим в консоль:

Команда вызвана
Проверка sscanf прошла
Результат выполнения функции fulldostup для игрока ID 23, - 0



И видим что проблема в нашей функции. Смотрим - а фулл доступ то мы себе и не дали, т.к. мы уже скачали новую копию аризоны и еще не настроили ее.

Вот так просто с помощью функций print и printf можно находить ошибки и исправлять их самостоятельно, без создания темы с вопросом и ожиданием ответа сутками.
 
Последнее редактирование:
Сверху Снизу