Лучшая практика: использование системных или пользовательских исключений для условий ошибок в ruby?

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

пока я просто поднимаю ArgumentError с разумным описанием при обнаружении ошибок в списке аргументов. Это хорошая практика, или я рискую скрыть ошибки программирования также с этим подходом? Другими словами, система определенные исключения в ruby предназначены для использования приложений, или мы всегда должны создавать свои собственные исключения для сообщений о несистемных ошибках?

Edit: Например, ruby вызывает ArgumentError, если я вызываю метод с неправильным количеством аргументов. Это ошибка программирования, о которой я хочу рассказать со следами стека и всем остальным. Однако, когда ввод в мою программу неверен, я могу дать краткое сообщение пользователю или даже игнорировать его молча. Это наводит меня на мысль это ArgumentError не подходит для приложений собственного использования.

2 ответов


Я считаю, что лучшей практикой является создание собственной пользовательской ошибки, пространства имен в модуле. Все конкретные классы исключений должны наследовать одно исключение из пространства имен, наследуемое от StandardError. Поэтому для вашего случая:

module MyApp
  class Error < StandardError; end
  class ArgumentError < Error; end
end

и поднять MyApp:: ArgumentError, когда пользователь предоставляет плохие аргументы. Таким образом, он отличается от ошибки аргумента в вашем коде. И вы можете спасти любое необработанное исключение из своего приложения на высоком уровне с помощью MyApp:: Ошибка.

вы также должны проверить Тор. Он может обрабатывать большую часть аргументов пользователя для вас. Вы можете посмотреть упаковщик для хорошего примера использования cli.


иерархия исключений 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 явно.)

вы can создайте класс исключений с атрибутами, пользовательскими конструкторами и т. д. но стандартная практика состоит в том, чтобы просто создавать простые определения:

class ProcessingError < RuntimeError; end

вы можете различать определенные ошибки с различными сообщениями об ошибках или создавать иерархию ошибок. Создание разработки иерархии исключений обычно не выполняются, или, по крайней мере, я не видел примера этого (и я, как правило, читаю источник библиотек I использовать.) То, что я видел, - это использование модуля для пространства имен ваших ошибок, что я считаю хорошей идеей.

module MyLibrary
  class Error < StandardError; end
  class ConnectionError < Error; end
end

тогда ваши исключения будут иметь вид MyLibrary::ConnectionError и для спасения ошибок из вашей библиотеки конкретно вы можете rescue MyLibrary::Error и поймать их всех.

Примечание: альтернативный синтаксис MyError = Class.new(RuntimeError) см. ссылку на сообщение в блоге Стива Клабника ниже.

ссылки:

фантастическое чтение: Красивый Рубиновый Авди Гримм