elizarov


Блог Романа Елизарова


Previous Entry Share Next Entry
Обработка ошибок в API высокого уровня
elizarov

Я опубликовал вводную часть руководства по dxFeed API на сайте dxFeed и хочу подробней остановиться на обработке ошибок в API высокого уровня. Посмотрите на типичное синхронное API по работе с файлами или сетью в любой операционной системе или в любом языке программирования общего назначения. Вы увидите набор методов для открытия файла или соединения, набор методов для отсылки и получения данных, для закрытия. В случае ошибки они возвращают какой-нибудь специальный результат для индикации ошибки или создают исключение (как например IOException). Асинхронное API отличается тем, что вызов метода дает лишь сигнал начать операцию, а её выполнение будет произведено позже. Тем не менее, сигнал об ошибке так или иначе является частью результата, который будет получен позже. Для API низкого уровня явная индикация всех ошибок это естественное поведение. Ведь на основе этого API стоится множество различных приложений с различными требованиями. Реакция на ошибку сильно зависит от конкретного приложения.

А вот в API высокого уровня работа с ошибками это существенно более сложный вопрос. Посмотрим, например, на типичное API высокого уровня для надежной передачи сообщений, который позволяет одному приложения послать сообщение, а другому приложению его получить и обработать. Типичным представителем такого API является JMS, а конкретно можно посмотреть на метод TopicPublisher.publish, который, в режиме по умолчанию, обеспечивает именно гарантированную доставку сообщений.

Какого рода ошибки должен возвращать метод посылки сообщения? Безусловно, он может и должен проверять правильность переданных аргументов. То есть возможность ошибки типа MessageFormatException это совершенно нормально. Другое дело, что в я зыке Java такая ошибка должна быть непроверяемой (unchecked exception) типа NullPointerException или IllegalArgumentsException, ибо её возникновение это признак логической ошибки в коде для которой, по сути, единственно правильным способом реакции является прекращение операции и запись её в журнал ошибок для того чтобы разработчики приложения могли идентифицировать и исправить эту ошибку. В то время как проверяемые ошибки (checked exception) были задуманы как способ индикации о внешних ошибках, которые обязаны быть обработаны приложением каким-то особенным образом зависящим от конкретной ошибки.

Но должен ли вообще API такого уровня сообщать о внешних ошибках типа потери соединения и т.п.? Как приложение должно реагировать на эти ошибки? Должно ли оно попытаться послать сообщение снова? Должно ли оно предпринимать какие-либо действия для попытки установления соединения заново? В каком вообще состоянии находятся реализация этого API после ошибки? Нужно ли заново его инициализировать? К сожалению, документация очень редко дает ответы на эти вопросы. При разработке API об ошибках задумываются в последнюю очередь, если вообще задумываются. Часто можно увидеть ситуацию аналогичную JMS, который (цитирую) "throws JMSException if the JMS provider fails to publish the message due to some internal error." Что в этом случае делать — не понятно. А зачем тогда вообще явно сообщать об ошибке (да еще и с помощью проверяемого исключения), если реакция приложения на него все равно не может быть адекватно запрограммирована в силу отсутствия каких-либо подробностей?

Если бы от такой проблемы страдало только JMS API, то не было бы повода писать эту стать-призыв. Но, к сожалению, это очень часто встречаемый шаблон. А правильный подход к дизайну API высокого уровня таков: Чем на более высоком уровне работает тот или иной программный API, тем больше информации для обработки ошибок содержится в его собственной реализации. Именно там, где есть эта информация, ошибки и должны обрабатываться. API высокого уровня не должно пробрасывать ошибки возникающие в его реализации тем, кто его использует. Надо помнить об инкапсуляции деталей реализации и в контексте обработки ошибок. "Заворачивание" ошибки возникшей на нижнем уровне (как, например, IOException) в ошибку высокого уровня (как, например, JMSException) это не инкапусляция. API высокого уровня должно либо обработать ошибку низкого уровня самостоятельно (например, API для надежной доставки сообщений должно, в общем случае, пытаться его доставить — в этом и есть смысл надежной доставки), либо, если решение о стратегии обработки ошибки зависит от конкретного приложения, использующего этот API, рапортовать ему ошибку со всеми высокоуровневыми подробностями, которые позволят приложению принять решения о стратегии обработки этой ошибки.

Например, в dxFeed API вся обработка ошибок полностью инкапсулирована внутри его реализации. Приложению вообще не надо заботиться что делать в том или ином случае, ибо о максимально оперативной доставке для него всех самых свежих рыночных данных мы уже позаботились, включая автоматическую переустановку порвавшихся соединений и т.п. Но, например, в нашей собственной реализации RMI (Remote Method Invocation) для удаленного вызова сервисов, не все ошибки могут быть обработаны самой реализацией RMI API. То есть понятно, что в случае потери соединения надо его восстановить, но что делать с вызовом, который был в процессе передачи, в общем случае не понятно (ибо зависит от специфики вызываемого метода). Поэтому такой вызов завершается с ошибкой, но все типы ошибок, которые могут возникнуть, подробно систематизированы и описаны, чтобы каждое приложение могло принять обоснованное решение об обработке той или иной ошибки.

UPDATE: Более развернуто о специфике именно проверяемых исключений можно почитать в моей следующей заметке.


  • 1
  • 1
?

Log in

No account? Create an account