В middleware безусловно преобразуют значение из recover в error. Какой риск возникает при таком проектировании?
Значение, возвращаемое recover, может иметь любой тип, а не только реализовывать интерфейс error. Безусловное приведение к error способно вызвать новую панику и скрыть исходную причину.
Даже безопасное форматирование произвольного значения как текста может потерять структурированную информацию. Поэтому обработчик должен явно выбрать политику: сохранить ошибки как причины, отдельно обработать не-ошибочные значения и решить, какие паники допустимо преобразовывать в ошибку.
В Go обычный способ сообщать об ожидаемых сбоях — возвращать error, а panic/recover предназначены для аварийного прерывания и восстановления на специально выбранной границе. Такое разделение позволяет не смешивать штатное управление ошибками с нарушением инвариантов или ошибками программирования.
Поскольку panic принимает значение любого типа, механизм не ограничен ошибками. Это удобно для внутреннего аварийного управления, но требует осторожности при интеграции с кодом, который ожидает только error.
Middleware может пытаться превратить любую панику в ответ сервиса или ошибку верхнего уровня. Если он выполнит прямое приведение значения из recover к error, паника с числом, строкой или пользовательским объектом вызовет новую ошибку типа при обработке.
В результате исходная паника будет замаскирована, диагностика ухудшится, а клиент может получить неправильный статус. Обратная крайность — безусловно преобразовать всё через форматирование — сохраняет выполнение, но уничтожает тип и возможность программно распознать причину.
recover возвращает ровно то значение, которое было передано в panic. Если это значение реализует error, его можно обернуть через %w, сохранив цепочку для errors.Is и errors.As. Для остальных значений допустимо создать обычную текстовую ошибку через %v, но это уже потеря типизированной семантики.
Безопасный обработчик также должен учитывать, что recover имеет смысл только во время раскрутки стека в отложенной функции той же горутины. Флаг завершения ниже отличает отсутствие паники от паники с нулевым значением и не требует безусловного приведения типа.
Такой код не делает не-ошибочное значение полноценной типизированной причиной. Для наблюдаемости обычно дополнительно записывают stack trace, а на границе процесса или запроса часто предпочитают не скрывать неожиданные паники: их можно залогировать и повторно вызвать panic, если политика системы требует аварийного завершения.
Главный компромисс — между устойчивостью границы и обнаружением дефектов. Преобразование паники в ошибку предотвращает падение конкретного запроса, но может скрыть ошибку программирования; повторная паника сохраняет сигнал о дефекте, но снижает изоляцию сбоя.
HTTP-сервис использует middleware, который должен изолировать панику одного обработчика от всего процесса. Рассматривались три варианта:
error. Вариант прост, но сам ломается на строковых и числовых значениях.%w, остальные безопасно форматировать, записывая стек и применяя отдельную политику для неожиданных паник.Выбран третий вариант. На внешней границе сервиса он позволяет вернуть контролируемый ответ и сохранить цепочку для ожидаемых ошибок, а для нештатных значений — не допустить вторичной паники и оставить достаточно данных для диагностики. В критичных компонентах дополнительно настроено повторное возбуждение паники после логирования.
1. Может ли recover вернуть значение, которое не является error?
Да. В panic можно передать любое значение, поэтому обработчик обязан использовать проверку типа или форматирование, а не предполагать реализацию интерфейса error. Прямое приведение без проверки способно вызвать вторичную панику именно в коде восстановления.
2. Почему преобразование через %v не равно оборачиванию через %w?
%v формирует текст и не сохраняет исходное значение как причину в цепочке ошибок. %w при форматировании с одной оборачиваемой ошибкой сохраняет связь, поэтому последующие errors.Is и errors.As могут исследовать исходную ошибку.
3. Следует ли middleware всегда поглощать любую панику?
Нет. Это архитектурное решение, а не универсальное правило. На границе запроса поглощение может изолировать клиента от сбоя, но для паник, указывающих на дефект программы или повреждение инварианта, часто правильнее зафиксировать стек, освободить ресурсы и повторно вызвать panic, чтобы ошибка не осталась незамеченной.