Fault Tolerance

sandal edited this page Dec 7, 2011 · 7 revisions

While raising an exception at the first point of failure can be a good strategy for low level code, the closer you get to application programmers, the more that strategy forces tedious code up to the surface. Especially when it comes to common rather than exceptional errors, a greater degree of tolerance can be beneficial. We see a perfect example of this in the way that ActiveRecord works.

When you work with an ActiveRecord model, it does not raise an exception by default when you attempt to save a record which does not pass its validations. Instead, it prevents the data from being persisted and then lists the errors in an attribute which can then be easily converted to a human readable form. In the context of Rails, this is a better solution than having to rescue an exception just to tell a user that they forgot to enter their email address in a form field. To balance things out, ActiveRecord also provide an alternative method (called save!) which will raise an exception rather than doing this softer form of error reporting, but this method tends to be used more for debugging purposes and for situations in which records which fail to validate are truly exceptional circumstances.

For those who aren't regular users of ActiveRecord, the plain old ruby object below provides a poor man's approximation of the approach it takes:

class Person
  def initialize(params={})
    @name  = params[:name]
    @email = params[:email]
  end

  attr_accessor :name, :email, :errors

  def valid?
    @errors = []

    @errors << "Must have a name" unless @name
    @errors << "Must have an email address" unless @email

    @errors.empty?
  end

  def save
    valid? && puts("person data saved!")
  end

  def save!
    raise "Invalid person data" unless valid?
    
    puts("person data saved!")
  end
end

person = Person.new(:name => "Gregory")
person.save
p person.errors #=> ["Must have an email address"]
person.email = "gregory.t.brown@gmail.com"
person.save #=> OUTPUTS "person data saved!"
Person.new.save! #=> raises an error

While this particular fault tolerance strategy is probably a good one in the context of ActiveRecord, there are other options available if you want to apply this pattern in your own project. One possibility is to have a single method which combines both the fault tolerant behavior and exception raising behavior and selects which one to use based on the value of the $DEBUG flag or something similar. Another option would be to inject a custom error handling object of some sort. Using this approach, an object which simply logged the error and moved on might be used in production, but a different object with the same interface may be used in development to cause exceptions to be raised. This approach is pretty heavyweight but provides the greatest degree of flexibility to control the way that errors get handled.

For deeper reading on this topic, I'd like to refer Practicing Ruby readers to the excellent e-book Exceptional Ruby by Avdi Grimm. He covers a lot of different options for dealing with this sort of problem in that book, as well as many other issues related to exception handling.


Turn the page if you're taking the linear tour, or feel free to jump around via the sidebar.

You can’t perform that action at this time.
You signed in with another tab or window. Reload to refresh your session. You signed out in another tab or window. Reload to refresh your session.
Press h to open a hovercard with more details.