“Assume Everything Will Break” Development

In Ruby, and Ruby on Rails, the community is very big on Test Driven Development (TDD). This is good! And it’s especially important when working with a team. But what patterns will you develop in buidling a huge Rails application on your own without tests? Well I’d like to call it “Assume Everything Will Break Development” (AEWBD), but I prefer the shorter acronym AEWB. I believe you can learn important lessons from the AEWB experience.

The pattern of AEWB is to assume every retrievable Object can, or will, be nil. If you are expecting an Array of results, or your view expects to display an Object, calling methods on those Object will crash with an Undefined method for NilClass error. This leads to a terrible experience for the End User who then may think your site, or those responsible, are bad and not worth their time. You never want the User Experience to be bad.

So how can we ensure that this problem never(tongue in cheek) occurs for the End User? The answer is to design everything(tongue in cheek) that will effect the User Experience to work with nil. That’s right. Don’t fight nil, befriend it. You might even call that NDD; Nil Driven Development.

Now in a standard Ruby Object design pattern you can use Duck Typing to build a Nil version of your Object Class. I won’t be going over duck typing as this is not pure AEWB since you can’t Duck Type everything. And when I say ‘everything’ I use the word loosely.

So let’s get to some code. Let’s say you are expecting an Array of results and you want to perform an action on each result.

nick_names = NickName.where(is_cool: true) nick_names.map(&:name) ["Johny Bravo", "Elvis Presley"] 1 2 3 4 nick_names = NickName . where ( is_cool : true ) nick_names . map ( & : name ) [ "Johny Bravo" , "Elvis Presley" ]

Now here we called map on the resulting Array and used a proc to call the name method on each Object within the Array. But sometimes when we get our Array back it’s empty. In the case of map there is no problem because it only executes once per item. And since there are no items; it doesn’t execute. But if we only wanted the first nickname then we start getting into trouble.

nick_names = NickName.where(name: "George W. Bush").where(is_cool: true) nick_names.first.name # NoMethodError: undefined method `name' for nil:NilClass 1 2 3 4 nick_names = NickName . where ( name : "George W. Bush" ) . where ( is_cool : true ) nick_names . first . name # NoMethodError: undefined method `name' for nil:NilClass

So… since nil will be rearing it’s head many of times in our development experience… what can we do about it?

First DO NOT change nil’s method_missing. You would be asking for trouble with that. And don’t try putting equivalent methods for everything in nil (like def name). The solution I recommend most highly is Rails’ “try” method. The try method will attempt to call your method, parameters, and block arguments and if unsuccessful it will return nil.

So let’s do the last example with try:

nick_names = NickName.where(name: "George W. Bush").where(is_cool: true) nick_names.first.try(:name) # => nil 1 2 3 4 nick_names = NickName . where ( name : "George W. Bush" ) . where ( is_cool : true ) nick_names . first . try ( : name ) # => nil

And we have successfully avoided an error and received a graceful nil. This is what is so wonderful about try. It can be chained into any location, and on any method, in Rails. So what if you wanted to chain commands?

nick_names.first.name.capitalize.reverse # NoMethodError: undefined method `name' for nil:NilClass 1 2 3 nick_names . first . name . capitalize . reverse # NoMethodError: undefined method `name' for nil:NilClass

Well if we assume that at ‘first ‘ we get nil, then it will break at ‘name’. If we put ‘try’ on the nil Object from ‘first’ for ‘name’, then we know try(:name) will also be nil. So we continue the try chain.

nick_names.first.try(:name).try(:capitalize).try(:reverse) # => nil 1 2 3 nick_names . first . try ( : name ) . try ( : capitalize ) . try ( : reverse ) # => nil

So build your code to be ready for nil and it’s smooth sailing. Now even if what we don’t have a result it won’t crash the view for the End User. If some detail is missing, it’s not a train wreck.

Well the above code is a bit ugly. Can we avoid putting so many trys in the chain? The answer is yes! Try also takes a block. So the previous code can be written as follows: (Note since we know where nil will be at, calling ‘first’, after that is where we put try. We put try on potential nil Objects when invoking methods on them)

nick_names.first.try {|t| t.name.capitalize.reverse } # => nil 1 2 3 nick_names . first . try { | t | t . name . capitalize . reverse } # => nil

Success! It’s not quite as ugly. We can use this and not feel like an insane code dis-embodier. What about a view for a Rails page?

# Lets say all but the last comment is hidden in a click-to-expand comment section <% comment = @comments.last %> <%# may be nil if no comments exist %> <% begin %> <%= comment.username %> | <%= comment.created_at %> <p><%= comment.content %></p> <% end %> 1 2 3 4 5 6 7 # Lets say all but the last comment is hidden in a click-to-expand comment section <% comment = @comments . last %> <% # may be nil if no comments exist %> <% begin %> <%= comment . username %> | <%= comment . created_at %> < p > <%= comment . content %> < / p > <% end %>

In this example all three method calls on ‘comment’ could be attempting on nil. This will break out in an error. But instead of putting try(:method_name) for each method it is better to use the ‘if’ keyword argument. To not uglify this code simply place ‘if’ after ‘end’.

<% end if comment %> 1 2 <% end if comment %>

The entire block will only run if ‘if’ evaluates to true. Nil Objects evaluate as false so the block will not run when the comment Object is nil.

More About try

‘try’ can take arguments as well eg: try(:method, :arguments, :etc). If you want to access the fifth element on an Array you place the boxxy symbols [] as the method to try and then the index argument you would have placed in the boxxy symbols as the next argument.

[1,2,3,4,5,6,7,8,9].try(:[], 4) # => 5 1 2 3 [ 1 , 2 , 3 , 4 , 5 , 6 , 7 , 8 , 9 ] . try ( : [ ] , 4 ) # => 5

So you can get very creative with try. Like this example from my Youtube Utility where I process a page result that is scraped with Mechanize/Nokogiri.

{ view_count: result.css('li').select {|i| i.try(:text) =~ /^[\d,]{1,} views/ }.first.try(:text).try(:split).try(:first).try(:gsub,",","_").to_i } 1 2 3 4 5 6 { view_count : result . css ( 'li' ) . select { | i | i . try ( : text ) = ~ / ^ [ \ d , ] { 1 , } views / } . first . try ( : text ) . try ( : split ) . try ( : first ) . try ( : gsub , "," , "_" ) . to _ i }

You might say. Why all the trys? Why not just try block the whole thing?

{ view_count: try { ... } } 1 2 3 4 { view_count : try { . . . } }

Most of the code in this project I have done in this simple way. But when an error was raised and it’s hard to track down where at, then the Paranoia Design Pattern kicks in ;-). In all likeliness all those trys weren’t necessary. But after things started working well I felt the Lazy if-it-works-don’t-fix-it Principle.

Now there are cases where try won’t work. I have found that if an Object has re-raised an error by chaining an additional error class; then try will fail out with that error. You can reproduce this result by using Mechanize to query a page that does not exist. So in those cases where multiple chained errors are involved you can mimic trys behavior with ‘rescue’.

4/0 * 10 # ZeroDivisionError: divided by 0 (4/0 rescue nil).to_i * 10 # nil.to_i always converts nil to 0 # => 0 begin 4/0 resue nil end.to_i * 10 # => 0 1 2 3 4 5 6 7 4 / 0 * 10 # ZeroDivisionError: divided by 0 ( 4 / 0 rescue nil ) . to_i * 10 # nil.to_i always converts nil to 0 # => 0 begin 4 / 0 resue nil end . to_i * 10 # => 0

I’ve also used this method for implementing backwards compatibility to methods whose syntax have changed over time. You can see my example in my pull-request for Compass Rails.

So, if all else fails ;-), use ‘rescue’ in place of ‘try’.

In Summary

In “Assume Everything Will Break Development” (AEWBD) you will always be thinking in terms of what can be nil, and then use it to your advantage. If you’re requesting an Object then that’s a pretty good place to invoke NDD. It’s all about protecting the User’s experience.

The code order to follow for this is either ‘if’ or ‘try’; and if you must ‘rescue’. Know that ‘nil’ is a perfectly fine truthfulness test and use it.

AEWB and NDD are for graceful failures. The whole idea is to protect the End User from a bad experience. These are not all encompassing practices. In development there are many places where you will want a valid fail and error response. They are generally helpful, informational, and there to protect you. So AEWB/NDD are practices in protecting user experience. They are not the fail-safe switch to protect you from bad, and potentially harmful, code design. Use the NDD for safe places to allow the unexpected to be graceful. If Security is vital for the Object/Information in question then don’t use NDD for that instance. It is not meant for that.

Now with this knowledge in hand you can combine AEWB/NDD with TDD. Your tests will be shorter, and your peace of mind the better for it.

Just to clarify AEWB and NDD are not the same. The words themselves clarify the meaning. AEWB can, and usually does, include NDD. But NDD doesn’t necessarily include AEWB.

I hope this information was both insightful and useful for you! Please comment, share, subscribe to my RSS Feed, and follow me on twitter @6ftdan!

God Bless! -Daniel P. Clark