Exceptional and Substitutable

I watched Sean Chambers deliver an excellent talk about S.O.L.I.D. principles last weekend at Tallahassee CodeCamp. It motivated me to look a little deeper into the Liskov Substitution Principle, which states:

Functions that use pointers or references to base classes must be able to use objects of derived classes without knowing it.

We accept this as a truth and it’s what makes us hate the is keyword in C# so damn much. I wrote a post a while back that explains (or attempts to) what LSP and why it’s important, which is to say I won’t harp on the what and why here. I’ll spend your attention, rather, on how LSP works with exceptions.

The LSP article on Wikipedia provides clarification around exceptions and subclasses:

No new exceptions should be thrown by methods of the subtype, except
where those exceptions are themselves subtypes of exceptions thrown by
the methods of the supertype.

Here’s a contrived and fugly example in ruby:

class FileSystem

  def save(path, text)    File.open(path, 'w+') {|f| f.write(text) }  end

end

class AmazonFileSystem < FileSystem

  include 'amazon_library'

  def save(path, contents)    begin      write_to_s3(path, contents)    rescue      AmazonWebServicesError.new('cannot write file');    end  end

end

class AmazonWebServicesError < StandardError

  def initialize(message)    @message = message  end

  def message    return @message  end

end

def backup_system

  file_name = 'book.txt';  my_important_text = 'hello world';

  [AmazonFileSystem.new, FileSystem.new].each do |file_system|    begin      file_system.save(file_name)    rescue AmazonFileError      # VIOLATION! FOR SHAME!    end  end

end

A try-catch (or begin-rescue) is a conditional pattern and it’s my sense that when we use this with implementation inheritance we’ve made a particularly egregious and insidious violation of the LSP.

First off, we’re know things about the internals of our subclass in our consumer. We’ve broken encapsulation and increased coupling.

Secondly, our special AmazonWebServiceError only shares StandardError with what’s likely to be thrown by our FileSystem class. This means we can’t treat errors that are likely to fall out of Ruby’s IO core polymorphically. More conditional logic/branching, more cyclomatic complexity, more coupling, and these are bad things.

What can we do to stay on the good side of LSP when dealing with exceptions in inheritance hierarchies, you ask? Well, as the rule states, we can make exceptions subclass the exception the parent throws. For example, we could catch whatever error our fictitious amazon library throws and decorate it with a custom error that derives from the error File.write in ruby core throws. Another, perhaps, better option would be to hold off on coding defensively wherever you can and let the exception bubble up to a central part of your program that deals, exclusively, with these corner cases. That is, implement an exception shield wherever we don’t need compensating logic.

LSP and exceptions: another thing to pay attention to while on programming’s happy trail, friends.

This entry was posted in C#, design, oo, principles, Ruby, solid. Bookmark the permalink. Follow any comments here with the RSS feed for this post.

2 Responses to Exceptional and Substitutable

  1. Benjamin Arroyo says:

    I think Java’s “throws” clause it’s a nice to have in other languages.

  2. I wrote about the LSP just yesterday. Maybe you want to take a look and see if there are inaccurate affirmations:
    http://giorgiosironi.blogspot.com/2009/09/solid-part-3-liskov-substitution.html

Leave a Reply

Your email address will not be published. Required fields are marked *

*

You may use these HTML tags and attributes: <a href="" title=""> <abbr title=""> <acronym title=""> <b> <blockquote cite=""> <cite> <code> <del datetime=""> <em> <i> <q cite=""> <strike> <strong>