In Ruby, interfaces are not really a part of the programming paradigm. By its very nature, Ruby is designed to be flexible and loosely-typed, and that provides developers with a lot of power. Over the years I’ve seen a number of projects that are written in Ruby become internally inconsistent. What I mean by this is that developers decide that their abstractions or structure isn’t working and instead of working from the bottom up, just start writing modifications to make things work. Making things work is incredibly valuable, but it can lead to piling up technical debt. Some of these problems feel as though they could be well solved by enforcing interfaces upon developers who are following your programming model.

Take, for example, Fog.io, which I mentioned in a previous post. Fog has a lot of interesting things going on but one of my primary frustrations is the lack of consistency between providers, which is exactly the problem that Fog should be solving. And Fog is not alone, I have seen this problem both on open source and internal projects (even at large organizations with a significant amount of programming discipline and policy).

Just because Ruby does not formally support interfaces in the same way Java or C# do, doesn’t mean that it is impossible to maintain a set of interfaces in your code and ensure that developers are adhering to those interfaces. One idea that I have is to use a common set of spec tests that traverse the inheritance tree of your “interface” classes in your project and then assert that those descendants follow the specification.

Take for example, some code in Ruby that looks like this:

An example interface in Ruby

class FunctionalInterface
   def do_thing
     raise "This is not implemented!"
   end
end

class Herp < FunctionalInterface
   def do_thing
      puts "herp"
   end
end

class Derp < FunctionalInterface
  def do_thing
    puts "derp"
  end
end

class Blorp < FunctionalInterface
  def do_some_other_thing
     puts "whoops"
  end
end

Here, we have three classes, FunctionalInterface, Herp and Derp; the interface doesn’t actually implement any code, only raises an exception if you call the methods. By doing this you guarantee that anyone implementing this interface needs to override the methods or else they will see an exception in their code. Now if you develop some RSpec tests that exercise this you can guarantee that anything implementing this interface at least implements the proper methods:

describe FunctionalInterface do
	before :all do
	  @implementations =
	    ObjectSpace.each_object(Class)
	      .select { |klass| klass < FunctionalInterface }
	end

	it "should enforce that the interface is implemented" do
	  @implementations.each do |klass|
	    entity = klass.new
	    expect { entity.do_thing }.to not_raise
	  end
	end
end

This is purely a naive example, and fog has many more extensions but the theory is the same. To keep things sane, enforce that any new extensions implement the interface before they are accepted as part of the project and that they pass the basic tests of taking the proper parameters and returning the proper data. If the interfaces are implemented correctly you can even go beyond unit testing to functional tests that actually talk to the upstream service providers and they should all behave accordingly.

I do not argue that interfaces are always necessary; I have seen many examples of putting the cart before the horse where someone has designed interfaces for things that only have one implementation. However, there are plenty of times when I long for interfaces in Ruby akin to the ones in Java, C# or other languages.

Filed under: ,