Два бага, которые врали мне в отчёте, и один, которого не было

Мне 19, я делаю маленькие продукты и разбираюсь по ходу. Разбор трёх поломок в собственной рассылке: две ничего не уронили и просто показывали неправильные цифры, а третья при проверке оказалась не поломкой.

Была рассылка по холодной базе: ~7000 организаций, из них 917 с почтой, 910 готовых писем. Аналитика на лендинге, метки на ссылках, всё вроде работает. Три вещи выглядели сломанными. Ни ошибки, ни падения, ни одного красного лога — и, как выяснилось, сломаны были не все три.Была рассылка по холодной базе: ~7000 организаций, из них 917 с почтой, 910 готовых писем. Аналитика на лендинге, метки на ссылках, всё вроде работает. Три вещи ломались молча. Ни ошибки, ни падения, ни одного красного лога.
Счётчик кликов стоял на одной кнопке из четырёх

Атрибут data-cta="buy" висел только на кнопке в середине страницы. На первом экране, в шапке, в футере — нет. Клики оттуда просто не считались.

В метрике это выглядело как «конверсия низкая, оффер слабый». А на самом деле — да непонятно, как оно выглядело, три четверти кнопок молчали. Я успел засесть за переписывание текста лендинга, прежде чем дошло.

Если метрика говорит «плохо» — сначала убедись, что она вообще считает. Иначе чинишь продукт по показаниям сломанного прибора.

Аналитика молча игнорировала мои же проверки

Проверял счётчик headless-браузером. События не появлялись. Довольно долго выяснял, почему не работает.

А оно работало. Аналитика просто отбрасывает запросы с navigator.webdriver и со словом Headless в user-agent — увидела бота и промолчала. Штатное поведение, ошибки никакой. Проверять надо с подменённым UA.

Заодно в статистике осело несколько моих же тестовых хитов с меткой utm_source=verify, теперь их вычитать. Тестовый трафик лучше метить сразу.

А теперь третий, которого не было

Три варианта текста письма, раскидываю по адресатам хешем. Хеш самый классический, из учебника: h = h * 31 + код_символа, потом h % 3.

И тут я заметил, что 31 ≡ 1 по модулю 3. То есть умножение на 31 по модулю 3 не перемешивает ничего — оно равносильно прибавлению единицы. Остаток должен выродиться почти в линейную функцию от суммы кодов символов, а адреса в базе похожи друг на друга как близнецы. Значит распределение по трём вариантам обязано перекоситься. А одинаковый текст веером — это ровно то, по чему письма и опознают как массовые.

Красивое рассуждение. Я переписал хеш на FNV-1a с финализатором, отметил в комментарии, что починил доставляемость, и пошёл дальше довольный собой.

А на днях решил замерить, насколько было плохо. Замерил:

Два бага, которые врали мне в отчёте, и один, которого не было

На всей базе старый хеш даёт 309/317/291. Ровная треть. Никакого вырождения.

Дальше интереснее. На тридцатке — той самой, которой реально отправляют, потому что холодную почту шлют батчами по десять-двадцать в день — старый хеш дал 10/16/4. Перекос налицо. Только новый, «правильный», на той же тридцатке дал ровно то же самое: 10/16/4. При том что хеши разные, у них первые же значения расходятся.

Значит перекос не от хеша. Это обычный разброс: тридцать штук по трём корзинам — 10/16/4 совершенно нормальный результат для любого хеша, хоть идеального.

Почему рассуждение не сработало: оно верно для суммы, которая никуда не переполняется. А в JS h = (h * 31 + c) | 0 переполняет 32-битное целое на каждом втором символе, и переполнение перемешивает биты достаточно, чтобы к моменту % 3 от стройной теории ничего не осталось.

Хеш, впрочем, оставлю новый. Он не хуже, а рассуждение про модуль верное — просто в этих условиях не проявляется.

Что тут общего

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

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

Сработает ли сама рассылка — пока не знаю. Напишу, когда будут ответы. Или когда станет ясно, что их ноль. Пишу такие разборы раз в неделю, тут и в телеграме: t.me/markbielski

1