Какие сбои следует моделировать через throws, а какие считать нарушением инварианта программы?
Через throws следует моделировать ожидаемые сбои выполнения, которые вызывающий код способен обработать: отсутствие сети, недоступность ресурса, некорректные внешние данные или отказ бизнес-операции. Нарушение внутреннего инварианта программы обычно не является штатной ошибкой и должно выявляться через assert, precondition или аварийное завершение, а не маскироваться как обычный Error.
Модель ошибок Swift разделяет два класса проблем: восстановимые ошибки выполнения и ошибки самого программного контракта. throws делает возможный сбой частью потока управления, тогда как утверждения предназначены для проверки предположений, которые корректная программа не должна нарушать.
Такое разделение позволяет не заставлять каждый вызывающий код обрабатывать дефекты реализации как обычные варианты бизнес-логики. Оно также делает контракт функции понятнее: выбрасываемая ошибка означает, что отказ является допустимым сценарием эксплуатации.
Если внутренний дефект представить как обычный Error, вызывающий код может ошибочно продолжить работу, показать пользователю неподходящее сообщение или повторить операцию, которая заведомо не должна повторяться. В результате первоначальная причина будет скрыта, а состояние программы может стать некорректным.
Обратная ошибка тоже опасна: если ожидаемый отказ оформить через precondition или fatalError, приложение аварийно завершится вместо того, чтобы дать вызывающему коду выбрать восстановление. Поэтому границу проводят по вопросу: может ли корректный клиент столкнуться с этим состоянием при нормальной работе системы?
Ошибку моделируют через Error, если она зависит от внешних условий или входных данных и имеет осмысленный сценарий обработки. Например, AuthError.expiredToken можно перехватить и инициировать обновление токена, а ValidationError — показать пользователю.
Нарушение инварианта означает, что ошибка в коде или невозможное состояние уже нарушили предположение разработчика. Для таких случаев применяют проверочные механизмы:
Пустая строка здесь является допустимым внешним отказом и возвращается как ParseError. Превышение внутреннего ограничения считается нарушением контракта функции и проверяется через precondition.
Граница не всегда определяется исключительно техническим типом сбоя. Если ограничение размера является ожидаемым пользовательским условием, его тоже следует представить как вариант Error; решение зависит от публичного контракта и предусмотренного способа восстановления.
Сервис импорта получает документы от клиента. Повреждённый документ должен приводить к типизированной ошибке валидации, чтобы API вернул понятный ответ и сохранил остальные корректные документы. С другой стороны, невозможное состояние внутреннего индекса указывает на дефект реализации.
Вариант «всё оформить через throws» удобен единым потоком обработки, но позволяет случайно скрыть серьёзный дефект и продолжить работу с испорченным состоянием. Вариант «всё завершать аварийно» упрощает диагностику, но делает нормальные ошибки ввода причиной падения сервиса.
Выбранное решение — публичные ошибки импорта моделировать перечислением Error с устойчивыми вариантами, а проверки внутренних инвариантов выполнять через precondition. В результате клиент получает предсказуемый контракт для ожидаемых отказов, а дефекты реализации не маскируются под штатные ошибки.
throws?Технически можно, но это не всегда правильно с точки зрения контракта. Если ошибка означает, что программа не способна корректно продолжить работу из-за собственного дефекта, её следует обнаружить проверкой инварианта, а не передавать как вариант, который клиент обязан восстанавливать.
assert отличается от precondition в моделировании ошибок?assert предназначен прежде всего для обнаружения ошибок во время разработки и обычно не выполняется в оптимизированных сборках. precondition выражает обязательное условие работы программы и рассчитан на остановку выполнения при его нарушении также в production-сценарии.
Error иметь способ восстановления?Не обязательно, но у ошибки должен быть осмысленный обработчик или понятная причина её распространения. Если ни один корректный вызывающий код не может восстановиться, а состояние указывает на дефект программы, такой случай, вероятно, ошибочно моделируется через throws и должен быть проверкой инварианта.