Лучшая практика: использование системных или пользовательских исключений для условий ошибок в рубине?
Написание довольно простого инструмента командной строки в ruby Мне нужно сообщать о значимых сообщениях об ошибках в аргументах командной строки или, что то же самое, о других условиях ошибки в программе. (Входной файл не найден, неверный формат ввода и т.д.)
На данный момент я просто поднимаю ArgumentError с разумным описанием при обнаружении ошибок в списке аргументов. Является ли это хорошей практикой, или я рискую скрывать ошибки программирования, а также с этим подходом? Другими словами, являются ли определенные в системе исключениями в рубине, предназначенными для использования приложения, или мы всегда должны создавать собственные исключения для сообщения о несистемных ошибках?
Edit:
Например, ruby вызывает ArgumentError, если я вызываю метод с неправильным числом аргументов. Это ошибка программирования, о которой я хочу рассказать со стеком и всеми остальными. Однако, когда вход в мою программу неверен, я могу дать краткое сообщение пользователю или даже игнорировать его молча. Это говорит мне о том, что ArgumentError не подходит для собственных приложений.
Ответы
Ответ 1
Я считаю, что лучшая практика заключается в том, чтобы поднять вашу собственную пользовательскую ошибку, именуемую в модуле. Все ваши конкретные классы исключений должны наследоваться от одного исключения, которое наследуется от StandardError. Итак, для вашего случая:
module MyApp
class Error < StandardError; end
class ArgumentError < Error; end
end
и поднять MyApp:: ArgumentError, когда пользователь предоставляет плохие аргументы. Таким образом, он отличается от ошибки аргумента в вашем коде. И вы можете спасти любое неперехваченное исключение из своего приложения на высоком уровне с помощью MyApp:: Error.
Вы также можете проверить Thor. Он может обрабатывать большинство аргументов пользователя для вас. Вы можете посмотреть Bundler для хорошего примера использования cli.
Ответ 2
Иерархия исключений Ruby 1.9 выглядит следующим образом:
Exception
+- NoMemoryError
+- ScriptError
| +- LoadError
| +- NotImplementedError
| +- SyntaxError
+- SignalException
| +- Interrupt
+- StandardError
| +- ArgumentError
| +- IOError
| | +- EOFError
| +- IndexError
| +- LocalJumpError
| +- NameError
| | +- NoMethodError
| +- RangeError
| | +- FloatDomainError
| +- RegexpError
| +- RuntimeError
| +- SecurityError
| +- SystemCallError
| +- SystemStackError
| +- ThreadError
| +- TypeError
| +- ZeroDivisionError
+- SystemExit
+- fatal
Иерархия исключений Ruby 2:
Exception
+- NoMemoryError
+- ScriptError
| +- LoadError
| +- NotImplementedError
| +- SyntaxError
+- SecurityError
+- SignalException
| +- Interrupt
+- StandardError # default for rescue
| +- ArgumentError
| | +- UncaughtThrowError
| +- EncodingError
| +- FiberError
| +- IOError
| | +- EOFError
| +- IndexError
| | +- KeyError
| | +- StopIteration
| +- LocalJumpError
| +- NameError
| | +- NoMethodError
| +- RangeError
| | +- FloatDomainError
| +- RegexpError
| +- RuntimeError # default for raise
| +- SystemCallError
| | +- Errno::*
| +- ThreadError
| +- TypeError
| +- ZeroDivisionError
+- SystemExit
+- SystemStackError
+- fatal # impossible to rescue
Вы можете использовать один из них, как есть, или создать свой собственный. При создании своего, есть несколько вещей, чтобы отметить. Во-первых, rescue
по умолчанию только спасает StandardError
и его потомков. Я нашел это с трудом. Лучше всего убедиться, что ваши пользовательские ошибки наследуются от StandardError
. (Кстати, чтобы спасти любое исключение, используйте rescue Exception
явно.)
Вы можете создать класс исключения с атрибутами, настраиваемыми конструкторами и т.д., но стандартная практика состоит в том, чтобы просто создавать простые определения:
class ProcessingError < RuntimeError; end
Вы можете различать определенные ошибки с различными сообщениями об ошибках или создавать иерархию ошибок. Создание сложных иерархий исключений, как правило, не выполняется, или, по крайней мере, я не видел его примера (и я, как правило, читаю источник библиотек, которые я использую). То, что я видел, - это использование модуля для пространственных имен ваших ошибок, которые, по моему мнению, являются хорошей идеей.
module MyLibrary
class Error < StandardError; end
class ConnectionError < Error; end
end
Тогда ваши исключения будут иметь форму MyLibrary::ConnectionError
и для спасения ошибок из вашей библиотеки, вы можете rescue MyLibrary::Error
и поймать их все.
Примечание: альтернативный синтаксис MyError = Class.new(RuntimeError)
см. ссылку на сообщение блога Стив Клабника ниже.
Ссылки:
Сказочное чтение: Исключительный Ruby от Avdi Grimm