Баги7 мин чтения2026-03-06

Поиск бага: как находить ошибки, которые ломают продукт

Когда я работал в Яндексе, мы ежедневно сталкивались с багами, которые проскакивали мимо всех проверок. Помню один случай — в продакшене обвалилась часть сервис

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

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

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

Что такое баг и почему его сложно найти

Баг — это не просто ошибка в коде. Это поведение программы, которое отличается от ожиданий пользователя или спецификации. Может быть логическая ошибка, утечка памяти, состояние гонки, баг безопасности — много вариантов.

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

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

На одном проекте я видел, как баг воспроизводился только если отправить запрос ровно в момент, когда в базе запускается миграция. Вероятность — примерно 1 на 10 000. Нашли его случайно, когда смотрели логи в продакшене.

Ручное тестирование vs автоматизация

Честно? Нужны оба подхода. Но в разных пропорциях, чем думают большинство.

Ручное тестирование хорошо для:

Автоматизация находит то, что ручное тестирование пропустит:

По статистике, автоматизированные тесты ловят примерно 70-80% багов. Ручное тестирование добавляет ещё 15-20%. Остальное — это мониторинг в продакшене и feedback от пользователей.

Вот почему у нас в Distiq — автоматический код-ревью анализирует каждый MR перед тем, как разработчик его вообще мержит. Это ловит много проблем на самом раннем этапе.

Как искать баги: практические методы

1. Граничные случаи (Boundary Value Testing)

Это первое, что нужно проверить. Если функция работает с числами, проверь:

Классический пример — баг с переполнением. Вот код:

def calculate_discount(quantity):
    """Скидка: 10% за каждые 10 единиц"""
    discount = (quantity // 10) * 10
    return discount

# Это работает правильно
print(calculate_discount(50))  # 50%

# А это — нет
print(calculate_discount(100))  # Ожидаем 100%, получаем 100%
# Но что если скидка не может быть больше 50%?

Видишь проблему? Функция не ограничивает максимальную скидку. При количестве 1000 она вернёт 1000%, что глупо.

Правильный код:

def calculate_discount(quantity):
    discount = min((quantity // 10) * 10, 50)  # Максимум 50%
    return discount

2. Состояния и переходы между ними

Каждый сложный объект в программе имеет состояния. Баги часто скрываются в переходах между ними.

Например, заказ может быть в состояниях: "создан", "оплачен", "отправлен", "доставлен", "отменён".

Вопросы для поиска багов:

Это не находится автоматически. Нужно думать.

3. Обработка ошибок и исключения

Большинство багов — это необработанные исключения или неправильная обработка ошибок.

def fetch_user_data(user_id):
    response = requests.get(f"https://api.example.com/users/{user_id}")
    return response.json()  # Что если сервер вернул 500?

Здесь целых три проблемы:

Нормально это выглядит так:

def fetch_user_data(user_id):
    try:
        response = requests.get(
            f"https://api.example.com/users/{user_id}",
            timeout=5
        )
        response.raise_for_status()  # Выбросит исключение на 4xx и 5xx
        return response.json()
    except requests.exceptions.Timeout:
        logger.error(f"API timeout for user {user_id}")
        return None
    except requests.exceptions.RequestException as e:
        logger.error(f"API error for user {user_id}: {e}")
        return None
    except ValueError as e:
        logger.error(f"Invalid JSON response for user {user_id}: {e}")
        return None

4. Конкурентность и race conditions

Если код работает с многопоточностью, асинхронностью или распределёнными системами, ищи race conditions.

# Опасный код
counter = 0

def increment():
    global counter
    counter += 1  # Это не атомарная операция!

# Если два потока вызовут increment() одновременно,
# counter может увеличиться только на 1 вместо 2

Правильно:

import threading

counter = 0
lock = threading.Lock()

def increment():
    global counter
    with lock:
        counter += 1

Типичные баги по языкам и фреймворкам

Python

1. Mutable default arguments

def add_item(item, list=[]):  # БАГИ!
    list.append(item)
    return list

print(add_item(1))  # [1]
print(add_item(2))  # [1, 2] — ОЖИДАЛИ [2]!

Список переиспользуется между вызовами. Правильно — list=None и создавать новый внутри.

2. Забывчивое закрытие ресурсов

# Плохо
f = open('file.txt')
data = f.read()
# Если здесь исключение, файл не закроется

# Хорошо
with open('file.txt') as f:
    data = f.read()  # Файл гарантированно закроется

JavaScript/TypeScript

1. Асинхронность и forgotten promises

// Баги!
function fetchData() {
    fetch('/api/data')
        .then(r => r.json())
        .then(data => console.log(data))
        // Ошибки не обработаны!
}

// Правильно
async function fetchData() {
    try {
        const r = await fetch('/api/data');
        if (!r.ok) throw new Error(`HTTP ${r.status}`);
        const data = await r.json();
        return data;
    } catch (e) {
        console.error('Failed to fetch:', e);
    }
}

2. Null/undefined checks

// Может упасть
const user = getUser();
const name = user.profile.name;  // Что если user или profile null?

// Правильно
const name = user?.profile?.name ?? 'Unknown';

SQL и базы данных

1. SQL-инъекции

# КРИТИЧЕСКИ ОПАСНО!
query = f"SELECT * FROM users WHERE email = '{email}'"
db.execute(query)

# Если email = "'; DROP TABLE users; --"
# Выполнится запрос: SELECT * FROM users WHERE email = ''; DROP TABLE users; --'

Правильно — всегда используй параметризованные запросы:

query = "SELECT * FROM users WHERE email = ?"
db.execute(query, (email,))

2. N+1 проблема

# Плохо — запросит БД 1000 раз
users = User.all()
for user in users:
    print(user.profile.name)  # Каждый .profile — отдельный запрос!

# Правильно — один запрос с JOIN
users = User.all().include('profile')
for user in users:
    print(user.profile.name)

Инструменты для поиска багов

Статический анализ кода:

Динамический анализ (во время выполнения):

Тестирование:

Property-based testing:

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

from hypothesis import given, strategies as st

@given(st.lists(st.integers()))
def test_sorted_list_is_ordered(lst):
    result = sorted(lst)
    assert all(result[i] <= result[i+1] for i in range(len(result)-1))

Hypothesis автоматически генерирует тысячи тест-кейсов и ищет edge cases, которые ломают твой код.

Code review и AI-анализ:

Честно, после того как я начал использовать автоматический код-ревью, количество багов в production упало примерно на 40%. Инструмент смотрит на каждый MR и находит то, что QA и разработчик пропустили — необработанные исключения, неправильные проверки, небезопасные паттерны.

Где искать баги в production

Когда баг уже в боевой системе, мониторинг становится твоим лучшим другом.

Логи:

Метрики:

User feedback:

Инструменты: Sentry, DataDog, Prometheus, ELK Stack.

Процесс поиска бага: пошагово

  1. Воспроизведи баг. Без воспроизведения ты не решишь его. Запиши шаги.
  2. Собери контекст. Версия кода, окружение, логи, метрики в момент ошибки.
  3. Выдвини гипотезы. Что может быть причиной?
  4. Проверь граничные случаи. Может ли баг быть связан с особыми значениями входных данных?
  5. Используй debugger. Запусти код пошагово и смотри переменные.
  6. Напиши тест. Тест, который воспроизводит баг. Потом исправляешь код так, чтобы тест прошёл.
  7. Проверь регрессии. Твой fix не сломал ничего ещё?

Культура поиска багов в команде

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


Если ты хочешь автоматизировать часть этого процесса, попробуй Distiq. Это AI-бот для code review, который анализирует каждый MR и находит баги, уязвимости и проблемы со стилем автоматически. Работает с GitHub, GitLab и GitVerse, интегрируется за две мин

Попробуйте Distiq для автоматического code review

AI-бот анализирует каждый MR/PR и оставляет комментарии с замечаниями. Интеграция за 2 минуты.

Попробовать бесплатно

Похожие статьи