Воспроизведите, прежде чем исправлять
Что это такое
Воспроизвести ошибку — значит заставить её происходить по требованию, так, чтобы это можно было запустить снова. Пока это невозможно, вы работаете с отчётом, а отчёт — рассказ о системе, который включает память рассказчика, его окружение и его ожидания; воспроизведение же — факт: такие-то входы, такое-то состояние, такой-то неверный вывод, и это первый и самый ценный шаг, потому что всё дальнейшее зависит от возможности сказать, исправило ли ваше изменение хоть что-нибудь.
Почему это важно
Исправление без воспроизведения — угадывание с хорошим шансом сломать что-то ещё. Хуже того, ошибку, которую нельзя воспроизвести, нельзя и подтвердить исправленной, поэтому тот же дефект возвращается, а второе расследование начинается с нуля. Воспроизведение ещё и сжимает проблему: поиск малейшего входа, который её вызывает, обычно вскрывает причину раньше, чем вы прочитали хоть строку кода. Опытные отладчики намеренно тратят здесь своё время, даже когда им кажется, что ответ они уже знают.
Ключевые идеи
Прочитайте ошибку, прежде чем строить теории
Начните с того, что действительно произошло: полное сообщение об ошибке, стек вызовов, строки лога непосредственно перед ней, версия и окружение. Новички читают первую строку и начинают угадывать, однако десятая строка обычно называет файл и причину. Если ошибки нет, выпишите точное различие между тем, что произошло, и тем, что должно было произойти, — это предложение и есть ваша спецификация для воспроизведения.
Ожидалось: сумма заказа 42.00 со скидкой участнику 10%
Получено: сумма заказа 46.20 (скидка не применена)
Постройте минимальное воспроизведение
Начните со сбойного случая и уберите всё, что не обязательно, перезапуская после каждого удаления. Цель — минимум входных данных и минимум кода, которые всё ещё падают. Маленькое воспроизведение быстро запускать, о нём легко рассуждать, и его можно прямо превратить в тест. Когда случай велик — целый рабочий процесс, — сожмите его до шага перед появлением неверного вывода.
полный поток оформления -> убрать оплату -> убрать доставку -> всё ещё неверно
-> сумма неверна, как только установлен флаг участника
Сделайте его детерминированным
Воспроизведение, которое падает один раз из пяти, почти так же плохо, как никакого. Зафиксируйте источники варьирования: задайте зерно генератора случайных чисел, заморозьте часы, отсортируйте коллекцию, установите локаль и часовой пояс, и запускайте в цикле, пока не узнаете долю провалов. Как только это повторяемо, вы можете это измерять, а как только можете измерять — можете делить пополам.
запуск 1000 раз -> 37 провалов -> порядок имеет значение
запуск с фиксированным зерном -> 1000 провалов -> теперь это отлаживаемо
Захватите состояние в момент сбоя
Смотрите значения, а не только исход. Напечатайте или поставьте точку останова на входах, промежуточных результатах и идентичности объектов в точке, где появляется неверное значение. Отладка чтением одного лишь кода отвечает на вопрос «что могло произойти», а осмотр состояния отвечает на вопрос «что произошло»; предпочитайте второе, когда это дёшево.
Превратите воспроизведение в тест
Воспроизведение, которое вы не сохранили, придётся собирать заново. Как только оно маленькое и детерминированное, обычно остаётся несколько строк до автоматического теста, и именно тест не даёт дефекту вернуться. Напишите его до исправления, если инструменты позволяют, и посмотрите, как он падает: этот провальный тест и есть доказательство того, что исправление работает.
Практика
Сожмите настоящий сбой
Возьмите ошибку, над которой работаете сейчас, и сведите воспроизведение к менее чем десяти строкам или к одной команде с фиксированным входом. Перезапускайте после каждого удаления.
Готово, когда: воспроизведение всё ещё падает, и вы убрали как минимум половину того, с чего начали.
Зафиксируйте недетерминизм
Для сбоя, который случается иногда, запустите воспроизведение в цикле и запишите долю провалов, затем зафиксируйте зерно, часы и порядок и запустите снова.
Готово, когда: доля провалов равна либо нулю, либо ста процентам, и вы знаете, какие источники варьирования убрали.
Частые ошибки
- Начало исправления до появления воспроизведения — исправление непроверяемо, и ошибка возвращается; исправить: сначала воспроизведите, даже когда причина кажется очевидной.
- Чтение только первой строки ошибки — имя сбойного файла и настоящая причина обычно дальше; исправить: прочитайте всё сообщение и стек.
- Воспроизведение на полном продакшен-наборе данных — запуск медленный, а сигнал погребён; исправить: сожмите до самого маленького входа, который всё ещё падает.
- Согласие на перемежающееся воспроизведение — вы не можете сказать, сработало ли исправление; исправить: зафиксируйте зерно, часы и порядок, пока оно не станет детерминированным.
- Исправление с выбрасыванием воспроизведения — дефект может вернуться незамеченным; исправить: превратите его в автоматический тест до изменения кода.
Что читать дальше
- Модульное тестирование (01-cs-fundamentals/10-unit-testing) — как превратить только что построенное воспроизведение в тест, который остаётся в наборе.
- Изолируйте и докажите причину (01-cs-fundamentals/11-debugging/02-isolate-and-prove-the-cause) — следующий шаг, как только сбой повторяем по желанию.
- Конкурентность и асинхронное программирование (01-cs-fundamentals/05-concurrency-async-programming) — почему некоторые сбои сопротивляются воспроизведению и что зафиксировать, чтобы они вели себя предсказуемо.