В функции с внешним defer обработчик catch регистрирует собственный defer: какой блок выполнится первым при выходе из catch?
Первым выполнится defer, зарегистрированный внутри catch, затем — внешний defer при выходе из функции. Причина в том, что внутренний блок покидает свою область видимости раньше внешней, а defer выполняется при выходе из области, в которой он объявлен.
Defer появился как структурированный способ гарантировать освобождение ресурсов и восстановление состояния независимо от того, завершилась функция обычным return или передачей ошибки через throw. Такой подход уменьшает дублирование очистки в нескольких ветвях выполнения.
Механизм опирается на области видимости: каждый зарегистрированный defer привязан к конкретной области и выполняется при выходе из неё.
Ошибка часто обрабатывается во вложенном do-catch, где требуется локально закрыть ресурс, отменить временное состояние или записать диагностическую информацию. При этом у функции может быть внешний defer для очистки ресурса более высокого уровня.
Если перепутать порядок, можно преждевременно освободить внешний ресурс или ошибочно считать, что внешний defer уже выполнил всю необходимую очистку. Это особенно опасно при вложенных транзакциях, блокировках и временных изменениях состояния.
defer выполняется при выходе из области видимости, в которой он был объявлен. Область обработчика catch завершается раньше, чем тело окружающей функции, поэтому его defer срабатывает первым.
Внутренний defer не становится частью списка внешней функции: он привязан к области catch. После выхода из catch выполняется его очистка, а затем функция продолжает выполнение и при окончательном выходе запускает внешний defer.
Если из catch выполнить return или повторно выбросить ошибку, внутренний defer всё равно выполнится перед выходом из обработчика. Затем, при выходе из функции, будет выполнен внешний defer.
Практический компромисс заключается в выборе области ответственности: локальный defer должен очищать только локальный ресурс, а внешний — ресурс, жизненный цикл которого охватывает всю функцию. Нельзя полагаться на порядок блоков без явного понимания их областей видимости.
Сервис открывает транзакцию перед выполнением операции. Внутри catch временно устанавливается признак, что ошибка уже преобразована в доменную форму, и для него нужен локальный defer.
Можно поместить всю очистку во внешний defer. Это проще, но смешивает ответственность: внешний блок начинает знать о временном состоянии внутреннего обработчика. Другой вариант — вручную очищать состояние в каждой ветви catch, однако при добавлении новой ветви легко забыть об очистке.
Оптимально использовать вложенный defer для локального состояния и внешний defer для транзакции. Тогда локальное состояние восстанавливается при выходе из catch, после чего внешний блок корректно завершает транзакцию. Результат — независимые области ответственности и одинаковое поведение при обычном завершении, return и throw.
Вопрос: Выполнится ли defer внутри catch, если обработчик повторно выбрасывает ошибку?
Ответ: Да. Повторный throw покидает область catch, поэтому все defer, зарегистрированные в этой области до момента выхода, выполняются. После этого ошибка передаётся наружу, где могут сработать defer внешних областей.
Вопрос: Что произойдёт, если внутренний defer изменит переменную, используемую внешним defer?
Ответ: Внешний defer увидит состояние переменной, существующее к моменту выхода из функции. Поэтому внутренний блок может подготовить данные для внешней очистки. Однако полагаться на такое взаимодействие следует осторожно: оно создаёт скрытую зависимость между вложенными областями.
Вопрос: Можно ли считать defer внутри catch эквивалентом defer, объявленного перед do-catch?
Ответ: Нет. Внешний defer действует до выхода из функции, а внутренний — только до выхода из области catch. При успешном завершении catch внутренний блок выполнится сразу, после чего функция продолжит работу; внешний блок выполнится позднее, при выходе из функции.