Сайт Романа ПарпалакаБлог20180529

Исправляем баги с помощью рефакторинга

29 мая 2018 года, 23:42

Василий Половнёв в советах рассказывает, как исправлять баги:

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

Когда проблема гарантированно повторяется, можно приступать к изолированию: искать причину проблемы, генерируя и проверяя гипотезы, отрезая всё, не относящееся к проблеме.

Когда причина обнаружена, остаётся устранить баг и порефлексировать: что пошло не так, почему, как сделать так, чтобы в будущем таких багов не было.

Написано верно. Такому алгоритму и нужно следовать, исправляя баги. Но что делать, если баг воспроизвести нельзя? Я расскажу об одном таком баге, с которым пришлось бороться в CityAds.

Симптом

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

Попытка воспроизведения

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

Изучение кода

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

$_SESSION['sms_codes'][$account_id]['code'] = $code;
$_SESSION['sms_codes'][$account_id]['time'] = time();
$_SESSION['sms_codes'][$account_id]['amount'] = $amount;

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

Говнокод плох тем, что он «одноразовый». Его просто написать, но сложно понимать и изменять.

Логирование

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

Я залогировал значения переменных и увидел, что к моменту проверки кода в сессии было пусто, как будто код вообще не генерировался. Тем не менее, другие значения, не связанные с смс-кодами, в сессии присутствовали. Я не смог найти причину очистки. Код на серверах был одинаков. Сравнение конфигурации PHP ничего не дало.

Гипотеза

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

Я решил не искать, кто именно портит данные в сессии, а с нуля переписать обработку смс-кодов и уйти от хранения данных в сессии.

Рефакторинг

К этому времени мы подключили Symfony и писали новый код по принципам SOLID. Я написал сервис, в котором инкапсулировал логику работы с смс-кодами. Сервис принимал тройки значений (code, time, amount) в виде одного объекта (DTO), сохранял их в кеше (у нас был мемкеш) и при необходимости возвращал. Старый код через синглтон обращался к DI-контейнеру и доставал оттуда сервис.

Переписанный код сразу заработал как нужно. После выкладки оказалось, что проблема на бразильском сервере исчезла.

Ссылки по теме

    Оставить комментарий
Поделиться
Записи