“!” and “?”: Understanding One of Ruby’s Coolest Naming Conventions

“Well, that’s quite some symbols you got there ♨_♨” – You, after reading this article’s title.

It was a calm Friday afternoon here at CodeMiner42, when one of my co-workers from another office came up with an interesting question in the company’s chat:

“Guys, are the ! and ? that we find at the end of method names a convention of Ruby, RoR, or whom else?”

Well, that’s a fair question, and I hope I can increase your knowledge by sharing my answer here.

It’s all about conventions, in the end

First of all, from an implementation standpoint, naming a method with ! or? at the end has no effect in the method’s functionality. With that, I mean that a method whose name ends with these marks is just as ordinary as any other. You are not bound to return a value with a specific type nor anything like this.

This convention is all a matter of code readability.

As a programming language, one of Ruby’s main features is having “an elegant syntax that is natural to read and easy to write”. Idiomatic Ruby code should resemble reading an English text. These naming conventions came up to help reach that goal.

Did you say Friday?

Let’s talk about questions. Consider this one: “Can you buy some drinks?”

Notice two things about it:

  • It has the intention of fetching a “yes” or “no” answer from whom is being asked;

  • It changes absolutely nothing within whom is being asked;

And as so should our methods with a ? suffix be!

The ? tells me, the person reading your code, that this method (also called predicate method) will return a boolean-like value (truthy or falsy — it does not have to be a strict boolean) and it should* *not yield side effects (you can read more about side effects here) or alter the object’s state. We have some out-of-the-box examples in Ruby like:

"".empty?     # => true
nil.nil?      # => true
[1].nil?      # => false

(Which answers our first question: it’s a Ruby convention!) ┐( ̄ヮ ̄)┌

With that in mind, you can implement situations like this one:

class Country
  attr_reader :legal_age

  def initialize(legal_age:)
    @legal_age = legal_age
  end
end

class Person
  attr_reader :age, :country

  def initialize(age:, country:)
    @age = age
    @country = country
  end

  def can_buy_drinks?
     age >= country.legal_age
  end
end

brasil = Country.new legal_age: 18

tom = Person.new(age: 17, country: brasil)

tom.can_buy_drinks? # => false

See how nice to read that last line is! (•̀ᴗ•́)و ̑

Bang!

The exclamation mark is… a bit more complicated.

Also called “bang methods”, despite being very widespread there’s not much consensus about its use.

That being, I’m going to enumerate a few situations where you could use a *“bang” *in one of your methods. So, if your method:

  • Performs a destructive action (like deleting something from a database);

  • Has side effects (writes to a file, calls for a user’s input, modifies non-local state);

  • Modifies the original object in which is being called;

  • Has potential to raise an exception;

Then use the bang!

But the most important tip is this one:

If you have a method that is marked with a bang at its end, then a version without it should also exist.

As my noble friend Thiago Araújo Silva stated to me:

The bang should be used exclusively as a more dangerous version of an already existing method. Therefore, as a rule, methods should not be written with the bang. But that convention only concerns the Ruby core language.

Take the following code as an example:

my_hash = {}         # => {}

my_hash.merge id: 1  # => {id: 1}
my_hash              # => {} - my_hash did not change

my_hash.merge! id: 1 # => {id: 1}
my_hash              # => {id: 1} - my_hash changed

The merge method returns a merged (“Ohh really? (⊙_☉)”) version of the hash but keeps the original object intact (the return value and the original object are different objects), while merge! actually modifies it.

One could even say the “bang version” is an “overheated version” of the method, like it says “I AM DANGEROUS!” or “I CAN RAISE AN EXCEPTION!!” or “I’LL FLIP THIS TABLE!!! (ノಠ ∩ಠ)ノ︵ ┻━┻”.

As the last drop of wisdom and advice, I would say this:

When working on a project, try to keep up with the project’s convention. There is a variety of situations where you could use this to write your methods, so it’s very important to keep your codebase consistent. Keep that in mind ʕᵔᴥᵔʔ

A special case

Remember that I said predicate methods should NOT change the object’s global state or yield side effects? Well… In Ruby’s standard library there’s a class called Set: “[It] is a hybrid of Array’s intuitive inter-operation facilities and Hash’s fast lookup”.

In any instance of a Set, you’ll find two methods: add and add?. Let’s check this nice gist below:

require 'set'
set = Set.new  # => <Set: {}>
set.add 1      # => <Set: {1}>
set.add 2      # => <Set: {1, 2}>
set            # => <Set: {1, 2}>
set.add 1      # => <Set: {1, 2}>
set            # => <Set: {1, 2}>

# looks okay, right? now check this out:

set = Set.new  # => <Set: {}>     # Wait for it...
set.add? 1     # => <Set: {1}>    # okay, it have modified the original object... ಠ_ಠ
set.add? 2     # => <Set: {1, 2}> # okay, again... ಠ_ಠ
set            # => <Set: {1, 2}>
set.add? 1     # => nil           # Wait, what? a nil? ( ̄■ ̄;)!?
set            # => <Set: {1, 2}>

Pretty crazy, right??

Actually, it’s also pretty cool in terms of usability, despite being a bit inconsistent with the convention. Remember: the implementation must make sense primordially within the project’s domain (in this case, the Set domain). The add? method is meant to be used like this:

ingredients = Set.new([
  :avocado,
  :garlic,
  :onion,
  :lemon,
  :black_pepper,
  :coriander
]) #<Set: {:avocado, :garlic, :onion, :lemon, :black_pepper, :coriander}>

if ingredients.add?(:tomato)
  puts 'Oh, I forgot to add the tomatoes!'
end

ingredients # => #<Set: {:avocado, :garlic, :onion, :lemon, :black_pepper, :coriander, :tomato}>

The point is that Set is a data structure for unique keys! So, instead of having to ask if you can add an item and then add it, you can do these two operations in one fell swoop and avoid a conditional. And I think that’s neat! (̿▀̿ ̿Ĺ̯̿̿▀̿ ̿)̄.

Some bits of history

“Aliens…” — That guy with funny hair

I have already worked/studied a few other languages in the past, and I can say that this naming pattern is NOT that common. I went curious about where these came from and decided to dig a bit deeper to the roots of it.

As far as I can say, the inspiration for the ? suffix came from the concept of predicate functions. In mathematical logic, they are usually defined as functions bound to return a boolean value, also called Boolean-valued function (the function’s domain is a true/false or 1/0 set). The usual way I’ve seen languages dealing with this concept is by prepending an is_/Is to the beginning of method names, or even a _p / P (standing for “predicate”).

Now trying to find more direct influences, I went no far than Ruby’s about page:

[Ruby’s] creator, Yukihiro “Matz” Matsumoto, blended parts of his favorite languages (Perl, Smalltalk, Eiffel, Ada, and Lisp) to form a new language […]

Now I had somewhere to start looking! I have not much experience in most of those languages, but enough to tell that:

  • In Perl, predicate functions end with a capital “P”;

  • In Ada, “Predicates” are like constraints for types;

  • Eiffel and Smalltalk have no conventions for this.

But Lisp, on the other hand (more specifically Scheme) is exactly what we’re looking for. The syntax is pretty much the same as Ruby’s, so that’s probably where it came from. You even have the ! suffix to convey the idea of a mutation:

;; a is set to 1
(define a 1)

;; Mutating a, beware!
(set! a 2)

;; Prints 2
(print a)

Wrapping up

So, there you have it! You got some drops of this marvelous language that Ruby is!

Although these conventions may seem irrelevant, they are a very nice technique to increase your code’s readability and make it feel more natural and easier to review and maintain.

Like one other of my co-workers commented in the discussion we had here:

I wished to see these conventions being applied more often. It would be awesome and it helps a lot!

What about you? Do you have additional thoughts? Saw some weird or funny cases? Share it with us!!

Well, that’s all! “ヽ(´▽`)ノ”

See you in the next one!

Some stuff to give an extra look:

  • RDocs Page on methods;

  • The “Predicates” section of these docs (Scheme);

We want to work with you. Check out our "What We Do" section!