To write readable and maintainable code, follow a few simple rules about argument ordering.

Tip 1: Optional arguments go last

When a method takes several arguments, and some of them have default values, make sure to put the optional ones at the end. Take this method definition:

def foo(a, b = "val_b", c)
  # some code...
end

Looks fine at first glance. But in practice, it’s impossible to call this method with values for a and c without also specifying b. You end up writing:

foo("val_a", "val_b", "val_c")

…which defeats the purpose of setting a default for b if you’re forced to specify it every time. ¯(ツ)

Solution: move optional arguments to the end

def foo(a, c, b = "val_b")
  # some code...
end

Now you can call:

foo("val_a", "val_c")

No problem 😎.

Tip 2: Use keyword arguments (aka kwargs)

Tip 1 only gets you so far. What if you have multiple optional arguments and want to override just the last one? You’d still need to specify all the previous ones. For example:

def foo(a, b, c = "val_c", d = "val_d", e = "val_e")
  # some code...
end

Want to change only e? Too bad — you still have to write:

foo("val_a", "val_b", "val_c", "val_d", "my_val_e")

Now imagine this in a large codebase:

foo(user.id, "DONE", true, false, 1, "always", true, post.id)

And you’re left thinking: “Wait, what’s that false again? What does the 1 do? Why ‘always’?” Not ideal, especially under pressure in a production bug fix.

Solution: switch to keyword arguments when you hit 2+ optional params

Benefits:

  • Argument order doesn’t matter anymore
  • You can ignore defaults entirely

The method now becomes:

def foo(a, b, c: "val_c", d: "val_d", e: "val_e")
  # some code...
end

To change only e, you write:

foo("val_a", "val_b", e: "my_val_e")

Ninja-level tip — the clean coder’s take:

If your method takes more than 1 argument, make everything after the first a keyword argument. Always.

Example:

def send_message_to(recipient, confirm: true, bcc: "joe@bill.com", cc: "jane@doe.com")
  # ruby code
end

Here, the first argument is obvious thanks to the method name: you’re sending a message to someone. Called without options:

send_message_to("sam@sam.com")

Clear and concise. Compare a non-keyword version:

send_message("recipient@mail.com", false, "joe@bill.com", "paul@smith.com")

…with the keyword version:

send_message_to("sam@sam.com", bcc: "paul@smith.com", confirm: false)

If you adopt these habits, future you will thank you when you revisit the code to debug or tack on yet another parameter.