Best practice: Using system supplied or custom exceptions for error conditions in ruby?
Writing a rather simple command line tool in ruby I need to report meaningful messages on errors in the command line arguments, or for that matter other error conditions in the program. (Input file not found, invalid format of input etc)
For now I just raise ArgumentError with a sensible description when detecting errors in the argument list. Is this good practice, or do I risk hiding programming errors as well with this approach? In other words, are the system defined exceptions in ruby meant for application usage, or should we always create our own exceptions for reporting non-system errors?
Edit: As an example, ruby raises ArgumentError if I call a method with the wrong number of arguments. This is a programming erro开发者_如何学编程r that I want to be told about with stack traces and all. However when input to my program is incorrect I may want to give a brief message to the user, or even ignore it silently. This suggests to me that ArgumentError is not suitable for the applications own use.
The Ruby 1.9 exception hierarchy is as follows:
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
The Ruby 2 exception hierarchy is:
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
You can use one of these, as you have, or create your own. When creating your own, there are a couple of things to note. For one, rescue
by default only rescues StandardError
and its descendants. I found this out the hard way. It's best to make sure your custom errors inherit from StandardError
. (By the way, to rescue any exception, use rescue Exception
explicitly.)
You can create an exception class complete with attributes, custom constructors, etc. but standard practice is to just create simple definitions:
class ProcessingError < RuntimeError; end
You can differentiate specific errors with different error messages, or create a hierarchy of errors. Creating elaborate exception hierarchies is generally not done, or at least I've not seen an example of it (and I tend to read the source of libraries I use). What I have seen is the use of a Module to namespace your errors, which I think is a good idea.
module MyLibrary
class Error < StandardError; end
class ConnectionError < Error; end
end
Then your exceptions will be of the form MyLibrary::ConnectionError
and to rescue errors from your library specifically you can rescue MyLibrary::Error
and catch them all.
Note: an alternative syntax is MyError = Class.new(RuntimeError)
see the reference to Steve Klabnik's blog post below.
References:
- Ruby's Exception Hierarchy by Nick Sieger.
- Raising the Right Exception by Jamis Buck.
- Random Ruby Tricks: Class.new by Steve Klabnik.
A fantastic read: Exceptional Ruby by Avdi Grimm
I believe the best practice is to raise your own custom error, namespaced in a module. All of your specific exception classes should inherit from one namespaced exception that inherits from StandardError. So for your case:
module MyApp
class Error < StandardError; end
class ArgumentError < Error; end
end
and raise MyApp::ArgumentError when the user provides bad arguments. That way it differentiates from an argument error in your code. And you can rescue any uncaught exception from your app at a high level with MyApp::Error.
You should also check out Thor. It can handle most of the user argument stuff for you. You can look at Bundler for a good cli usage example.
精彩评论