Разработайте стратегию обработки ошибок для открываемой библиотеки, которую будут использовать разработчики на разных языках: когда использовать исключения, когда возвращать коды ошибок, как документировать контракт и поддерживать совместимость
Краткая стратегия обработки ошибок для кросс-языковой библиотеки. Принципы - Единая модель ошибок: внутренне храните ошибки как структурированные коды + машинно-читаемые поля (код, категория, опционально cause/metadata) и текст для людей. - Ясный контракт: документируйте семантику каждого кода и границы, в которых поведение гарантировано. - Интероп: экспортируйте простую C‑ABI версию (код возврата + out-аргументы) и в биндингах для языков с исключениями—конвертируйте коды в исключения. Когда использовать исключения vs коды ошибок - Используйте исключения в высокоуровневых API для языков, где исключения — идиоматичны (Java, C#, Python, C++ при внутреннем использовании): - для ошибок, которые нарушают нормальный поток выполнения и не ожидаются при обычном использовании (I/O, парсинг, валидация). - для ошибок с богатой трассировкой/контекстом. - Используйте коды ошибок/возвратные значения в низкоуровневой C‑совместимой API и в ситуациях, где: - производительность/определяемость важны (no-except style), - язык не имеет общих исключений (C), - вызовы в hot path должны избегать стоимости обработки исключений. - Гибрид: внутренне храните коды; в биндингах кидайте исключения, а в C‑ABI возвращайте код. Контракт и документирование - Опишите модель ошибок в одном месте: форматы кодов, категории (например: NOT_FOUND, INVALID_ARGUMENT, IO, INTERNAL, UNAVAILABLE), структура метаданных, правила локализации сообщений. - Для каждого публичного API укажите: - какие коды/исключения могут быть возвращены; - семантику и условия возникновения каждого кода; - какие ошибки считаются фатальными (небезопасно продолжать) vs recoverable; - принадлежность к версии (caveats) и рекомендации по ретраи/бекофу. - Примеры: для функции foo() документируйте: "может вернуть коды: NOT_FOUND — если X; INVALID_ARGUMENT — если Y; INTERNAL — редкое, при Z". - Машинно-ориентированная документация: JSON/YAML файл с перечислением кодов, описанием и рекомендациями для биндингов (позволит автогенерацию). Схемы кодов и совместимость - Идентификаторы: используйте стабильные строковые или числовые коды; не полагайтесь на сообщения для логики. - Расширяемость: - разрешено добавлять новые коды; клиенты должны обращаться с неизвестными кодами как с generic error + советом retry/abort в документации; - нельзя менять семантику существующего кода (breaking change). Если нужно изменить поведение — введите новый код или новую версию API. - Резервирование: зарезервируйте диапазон кодов для будущего расширения и для пользовательских/плагинных ошибок. - Семантика изменения: - подсчет несовместимых изменений: изменение набора ошибок, изменение meaning — breaking; документируйте в CHANGELOG. - добавление необязательных полей в структуру ошибки — backward compatible; удаление полей — breaking. Практические рекомендации по реализации - Слой ошибок: core error struct { code, message, details(map) }. Экспонируйте код и детали, а текст — только для логов/UI. - Для C‑ABI: возвращается int/enum code; при success (code == OK) out-значения валидны; при error out-указатели не изменяются или явно очищаются. Документируйте правила владения и освобождения памяти для сообщений ошибок. - Для языковых биндингов: при получении ненулевого кода — сформировать исключение подходящего класса/типа и вложить структурированные поля как свойства исключения. - Логирование: предоставьте опции логирования/контекстирования ошибок, но не печатайте секреты по умолчанию. Миграция и обратная совместимость - Семвер + правила: повышайте major при breaking change в поведении ошибок; minor/patch для добавления новых кодов/полей. - Депрецирование: уведомляйте заранее, предоставьте transition path (OLD_CODE -> NEW_CODE) и feature flag/compat режим. - Тесты: покрывайте matrix API × версии: ожидаемые коды, сообщения и контракт владения памятью. Советы для разработчиков биндингов - Конвертация: map код → конкретный исключаемый тип; оставьте оригинальный код/детали в свойствах исключения для диагностики. - Предоставляйте both styles API (sync throwing и error-returning) если это полезно для целевого языка. - Документация биндингов должна явно указывать маппинг код→исключение и правила транслирования метаданных. Короткая сводка (чеклист) - 111 Внутренне — структурированный код + детали. - 222 C‑ABI — возвращаемые коды; поля ошибок выделяются/освобождаются по контракту. - 333 Высокоуровнево — исключения в биндингах, конвертация код→исключение. - 444 Документировать все коды, семантику, рекомендации по retry/abort. - 555 Сохранять совместимость: не менять семантику существующих кодов; добавление кода — не breaking; breaking — новая major‑версия. Если нужно, могу дать шаблон спецификации ошибок (формат JSON/YAML) и примеры маппинга код→исключение для конкретных языков.
Принципы
- Единая модель ошибок: внутренне храните ошибки как структурированные коды + машинно-читаемые поля (код, категория, опционально cause/metadata) и текст для людей.
- Ясный контракт: документируйте семантику каждого кода и границы, в которых поведение гарантировано.
- Интероп: экспортируйте простую C‑ABI версию (код возврата + out-аргументы) и в биндингах для языков с исключениями—конвертируйте коды в исключения.
Когда использовать исключения vs коды ошибок
- Используйте исключения в высокоуровневых API для языков, где исключения — идиоматичны (Java, C#, Python, C++ при внутреннем использовании):
- для ошибок, которые нарушают нормальный поток выполнения и не ожидаются при обычном использовании (I/O, парсинг, валидация).
- для ошибок с богатой трассировкой/контекстом.
- Используйте коды ошибок/возвратные значения в низкоуровневой C‑совместимой API и в ситуациях, где:
- производительность/определяемость важны (no-except style),
- язык не имеет общих исключений (C),
- вызовы в hot path должны избегать стоимости обработки исключений.
- Гибрид: внутренне храните коды; в биндингах кидайте исключения, а в C‑ABI возвращайте код.
Контракт и документирование
- Опишите модель ошибок в одном месте: форматы кодов, категории (например: NOT_FOUND, INVALID_ARGUMENT, IO, INTERNAL, UNAVAILABLE), структура метаданных, правила локализации сообщений.
- Для каждого публичного API укажите:
- какие коды/исключения могут быть возвращены;
- семантику и условия возникновения каждого кода;
- какие ошибки считаются фатальными (небезопасно продолжать) vs recoverable;
- принадлежность к версии (caveats) и рекомендации по ретраи/бекофу.
- Примеры: для функции foo() документируйте: "может вернуть коды: NOT_FOUND — если X; INVALID_ARGUMENT — если Y; INTERNAL — редкое, при Z".
- Машинно-ориентированная документация: JSON/YAML файл с перечислением кодов, описанием и рекомендациями для биндингов (позволит автогенерацию).
Схемы кодов и совместимость
- Идентификаторы: используйте стабильные строковые или числовые коды; не полагайтесь на сообщения для логики.
- Расширяемость:
- разрешено добавлять новые коды; клиенты должны обращаться с неизвестными кодами как с generic error + советом retry/abort в документации;
- нельзя менять семантику существующего кода (breaking change). Если нужно изменить поведение — введите новый код или новую версию API.
- Резервирование: зарезервируйте диапазон кодов для будущего расширения и для пользовательских/плагинных ошибок.
- Семантика изменения:
- подсчет несовместимых изменений: изменение набора ошибок, изменение meaning — breaking; документируйте в CHANGELOG.
- добавление необязательных полей в структуру ошибки — backward compatible; удаление полей — breaking.
Практические рекомендации по реализации
- Слой ошибок: core error struct { code, message, details(map) }. Экспонируйте код и детали, а текст — только для логов/UI.
- Для C‑ABI: возвращается int/enum code; при success (code == OK) out-значения валидны; при error out-указатели не изменяются или явно очищаются. Документируйте правила владения и освобождения памяти для сообщений ошибок.
- Для языковых биндингов: при получении ненулевого кода — сформировать исключение подходящего класса/типа и вложить структурированные поля как свойства исключения.
- Логирование: предоставьте опции логирования/контекстирования ошибок, но не печатайте секреты по умолчанию.
Миграция и обратная совместимость
- Семвер + правила: повышайте major при breaking change в поведении ошибок; minor/patch для добавления новых кодов/полей.
- Депрецирование: уведомляйте заранее, предоставьте transition path (OLD_CODE -> NEW_CODE) и feature flag/compat режим.
- Тесты: покрывайте matrix API × версии: ожидаемые коды, сообщения и контракт владения памятью.
Советы для разработчиков биндингов
- Конвертация: map код → конкретный исключаемый тип; оставьте оригинальный код/детали в свойствах исключения для диагностики.
- Предоставляйте both styles API (sync throwing и error-returning) если это полезно для целевого языка.
- Документация биндингов должна явно указывать маппинг код→исключение и правила транслирования метаданных.
Короткая сводка (чеклист)
- 111 Внутренне — структурированный код + детали.
- 222 C‑ABI — возвращаемые коды; поля ошибок выделяются/освобождаются по контракту.
- 333 Высокоуровнево — исключения в биндингах, конвертация код→исключение.
- 444 Документировать все коды, семантику, рекомендации по retry/abort.
- 555 Сохранять совместимость: не менять семантику существующих кодов; добавление кода — не breaking; breaking — новая major‑версия.
Если нужно, могу дать шаблон спецификации ошибок (формат JSON/YAML) и примеры маппинга код→исключение для конкретных языков.