Menu
Alfredo Motta
  • Home
  • Newsletter
  • Articles
  • About me
  • Events
  • Books
  • Research
  • Learn to code in London
Alfredo Motta

To raise or not to raise exceptions, and the art of designing return values

Posted on November 3, 2017May 18, 2022

Each time we call a function that’s meant to perform some operation that could succeed or fail we are always left with the same dilemma. What should be the return value? Should I return nil if a failure happened? Or I should throw an exception? What does failure means anyway?

Like every interesting question, the answer is it depends. In this blog post, I’ll bring clarity to the discussion and present what are the tradeoffs involved and how to choose between the existing alternatives.

Let’s start with an example when nil might be a good idea.

For that, let’s consider the Ruby Enumerable#find method 1. If you never used it before such method is responsible to search a value that satisfies the condition specified in the provided block. If the value can’t be found then the method returns nil like so:

Ruby
1
2
(1..10).find { |i| i == 42 }
=> nil

Now let’s stop for a second and ponder. Why is nil a valid return value for that function? The key idea is that element you are looking for may or may not be there. In other words, it is expected that a value may not be found and this is a specific trait of the function we are working on.

Coming up with such expectations is not a trivial job. Even functions with the same name can be interpreted differently. For example let’s consider to the ActiveRecord find method 2. Same name, different context. Here we don’t return nil when the object is not found but instead, we raise an exception as follows:

Ruby
1
2
3
4
5
begin
  my_record = Record.find(1)
rescue ActiveRecord::RecordNotFound => e
  puts "Could not find record with id 1"
end

Whether or not raising an exception depends on your context and what is commonly perceived as the norm.

But what is it that makes this method different? Here the goal is to find a record in the database given an id. The fact that we have a unique identifier at hands entails that the record does exist already and that unless something exceptional occurs we will find it in the database. This is why we raise an exception if we don’t. Whether or not raising an exception depends on your context and what is commonly perceived as the norm.

But the above discussion goes beyond nil versus raising exceptions. In fact being nil the billion dollar mistake of our time 3 whenever you find yourself leaning towards nil  you should spend time seeking for a reasonable fallback instead.

Whenever you find yourself leaning towards nil  you should spend time seeking for a reasonable fallback.

The parse method of the nokogiri gem is a great example for that. When trying to parse a nil document the library does not raise an exception and returns a fallback value instead, an empty HTML::Document object like so:

Ruby
1
2
document = Nokogiri::HTML::Document.parse(nil)
=> #<Nokogiri::HTML::Document:0x3fe82eb44d5c name="document" children=[#<Nokogiri::XML::DTD:0x3fe82eb449c4 name="html">]>

Now you can compose such empty document with subsequent operations and get back reasonable values instead of checking for nil all the time, like so:

Ruby
1
2
Nokogiri::HTML::Document.parse(nil).css('h1')
=> []

No silver bullet, please

From my previous discussion, you might conclude that if you ponder enough about your domain you will always find a good insight that will let you decide whether or not you should raise an exception or if a good fallback value exist. Unfortunately, this is not true and a simple way to demonstrate it is to look at how division by zero works in different languages:

Ruby division by zero
Ruby
1
2
i = 3/0
# Ruby, ZeroDivisionError: divided by 0

Javascript division by zero
JavaScript
1
2
var i = 3/0;
// Javascript, returns Infinity

PHP division by zero
1
2
$i = 3/0
// PHP, returns INF and a warning

Python division by zero
Python
1
2
i = 3/0
# Python, ZeroDivisionError: division by zero

As you can see, you are not the only one confused here! When every other heuristic fails, the safest bet is to just follow the convention of the codebase. In other words, if under a given context (for example API calls) every other function is raising an exception (for example if the API call fails) then by following the existing convention you respect the Principle of least surprise 4 and you will make every other developer in your team happier.

When every other heuristic fails, the safest bet is to just follow the convention of the codebase

Conclusion

Return values and nil belongs to one of the top 100 answers in StackOverflow and I probably did not give them enough justice in this short blog post so I would invite you to dig deeper, it’s a fascinating discussion. 5 6

There is a lot that can be done beyond using nil and exceptions, for example with tools like monads or response objects. However, this is a broader topic and I will leave such discussion for a follow-up blog post.

If you are interested in reading more please let me know in the comments and if you enjoyed this blog you can also follow me on twitter.

References

  1. Ruby enumerable find method
  2. Rails ActiveRecord find method
  3. Null References the billion dollar mistake
  4. Wikipedia: Principle of least surprise
  5. StackOverflow: Avoiding != null statements
  6. Should a retrieval method return ‘null’ or throw an exception when it can’t produce the return value?

2 thoughts on “To raise or not to raise exceptions, and the art of designing return values”

  1. Kris Leech says:
    November 3, 2017 at 9:36 am

    Nice post. To add to your conclusion two ideas I like the sound of are:

    Railway Orientated Programming: https://www.slideshare.net/ScottWlaschin/railway-oriented-programming
    And to never return nil but an empty array, even for things like #find. So you would always iterate over the results, #find and #where would always return a collection.

    Reply
    1. mottalrd says:
      November 3, 2017 at 11:58 am

      Very sound advice Kris! Thank you!

      Reply

Leave a Reply Cancel reply

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

Answer the question * Time limit is exhausted. Please reload CAPTCHA.

This site uses Akismet to reduce spam. Learn how your comment data is processed.

Tweets by mottalrd

Recent Comments

  • mottalrd on Not So Random Software #21 – Technical debt
  • mottalrd on Not So Random Software #24 – Climate Change Tech
  • mottalrd on A/B Testing, from scratch
  • mottalrd on A/B Testing, from scratch
  • Karim on A/B Testing, from scratch
©2023 Alfredo Motta | Powered by SuperbThemes & WordPress