В приложении одну ошибку нужно надёжно классифицировать программно и одновременно локализованно показать по...

В приложении одну ошибку нужно надёжно классифицировать программно и одновременно локализованно показать пользователю. Как разделить эти две задачи в модели ошибки Swift?

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

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

Для программной классификации используйте отдельный тип ошибки, обычно enum, с машинно значимыми случаями и associated values. Для пользовательского текста добавьте соответствие LocalizedError либо преобразуйте ошибку в сообщение на уровне UI. Логику нельзя строить на сравнении localizedDescription: это текст представления, а не стабильный идентификатор причины.

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

Базовый протокол Error описывает сам факт ошибки, но не задаёт формат её отображения пользователю. Поэтому в Swift разделены модель ошибки и дополнительные протоколы представления, включая LocalizedError.

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

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

Если использовать только строки, код начинает распознавать ошибки через сравнение текста. Это хрупко: перевод, изменение формулировки или добавление технических деталей ломает логику обработки.

Если, наоборот, показывать пользователю имя enum-case или техническое описание, сообщение будет непонятным и может раскрыть внутренние детали. Поэтому причина сбоя должна храниться структурированно, а пользовательский текст — формироваться отдельно.

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

Опишите доменные причины как случаи enum, а параметры, необходимые для диагностики или восстановления, храните в associated values. Для отображения можно реализовать LocalizedError и предоставить errorDescription; при этом обработчик продолжает работать с самим типом ошибки.

import Foundation enum CheckoutError: Error, LocalizedError { case declined(limit: Decimal) case unavailable var errorDescription: String? { switch self { case .declined: return "Платёж отклонён" case .unavailable: return "Сервис временно недоступен" } } } func message(for error: Error) -> String { (error as? LocalizedError)?.errorDescription ?? "Произошла ошибка" }

В бизнес-логике следует сопоставлять ошибку с типом и случаем, например различать CheckoutError.declined и CheckoutError.unavailable. Поля associated values можно использовать для решения: предложить повторить операцию, попросить выбрать другой способ оплаты или показать ограничение.

LocalizedError не превращает ошибку в строку: он лишь предоставляет стандартный источник описания и дополнительных подсказок. Если доменный слой не должен зависеть от локализации, лучше оставить ему только Error, а в presentation-слое преобразовывать случаи ошибки в локализованные ресурсы.

Плюс реализации LocalizedError — единый стандартный механизм получения описания. Минус — связь модели ошибки с пользовательским языком и UI-контекстом. Отдельное преобразование в presentation-слое лучше разделяет ответственность, но требует явного маппинга для каждого значимого случая.

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

Платёжный сервис возвращает ошибки declined с кодом банка и unavailable с признаком временности. Вариант со строками прост, но не позволяет надёжно отличить временный сбой от отказа после локализации сообщения. Вариант с LocalizedError удобен для быстрого показа текста, но может связать сетевой или доменный слой с конкретным языком интерфейса.

Оптимальное решение — хранить структурированный CheckoutError в доменном слое, а в UI иметь преобразователь ошибки в локализованный ресурс. Если небольшой компонент не требует строгого разделения слоёв, допустимо реализовать LocalizedError непосредственно на enum. Результат в обоих случаях один: ветвление выполняется по типу и данным ошибки, а текст можно менять независимо.

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

  1. Можно ли использовать localizedDescription как код ошибки?

    Нет. Это представление для человека, которое может зависеть от языка, реализации протокола и контекста. Для ветвления нужно проверять тип ошибки, её case и структурированные значения.

  2. Обязана ли каждая ошибка реализовывать LocalizedError?

    Нет. Достаточно соответствия Error; тогда вызывающий код всё равно может обработать ошибку типобезопасно. LocalizedError нужен только там, где ошибка должна предоставить стандартное описание, recovery suggestion или другие пользовательские сведения.

  3. Где лучше выполнять локализацию: в enum ошибки или в UI?

    Это зависит от границ слоя. Реализация LocalizedError удобна для небольшого приложения и единообразного доступа к описанию, но связывает модель с пользовательским представлением. В многослойной системе предпочтительнее локализовать в UI: доменная ошибка остаётся независимой от языка, а presentation-слой явно контролирует тексты и контекст показа.