Please stop using ||= all the time

matmooredev

Mat

Posted on April 18, 2022

Please stop using ||= all the time

In ruby code I often see people writing code like

def foo
    @foo ||= 'bla bla bla'
end
Enter fullscreen mode Exit fullscreen mode

Instead of

def initialize
    @foo = 'bla bla bla'
end

attr_reader :foo
Enter fullscreen mode Exit fullscreen mode

The duck operator ||= is used to lazily initialize the instance variable, instead of initializing the instance variable in the initialize method.

This is possible in ruby because assignments are also expressions, and all methods have an implicit return value. So @foo ||= 123 always evaluates to 123 but never assigns to @foo more than once.

While this is very concise, I found it odd when I first came to ruby after coding in other languages. It feels like a very roundabout way of doing things. If you are initializing instance variables why not use the initializer?

The lazy initialization pattern is one way to avoid running an expensive computation that might not be needed. However, if you know that the initialization is always going to be required for the object to be usable, then there are no performance benefits to structuring it this way.

Structuring code like this makes dependencies a bit harder to notice just from looking at the class. Users of the class might be misled into thinking that the object is fully initialized when it is not, and this can make it harder to initialize objects in a test or in other parts of the codebase.

In practice, I get suspicious even when the initialization is optional, because the optional initialization is still separate from the initializer and so it is less obvious when reading the code. Optional behaviour can also be sign that the class has more than one responsibility.

You can always refactor the class to avoid this. For example, consider using dependency injection to depend on the result of the computation instead of doing the computation internally.

def initialize(foo: nil)
    @foo = foo
end

# ...
# methods that use `foo`
# ...

private

attr_reader :foo
Enter fullscreen mode Exit fullscreen mode

Lazy initialization is a special case of temporal coupling: the behaviour of the class is allowed to vary depending on what order you call its methods in. The more complex the behaviour, the harder it is to reason about the code and the easier it is to introduce bugs. In this case, if other methods of your class depend on the instance variable, then they could read nil if the initializer method isn't called first.

Side note: I see this problem a lot with rails controllers, which do not get initialized on a per-request basis, and force you to set instance variables within your action methods as a way to communicate with the view. This design is unfortunate, but you can still avoid the problem elsewhere in your codebase.

On the other hand, if all the initialization for the class is handled by the initialize method, it's quite easy to make the behaviour of the class predictable. You can use command-query responsibility segregation (CQRS) to ensure that messages you send to the object either ask for information, or change the objects state, but not both.

Photo by Francesco Altamura from Pexels

💖 💪 🙅 🚩
matmooredev
Mat

Posted on April 18, 2022

Join Our Newsletter. No Spam, Only the good stuff.

Sign up to receive the latest update from our blog.

Related