In design (of software and non-software products), is it good to provide several alternatives? Let's see this scenario:
You're learning Ruby. You learn that to write an array, you use ["a", "b", "c"]
, like most other languages. Cool. One day, while reading some library code, you come across some weird code like this: %w(a b c)
. Confused, you google it, only to find out it's still an array. Same thing as ["a", "b", "c"]
. A bit puzzling why it exists, but okay.
A few days later, you come across the %w
construct again, but this time with a string: %w"a b c"
. Time to play a guessing game. Is this meant to be ["a b c"]
, ["a", "b", "c"]
, or something else? Turns out, it does the same thing as before. In this case, %w
will split the string for you.
Fast forward a few days, and you encounter %w
once more, but now it's with {}
rather than ()
: %w{a b c}
. You guess that it's probably the same thing, double-check, and yes it is.
Do you see a problem here? Having too many different ways to do one thing can lead to:
- Unnecessary time spent looking up and learning irrelevant shit. I already know how to write an array. Rather than focusing on learning the logic of the code I'm reading, I have to interrupt myself multiple times to look up some strange syntax which often turns out to be a different way of doing something I already know(!) This isn't fictional, by the way. It's happened to me multiple times, and it's one of the things that frustrates me about Ruby.
- More bikeshedding. More options means more variation, so people will look for some consistency. People will spend time asking "which should I use?" and others will spend time arguing about the answer. For example: Ruby gives you two ways to
throw
an exception (raise
vsfail
). This Stack Overflow thread is basically people debating which to use, even though both constructs mean exactly the same thing. (You can debate that the semantics are different, but that's made-up.)
Fun fact: while writing this article, I found out about some more ways to write arrays:
%W[a b c] #=> ["a", "b", "c"]
%i[a b c] #=> [:a, :b, :c]
%I[a b c] #=> [:a, :b, :c]
Oh, and how about strings? You thought that was only single/double quotes and <<<HEREDOC
syntax?
%q[This is a string, not an array]
%Q{This too}
%(Yep, and this!)
?a # => a string with a single-character
Even better, the delimiter doesn't have to be []
, {}
or ()
. You can use any symbol. So:
%w"This is an array" # => ["This", "is", "an", "array"]
%q@This is a string@ #=> "This is a string"
It doesn't help that they're all cryptic, and there's no way to tell what the operators do unless you consult the docs.
But they have different uses!
Now, you might argue that each of these array methods has slightly different behaviours and that justifies them. Hmmm. Let's take our examples.
-
%w{a b c}
is the same as["a", "b", "c"]
, just without quotes. -
%W
supports interpolation, so you can do%W{John #{name}}
and get["John", "Peter"]
. I don't see how this is needed, when you can just as easily write["John", name]
, or"John #{name}".split
. -
%i
and%I
will give you arrays of symbols (%I
supports interpolation, like above), but again, you can just write[:a, :b, :c]
yourself. It isn't that hard or noisy.
The string approaches are a little more useful, with the major benefit being that you won't have to escape quotes inside the string: %q{He said, "Oh shit."}
versus "He said, \"Oh shit.\"
.
I'll agree that some of these are occasional nice-to-haves. Some of them are even pretty cool. However, I don't think they're necessarily worth it. Ruby's philosophy, one I wholly endorse, is "developer productivity". However, providing too many options can run counter to that.
In fact, the differences can add to the confusion. For example, "hello #{name}".split
and %w"hello #{name}"
will not give you the same results, even though they both appear to use quoted strings:
name = "Peter"
"hello #{name}".split #=> ["hello", "Peter"]
%w"hello #{name}" #=> ["hello", "\#{name}"]
These are just a few scenarios, but there are many more examples of this in Ruby. Of course, Ruby isn't the only language with unnecessary alternatives, but it's one where this is often celebrated as a feature — something I, quite frankly, do not get. I've also seen this happen with libraries that provide too many aliases for a method, leading to a bit of confusion.
Sometimes, less is more. Sometimes you can create a better overall experience for your users by limiting the available options and enforcing some consistency (something Apple has figured out🙄). If you're going to provide alternatives, keep them to a reasonable limit and make their use cases and benefits obvious.
Top comments (2)
Using a good linter with strict rules agreed upon by your team can help a lot with this. I specifically referring to Rubocop for Ruby ofc. If you have access to RubyMine, its really good at just picking up these rules. That being said, you're right. Ruby (my language of choice) is pretty riddled with examples of this. Alias method are another example. Inject vs Reduce is the first example that pops into my head.
Yes! inject vs reduce caught me out too. The first time I saw inject, I thought it was for an entirely different purpose (especially with the way it was explained). Took me a long time to realise it was the same. Alias methods can really be the devil.