Представьте: вы настроили в боте два ограничения — сколько ответов дать пользователю до передачи менеджеру и сколько бессмысленных сообщений подряд терпеть. Кажется, что теперь диалог под контролем. Но в реальной эксплуатации ни один лимит не сработал ни разу. Знакомо? Эта история произошла в одном проекте, и мы разберём, почему так вышло, как найти подобные ошибки и что сделать, чтобы они не повторялись.
Две ошибки, которые маскируют друг друга
В коде проверки лимитов было два независимых дефекта. Первый — в условии срабатывания стояло логическое И вместо ИЛИ. То есть бот должен был передать менеджеру диалог только если одновременно превышены оба лимита: и по количеству ответов, и по числу бессмысленных сообщений. На практике такое совпадение почти невозможно, поэтому передача не срабатывала. Второй дефект — в ветке, которая должна была выполнять передачу, результат записывался в переменную, которую никто не читал. Даже если бы условие выполнилось, ничего бы не произошло. Две ошибки спрятали друг друга: тестировщики видели, что лимит не работает, но не могли понять, в чём дело, потому что исправление одной ошибки не меняло поведения — вторая всё равно блокировала результат.
Такие ситуации опасны тем, что создают иллюзию работающей системы. Вы добавляете новые проверки, но они тоже не срабатывают, и вы не замечаете подвоха, пока не случится инцидент. Поэтому так важно уметь изолировать логику и проверять каждый компонент отдельно.
Как вынести логику в чистую функцию
Решение, которое сработало в этом кейсе, — вынести всю логику проверки лимитов в отдельную чистую функцию. Чистая функция не зависит от внешнего состояния, не пишет в базу и не отправляет сообщения. Она просто принимает на вход данные (например, количество ответов, число бессмысленных сообщений, текущие настройки лимитов) и возвращает решение: передавать менеджеру или нет. Такой подход даёт несколько преимуществ:
- Логику легко протестировать: вы подаёте разные комбинации входных данных и проверяете результат.
- Ошибки в условиях становятся очевидными, потому что нет побочных эффектов, которые могли бы их замаскировать.
- Функцию можно переиспользовать в разных местах без дублирования кода.
В нашем случае после выноса логики в чистую функцию удалось написать 16 тестов, которые покрыли все граничные случаи: когда превышен только первый лимит, только второй, оба сразу, ни один, а также ситуации с нулевыми и максимальными значениями. Тесты сразу показали, что условие с И не работает так, как задумано, и что ветка передачи не влияет на результат.
Где прячутся ошибки на единицу
Отдельная головная боль — ошибки на единицу (off-by-one). В формулах лимитов легко ошибиться, считая количество сообщений или ответов. Например, если лимит «не более 5 ответов», то при счётчике от 0 до 4 условие срабатывания должно быть «больше или равно 5», а не «больше 5». Или наоборот: если считать с 1, то условие меняется. В нашем боте такие ошибки тоже могли бы возникнуть, но после выноса в чистую функцию и написания тестов мы явно зафиксировали, как считаются сообщения и когда именно срабатывает лимит. Вот несколько типичных мест, где прячутся ошибки на единицу:
- Сравнение счётчика с порогом: использовать > или >= в зависимости от того, с какого значения начат отсчёт.
- Индексация в массивах: если лимит на количество элементов, не забывайте, что индексы начинаются с 0.
- Подсчёт «подряд идущих» сообщений: сброс счётчика при осмысленном сообщении должен происходить до или после проверки лимита — это влияет на результат.
Чтобы избежать таких ошибок, всегда явно описывайте в тестах, что считается «одним ответом» и «одним бессмысленным сообщением», и проверяйте граничные значения: 0, 1, лимит, лимит+1.
Чек-лист для проверки своих лимитов
Если у вас в боте или другом сервисе есть настраиваемые лимиты, пройдитесь по этому списку:
- Проверьте логические условия: нет ли там И вместо ИЛИ или наоборот? Убедитесь, что условие срабатывания соответствует бизнес-правилу.
- Убедитесь, что результат проверки используется: переменная, в которую пишется решение, должна читаться в том месте, где выполняется действие.
- Вынесите логику в чистую функцию и напишите тесты на все комбинации входных данных, включая граничные.
- Явно определите, как считаются сообщения и ответы, и проверьте ошибки на единицу.
- Добавьте логирование срабатывания лимитов, чтобы в реальной эксплуатации видеть, когда и почему они срабатывают.
Следуя этим шагам, вы сможете избежать ситуаций, когда лимиты существуют только на бумаге. А если ошибка уже есть, чистая функция и тесты помогут быстро её локализовать и исправить.