“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"]
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
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
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
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
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
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 %>
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 %>
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
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 }
You might say. Why all the trys? Why not just try block the whole thing?
{ 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
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
Lewis Buckley
December 28, 2014 - 4:41 am
Instead of try, often it makes more sense to use presence.
http://apidock.com/rails/Object/presence
Daniel P. Clark
December 28, 2014 - 1:50 pm
Presence is great! Thank you for sharing it. But it’s not a replacement for try as it does not take any arguments. I do believe presence should be used as well as try in development.
Jeff Dickey
December 29, 2014 - 2:12 am
So IIUC, this and tell-don’t-ask approaches like Confident Ruby would regard each other as antipatterns, yes?
Daniel P. Clark
December 29, 2014 - 9:06 pm
I wouldn’t think so. Both Confident Ruby and this use “Simple habits to eliminate the dreaded “NoMethodError for NilClass” exception.” Confident Ruby focusses on passing behavior while AEWB is more of a retrieve-data pattern.
Confident Ruby, Tell Don’t Ask, and East Oriented Code all focus more on passing data/behavior forward without backtracking. AEWB I wouldn’t consider backtracking or West Oriented code. I more consider it the starting line to go Eastward in Tell Don’t Ask. If you’re working with data it must be obtained before it is used. So this could very well be a precursor for all of the other patterns.
toddmeinershagen
January 1, 2015 - 4:10 pm
I played a little with implementing this pattern of Try in C# and started asking myself the same question. Is this an anti-pattern? If you disregard null values, are you just kicking the issue down the road. (This is an honest question – not sure.)
Wouldn’t it be better to check for Null values and throw an exception so that logging would catch the problem where the problem occurred? Otherwise, troubleshooting might be more difficult.
Daniel P. Clark
January 1, 2015 - 5:15 pm
It all depends on your purpose of doing it. My use case is a graceful fail to protect the user experience. The biggest issue I come across in Rails is the view has no issue dying in front of people if things aren’t kosher.
If you wanted you can monkey patch try to message to your logger whenever it’s incurred and still have a graceful nil situation. I think this is a great way to continue with a safe experience and get all the details for what needs to be updated along the way.
Jeff Dickey
January 1, 2015 - 10:59 pm
I like that answer, and the monkey patch is definitely something I’ll remember to try next time I’m spelunking someone else’s code.
Getting back to Todd’s question, I agree with what I understood to be Avdi Grimm’s point: too often, nil is just a seemingly handy flag for “things broke, oops” and conveys no useful information beyond that. Don’t use/check for nil for object instances where you mean “no specific value” like logged-in users (there’s a Gem for that). When you’re tempted to return nil from some code that’s going to have to be checked for all over the place, ask yourself if you could return a null object instead.