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