Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

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 error 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.

like image 370
harald Avatar asked Dec 07 '10 10:12

harald


2 Answers

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

like image 63
Mark Thomas Avatar answered Oct 22 '22 10:10

Mark Thomas


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.

like image 44
Justin Blake Avatar answered Oct 22 '22 11:10

Justin Blake