Take me home

rescue nil is not awesome

Published September 11, 2008

Deliberately calling code that may raise an error is ugly. Just look at it:

person.name.gsub(/foo/, 'bar') rescue nil

As person.name returns nil if no name has been set, person.name.gsub would raise a NoMethodError in the event of a nameless person. nil has no gsub method. Because we know this, we rescue. So, we deliberately call code that may raise an error. Which is ugly.

Instead, you should determine wether or not the code you call can raise an error, and then just not call it.

person.name.gsub(/foo/, 'bar') if person.name

This is also sort of odd, though. If getting a persons name takes 2 seconds, the additional call to person.name sucks. Etc.

Introducing try!

class Object
  def try(*args, &block)
    return if self.nil?
    block_given? ? yield(self) : self.__send__(args.shift, *args)
  end
end

Yes! Now you can do this instead:

person.name.try(:gsub, /foo/, 'bar')

An actual working sample:

class Person
  attr_reader :name

  def initialize(name = nil)
    @name = name
  end
end

p = Person.new() # name is nil!
p.name.upcase
# => NoMethodError: undefined method ?upcase? for nil:NilClass

p.name.try(:upcase)
# => nil

p.name.try(:gsub, /foo/, 'bar')
# => nil

p.name.try {|n| n.upcase.reverse }
# => nil

Every call returns nil, because name is nil. The point is that no errors were raised.

Here's how it looks when name isn't nil:

p2 = Person.new('Joe')

p2.name.try(:upcase)
# => "JOE"

p2.name.try(:gsub, /J/, 'A')
# => 'Aoe'

p2.name.try {|n| n.upcase.reverse }
# => "EOJ"

Questions or comments?

Feel free to contact me on Twitter, @augustl, or e-mail me at august@augustl.com.