Для ошибки, которую пользователь может исправить выбором действия, какой контракт Swift позволяет описать в...

Для ошибки, которую пользователь может исправить выбором действия, какой контракт Swift позволяет описать варианты восстановления?

Проходите собеседования с ИИ помощником Hintsage

Краткий ответ

Для такой ошибки подходит протокол RecoverableError из Foundation. Он описывает доступные варианты восстановления и метод, который пытается выполнить выбранное действие. Сам протокол не показывает интерфейс и не повторяет операцию автоматически: это обязанность вызывающего кода.

Исторический контекст

Подход возник из потребности отделить обычное сообщение об ошибке от описания возможных действий пользователя. В модели ошибок Foundation ошибка может содержать не только причину сбоя, но и варианты восстановления, например повторить операцию, использовать другое значение или отменить действие.

Это дополняет базовый механизм throws: throws передаёт факт сбоя, а RecoverableError формализует возможный сценарий восстановления.

Постановка проблемы

Если ошибка содержит только текст, интерфейс вынужден угадывать, какие действия доступны пользователю. Такой код становится хрупким: тексты локализуются, меняются и не должны использоваться как программный контракт.

Если же объявить варианты восстановления, но не определить их реальное поведение, пользователь увидит формально доступное действие, которое ничего не исправляет. Поэтому восстановление должно быть частью согласованного контракта ошибки, а не просто набором подписей кнопок.

Подробное решение

Тип ошибки реализует RecoverableError и предоставляет массив recoveryOptions. Эти строки предназначены для отображения пользователю, поэтому их следует локализовать на уровне приложения. Метод attemptRecovery(optionIndex:) получает индекс выбранного варианта, выполняет попытку восстановления и возвращает true, если она завершилась успешно.

import Foundation enum ImportError: Error, RecoverableError { case duplicate var recoveryOptions: [String] { ["Заменить", "Пропустить"] } func attemptRecovery(optionIndex: Int) -> Bool { optionIndex == 1 } } let error = ImportError.duplicate let recovered = error.attemptRecovery(optionIndex: 1) print(recovered) // true

Выбрасывание ошибки при этом не меняется: ошибка по-прежнему распространяется через throws и перехватывается в catch. После перехвата вызывающий код сам решает, показывать ли варианты, вызывать ли attemptRecovery, повторять ли исходную операцию и что делать при результате false.

RecoverableError не следует использовать для любого сбоя. Если восстановление требует асинхронного запроса, сложного сценария или участия нескольких слоёв приложения, часто лучше оставить ошибку обычной и реализовать процесс восстановления отдельно. Протокол особенно уместен, когда ошибка действительно может предложить конечный набор непосредственных действий.

Его можно сочетать с LocalizedError для локализованного описания причины. При этом errorDescription отвечает за объяснение сбоя, а recoveryOptions и attemptRecovery — за варианты исправления.

Ситуация из практики

При импорте файла обнаружен дубликат. Рассматривались три варианта: зашить текстовые кнопки в экран, вернуть обычную ошибку без описания восстановления или реализовать RecoverableError.

Первый вариант прост, но связывает слой импорта с конкретным интерфейсом и ломается при локализации. Второй сохраняет разделение слоёв, но заставляет экран самостоятельно знать все причины и допустимые действия.

Выбран третий вариант: доменная ошибка предоставляет варианты восстановления, а экран решает, как их показать. После выбора экран вызывает восстановление и при успехе повторяет или продолжает импорт. В результате причина ошибки и сценарий восстановления остаются типизированными, а UI не анализирует текст сообщения.

Что кандидаты часто упускают

  1. Достаточно ли соответствия RecoverableError, чтобы система автоматически показала пользователю варианты?

Нет. Соответствие протоколу только предоставляет данные и операцию восстановления. Конкретный экран, обработчик ошибок или инфраструктурный компонент должен получить ошибку, построить интерфейс и вызвать метод для выбранного варианта.

  1. Должен ли индекс варианта восстановления совпадать с индексом кнопки в любом интерфейсе?

Нет, протокол задаёт индекс относительно массива recoveryOptions, но отображение может быть другим. UI не должен безоговорочно предполагать, что порядок вариантов или их количество неизменны: он должен использовать предоставленный массив и передавать корректный индекс выбранного элемента.

  1. Что означает возвращаемое значение false из attemptRecovery?

Это означает, что попытка восстановления не удалась. Ошибка не исчезает автоматически, исходная операция не повторяется сама и новый throw не возникает по волшебству. Вызывающий код должен решить, сообщить ли о неудаче, предложить другой вариант, повторить операцию или завершить сценарий.