1
\$\begingroup\$

Task: Implement a function which loops from 1 to 100 and prints "Fizz" if the counter is divisible by 3. Prints "Buzz" if the counter is divisible by 5 and "FizzBuzz", when the counter is divisible by 3 and 5.

The instructor gave me the hint, that it's solvable using the ternary operator. Otherwise I would have used an if-then-else approach.

My solution:

def fizz_buzz
  for i in 1...100
    puts i % 3 == 0 ? i % 5 == 0 ? "FizzBuzz" : "Fizz" : i % 5 == 0 ? "Buzz" : i
  end
end

What's your opinion about the approach? Would you still consider it as readable?

I personally consider the readability hard on the border.

Which approach would you prefer and why?

\$\endgroup\$
1
  • \$\begingroup\$ As I envision a dual-if: How would you do if-else? \$\endgroup\$
    – greybeard
    Commented Jun 26, 2023 at 6:29

4 Answers 4

4
\$\begingroup\$

Ternary statement is cleaner and often faster to comprehend than if/else; especially true for languages with ceremony syntax, such as curly braces. And I like nested ternary statements (or is it singular?) but it's important to format to help the reader with logic flow. Assume the reader knows the operator structure and resist over-indentation resulting eye boggling pseudo if/else.

def fizz_buzz
  for i in 1...100
    puts i % 3 == 0 ?  i % 5 == 0 ? "FizzBuzz" : "Fizz"   : 
         i % 5 == 0 ? "Buzz" : i
  end
end
\$\endgroup\$
3
  • \$\begingroup\$ (I'd use parentheses rather than multiple spaces to indicate association.) \$\endgroup\$
    – greybeard
    Commented Jun 26, 2023 at 6:44
  • \$\begingroup\$ hmmm... I hesitate. I've not used parenthesis except as necessary for operator precedence. Here it serves to signal AND and/or OR and that also feels like "wink, wink, I'm an if/ else." I like your suggestion of inline if's better. \$\endgroup\$
    – radarbob
    Commented Jun 26, 2023 at 7:10
  • \$\begingroup\$ Space update: I think the spacing (see above) does not help. It is too subtle unless the spacing is exaggerated. In any case it doesn't feel like it's adding to understanding. \$\endgroup\$
    – radarbob
    Commented Jun 26, 2023 at 20:20
2
\$\begingroup\$

No nested if's (and no ternary operators):

def fizzbuzz
  (1..100).each do |n|
    res = ""
    res << "Fizz" if n % 5 == 0
    res << "Buzz" if n % 3 == 0
    res << n.to_s if res.empty?
    puts res
  end
end

With one extra line of code this transforms to a FizzBuzzBazz.

\$\endgroup\$
2
  • \$\begingroup\$ The observation here seems to be "Don't use ternary conditional", but it's not clear why you say that. And I don't get the bit about nested if, since the code under review has no ifs at all. \$\endgroup\$ Commented Sep 18, 2023 at 9:31
  • \$\begingroup\$ This example shows that ifs without nested branching can increase clarity even compared to ternary statements. Further, the Ruby-esque "trailing IF" not only complements clarity but emphasizes the result-value over the condition that emits that value. One could say it coveys a precedence or preference of returnable values. Again it is about what you want to communicate and emphasize about the code. \$\endgroup\$
    – radarbob
    Commented Jan 21 at 23:17
2
\$\begingroup\$

In all the cases except the one where performance is a top priority, I'd prefer the most readable approach:

def fizz_buzz
  100.times do |i|
    if i % 3 == 0 && i % 5 == 0
      puts "FizzBuzz"
    elsif i % 3 == 0
      puts "Fizz"
    elsif i % 5 == 0
      puts "Buzz"
    else
      puts i
    end
  end
end

This code immediately tells you that there are 4 cases, you may easily see the conditions for each of them, all the outcomes are clear and the code is still very simple. Other programmers (or future you) will spend almost no time to read and understand it.

Some people will say that this code is not optimal because it does more checks than the required minimum, but that's usually much less important than readability. It'll certainly take several additional nanoseconds, but in most cases it is negligible. For example, in web development (where Ruby is mostly used), typical response times are in tens or hundreds of milliseconds. Developer time, however, costs a lot. Caring about code readability pays

Also, notice that i % 3 == 0 && i % 5 == 0 is the same as i % 15 == 0, but I'd suggest not to make this replacement either, because i % 3 == 0 && i % 5 == 0 is more intent-revealing

I suggest reading "99 bottles of OOP" by Sandi Metz for a deeper perspective on that.

\$\endgroup\$
2
  • 1
    \$\begingroup\$ "... but I'd suggest not to make this replacement either, because i % 3 == 0 && i % 5 == 0 is more intent-revealing" One could also add a comment to explain if one wanted to opt for fewer operations. \$\endgroup\$ Commented Dec 11, 2023 at 21:53
  • 1
    \$\begingroup\$ Right, something like i % 15 == 0 # both 3 & 5 will be even more readable. \$\endgroup\$
    – Vizvamitra
    Commented Dec 11, 2023 at 22:07
1
\$\begingroup\$

A post just to suggest a different formatting of radarbob's suggestion

def fizz_buzz
  for i in 1...100
    puts i % 3 == 0 ? i % 5 == 0 ? "FizzBuzz" : "Fizz"
                    : i % 5 == 0 ? "Buzz" : i
  end
end
\$\endgroup\$
3
  • \$\begingroup\$ I never coded ruby in earnest. \$\endgroup\$
    – greybeard
    Commented Jun 27, 2023 at 18:56
  • \$\begingroup\$ The more one learns of Ruby the more one enjoys it and indeed love it in comparison to others. "Everything is an object" is freeing; syntax is refreshingly clean. \$\endgroup\$
    – radarbob
    Commented Jun 27, 2023 at 23:30
  • \$\begingroup\$ I'd suggest your format highlights " i % 3"s TRUE : FALSE clauses and my answer emphasizes "I % 3" OR "i % 5". Both good. It's about what you want to say to the reader. \$\endgroup\$
    – radarbob
    Commented Jun 27, 2023 at 23:37

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Not the answer you're looking for? Browse other questions tagged or ask your own question.