Русский — Официальная русская версия
bugfix-protocol.
Bugfix Protocol: Систематическая 6-фазная отладка
Структурированный подход к ошибкам — от анализа симптомов до проверки. Предотвращает бесцельный метод проб и ошибок и гарантирует устойчивость исправлений.
Обзор и цель
| Фаза | Название | Цель | Макс. время |
|---|---|---|---|
| 1 | Быстрые проверки | Исключить очевидные причины | 2 мин |
| 2 | Диагностика | Локализовать первопричину | 10 мин |
| 3 | Изолированный тест | Сделать баг воспроизводимым | 5 мин |
| 4 | Исправление | Минимальная коррекция | 10 мин |
| 5 | Верификация | Проверить исправление + проверить побочные эффекты | 5 мин |
| 6 | Документирование | Сохранить знания | 2 мин |
Правило 20 минут: Если через 20 минут прогресс отсутствует, измените подход или обратитесь за помощью.
Фаза 1: Быстрые проверки (2 мин)
Прежде чем погружаться глубоко — проверьте наиболее частые причины:
Чек-лист
- Синтаксическая ошибка? Внимательно прочитайте сообщение об ошибке, проверьте строку
- Ошибка импорта? Модуль установлен? Имя правильное? Циклический импорт?
- Опечатка? Правильно ли указаны имена переменных/функций?
- Неверный тип данных? String вместо int? None там, где ожидался объект?
- Устаревший кэш? Удалите
__pycache__, перезапустите - Неверное окружение? Активно ли правильное venv? Правильная ли версия Python?
- Кодировка? UTF-8 против cp1252 (классический Windows)
Быстрые действия
# Очистить кэш
find . -name "__pycache__" -type d -exec rm -rf {} + 2>&1
find . -name "*.pyc" -delete 2>&1
# Проверить импорты
python -c "import modulename"
# Проверить синтаксис
python -m py_compile file.py
Фаза 2: Диагностика (10 мин)
Стратегия: Снаружи внутрь (Outside-In)
- Анализ сообщения об ошибке — Читайте трассировку стека (traceback) снизу вверх
- Проверка последних изменений —
git diff,git log --oneline -10 - Использование диагностических утилит — Используйте специфичные для проекта инструменты
Диагностические утилиты (Примеры)
В зависимости от проекта могут пригодиться специализированные скрипты диагностики:
| Инструмент | Назначение |
|---|---|
import_diagnose.py |
Анализ проблем с импортом |
method_analyzer.py |
Проверка сигнатур методов |
env_checker.py |
Валидация переменных окружения/путей |
Примечание: Создавайте специфичные для проекта диагностические утилиты или используйте существующие. Важен систематический подход, а не конкретный инструмент.
Методы отладки
# 1. Отладка через print (быстро, но эффективно)
print(f"DEBUG: variable={variable!r}, type={type(variable)}")
# 2. Точка останова (интерактивно)
breakpoint() # Python 3.7+
# 3. Расширенный traceback
import traceback
traceback.print_exc()
# 4. Логирование вместо print
import logging
logging.basicConfig(level=logging.DEBUG)
logger = logging.getLogger(__name__)
logger.debug(f"State: {state!r}")
Фаза 3: Изолированный тест (5 мин)
Минимальный воспроизводимый пример (MRE)
Цель: Воспроизвести баг с минимальным количеством кода.
# test_bug.py — Минимальный тест воспроизведения
"""
Bug: [Краткое описание]
Expected: [Что должно произойти]
Actual: [Что происходит вместо этого]
"""
# Минимальная настройка
# ... только самое необходимое
# Триггер бага
# ... точный код, вызывающий баг
# Ожидаемый результат
# assert result == expected, f"Got {result}"
Стратегии изоляции
- Новый файл: Воспроизведите баг в отдельном файле
- Удаление зависимостей: По одной, пока баг не исчезнет
- Бинарный поиск: Разделите блок кода пополам, проверьте, в какой половине баг
- Git bisect:
git bisect start,git bisect bad,git bisect good <commit>
Фаза 4: Исправление (10 мин)
Принципы
- Минимальность: Меняйте как можно меньше
- Понимание: Никогда не чините вслепую — поймите, ПОЧЕМУ код сломан
- Одна задача: Одно исправление на коммит, не чините несколько проблем одновременно
- Обратная совместимость: Не ломайте существующий функционал
Шаблоны исправлений
# ПЛОХО: Лечение симптома
try:
result = broken_function()
except: # Подавление всех исключений
result = default_value
# ХОРОШО: Исправление первопричины
def broken_function():
if input_data is None: # Настоящая причина: отсутствие проверки на None
return default_value
return process(input_data)
Распространенные категории исправлений
| Категория | Типичное исправление |
|---|---|
| None/Null | Защитное условие: if x is None: return default |
| Ошибка индекса | Проверка границ: if i < len(lst) |
| Ошибка типа | Явное приведение: str(x), int(x) |
| Ошибка импорта | Исправить путь, установить пакет |
| Кодировка | Явно указать UTF-8: encoding='utf-8' |
| Состояние гонки | Блокировка/Мьютекс или изменение порядка |
| Баг состояния | Проверить инициализацию, добавить сброс |
Фаза 5: Верификация (5 мин)
Чек-лист
- Баг исправлен: Исходная проблема больше не возникает
- MRE проходит: Изолированный тест выполняется успешно
- Нет регрессий: Существующие тесты по-прежнему проходят
- Граничные случаи: Проверены пустой ввод, None, большие объемы данных
- Утилиты проекта: Проверьте директорию утилит проекта на наличие тестов/валидаторов
Команды тестирования
# Юнит-тесты
python -m pytest tests/ -v
# Только затронутые тесты
python -m pytest tests/test_module.py -v -k "test_name"
# Проверка типов
python -m mypy file.py
# Линтер
python -m flake8 file.py
Фаза 6: Документирование (2 мин)
Шаблон отчета об ошибке
## Bug Report: [Краткий заголовок]
**Date:** YYYY-MM-DD
**Severity:** critical / high / medium / low
**Component:** [Модуль/Файл]
### Symptom
[Что видит пользователь / сообщение об ошибке]
### Root Cause
[Техническая первопричина]
### Fix
[Что было изменено + почему]
### Affected Files
- `file1.py` — [Изменение]
- `file2.py` — [Изменение]
### Prevention
[Как предотвратить подобный баг в будущем?]
Формат сообщения коммита
fix: [Краткое описание исправления]
Cause: [Первопричина в одном предложении]
Fix: [Что было изменено]
Test: [Как проверялось]
PyQt6 / Отладка GUI — Распространенные ловушки
Этот раздел актуален для десктопных GUI-проектов на PyQt6/PySide6.
Топ-5 ловушек PyQt6
| Ловушка | Проблема | Решение |
|---|---|---|
| Отключение Signal-Slot | Сигнал подключен, но обработчик не выполняется | print в обработчике, проверка сигнатуры |
| Потокобезопасность | Обновление GUI из рабочего потока | QMetaObject.invokeMethod или использование сигнала |
| Каскад макета (Layout) | Виджет не виден / смещен | widget.show(), проверка иерархии layout |
| Блокировка цикла событий | Зависание GUI | Перенести долгие операции в QThread |
| Сборка мусора | Виджет внезапно исчезает | Сохранять ссылку как self.widget |
Вспомогательные функции отладки PyQt6
# Дамп иерархии виджетов
def dump_widget_tree(widget, indent=0):
print(" " * indent + f"{widget.__class__.__name__}: {widget.objectName()}")
for child in widget.findChildren(QWidget):
if child.parent() == widget:
dump_widget_tree(child, indent + 2)
# Отладка сигналов
from PyQt6.QtCore import QObject
original_connect = QObject.connect
def debug_connect(self, *args, **kwargs):
print(f"CONNECT: {self.__class__.__name__} -> {args}")
return original_connect(self, *args, **kwargs)
Быстрая справка
ОШИБКА НАЙДЕНА?
|
v
[Фаза 1: Быстрые проверки] ───── Очевидно? -> ИСПРАВИТЬ
|
v
[Фаза 2: Диагностика] ────────── Причина ясна? -> Фаза 4
|
v
[Фаза 3: Изолированный тест] ── Воспроизводимо? -> Фаза 4
| |
| Не воспроизводится?
| |
| Добавить логирование,
| ждать повторения
v
[Фаза 4: Исправление] ────────── Минимальное + понятное
|
v
[Фаза 5: Верификация] ───────── Тесты пройдены? -> Фаза 6
| |
| Тесты провалены? -> Назад к Фазе 4
v
[Фаза 6: Документирование] ──── Отчет об ошибке + коммит
Правило 20 минут
Если вы застряли через 20 минут:
- Измените подход — Попробуйте другой метод отладки
- Метод утенка — Объясните проблему вслух (или запишите ее)
- Сделайте перерыв — Отодите на 5 минут, вернитесь со свежим взглядом
- Обратитесь за помощью — Спросите коллегу, проверьте Stack Overflow или документацию
- Сброс —
git stash, начните с чистого листа