Ruby's if / else in methods - ruby

EDIT: I've received answers about refactoring to a ternary operation. While I'm aware that that's possible with all the languages below, that wasn't necessarily my intent with the original question about style. I've edited the snippets to reflect this.
I am curious if it's idiomatic or preferred to always use an else when using if/else in a method.
For example, in JS, this is stylistically acceptable (and sometimes preferred)
function jsFunc(x) {
if (x == 0) {
// do a bunch of stuff here
return answer;
}
// do something else
return differentAnswer;
}
and I've also seen a similar style in Python:
def py_func(x):
if x == 0:
# do a bunch of stuff here
return answer
# do something here
return differentAnswer
but from the Ruby I've seen, it's always:
def ruby_method(x)
if x == 0
# do a bunch of stuff here
answer
else
# do something else
differentAnswer
end
end
I've also gotten a comment that not using an else in Ruby seems messier. Do Rubyists prefer the explicit else? (Also curious a lot of the language is implicit.)

It actually depends.
Guard clause returns are actually used in Ruby quite often. They are even encouraged by the widely accepted style guide.
However, guard clauses are used in corner cases. So you have to ask yourself what are the possible values of x and how often is it actually 0?
If it's rarely 0 and it's somewhat special, the return at the start is perfectly acceptable (and encouraged).
For example, this is a perfectly serviceable (even though not optimal) implementation of factorial:
def factorial(n)
return 1 if n.zero?
n * factorial(n.pred)
end
Reason being that 0 is definitely a corner case.
If, on the other hand, the 'Yes' and 'No' cases are both likely and normal, this symmetry should be visually represented in the code (if - else).
For example, consider a robot deciding what to do on a traffic light:
def take_action(light)
if light.green?
go
elsif light.yellow?
prepare_to_go
elsif light.red?
wait
end
end
You can write it with guard clauses, but it sounds weird. All of the possible colours are equally a "main" colour.
Either way, in this specific case there is a more concise alternative:
x.zero? ? 'Yes' : 'No'

I think this is due to the fact that in Ruby we have implicit return and Ruby developers tend to dislike the use of return. In fact, the alternative to your snippet would be
def ruby_method(x)
return "Yes" if x.zero?
"No"
end
Kinda strange I think

Related

What's the purpose of using the suffix "if" statement in Ruby?

I know Ruby supports a suffix if like:
number = -42 if opposite
but what's the purpose of this? Why would it be used in place of the prefix if statement?
The suffix-style if and unless can also be good for "guard clauses", in the form of:
return if ...
return unless ...
Here's an example:
# suffix-style
def save
return false if invalid?
# go for it
true
end
Versus:
# indented style
def save
if valid?
# go for it
true
else
false
end
end
In the second example, the entire implementation of the method has to be shifted over by one indent due to the valid? check, and we need an extra else clause. With the suffix style, the invalid? check is considered an edge case that we handle and then bail out, and the rest of the method doesn't need an indent or an else clause.
This is sometimes called a "guard clause" and is recommended by the Ruby Style Guide.
It can make the code easier to read in some cases. I find this to be true especially in the case of unless, where you have some action you usually want to perform:
number = -42 unless some_unusual_circumstance_holds
Once you have it for unless, for symmetry it makes sense to support it for if as well.
number = -42 if opposite
is the same as
if opposite
number = -42
end
Some people prefer the one-liner for readability reasons. Imagine a line like:
process_payment if order_fulfilled?
Doesn't that read nice?
Postfix style does not have the else section. It is useful when you only want to do something with one of the two cases divided by the condition and don't want to mess with the other case.
It's the same as prefix but shorter. The only reason is to save vertical space in the text editor.

trying to find documentation on ruby's trailing if usage

I'm working in the RSpec book (page 121) and am being presented with a bit of code that is apparently self evident and clear. It's not self evident for me, and I'm hoping someone can help me understand.
I'm coming to ruby from c# so please use small words :)
Here's the original code
def total_match_count
count = 0
secret = #secret.split('')
#guess.split('').map do |n|
if secret.include?(n)
secret.delete_at(secret.index(n))
count += 1
end
end
count
end
here's the refactor
def total_match_count
secret = #secret.split('')
#guess.split('').inject(0) do |count, n|
count + (delete_first(secret, n) ? 1 : 0)
end
end
def delete_first(code, n)
code.delete_at(code.index(n)) if code.index(n)
end
Again, this is supposed to be so obvious as to need no comment.
I'm not understanding the trailing "if code.index(n)" bit and I can't find any documentation on using the keywords "ruby trailing if"
Obviously I'm missing something basic.
Ruby Post-Conditions as Syntactic Sugar
In Ruby, almost everything is an expression, and keywords like if and unless can be used as expression modifiers that follow an expression. Some languages refer to these as post-conditions, but the general idea is that:
if 1 == 1
puts true
end
is intended to be equivalent to:
puts true if 1 == 1
The post-condition can sometimes make the intent of the code clearer, or create a more natural flow. The parser differentiates between the :if and :if_mod tokens that internally represent the "normal" if-statement and its matching post-condition, but from a programmer's perspective the post-conditions are (or should be) largely syntactic sugar to make certain expressions easier or cleaner to read and write.
You don't ever need post-conditions in Ruby, but you will often find them in idiomatic Ruby code. If you don't grok them, or don't find that they improve the readability of your code, then feel free to ignore them until and unless they seem useful to you.
This:
code.delete_at(code.index(n)) if code.index(n)
is the same as this:
if code.index(n)
code.delete_at(code.index(n))
end
Some people think the one-liner is easier to read. It's a matter of style--when lines become long, the "trailing if" can be a gotcha, as you might not think to read to the end of the line to realize it has a condition attached. Use judiciously.
Ruby also has unless, which can be used in the "trailing" form too:
do_stuff unless no_on_second_thought

Why does Enumerable#detect need a Proc/lambda?

Enumerable#detect returns the first value of an array where the block evaluates to true. It has an optional argument that needs to respond to call and is invoked in this case, returning its value. So,
(1..10).detect(lambda{ "none" }){|i| i == 11} #=> "none"
Why do we need the lambda? Why don't we just pass the default value itself, since (in my tests) the lambda can't have any parameters anyway? Like this:
(1..10).detect("none"){|i| i == 11} #=> "none"
As with all things in Ruby, the "principle of least surprise" applies. Which is not to say "least surprise for you" of course. Matz is quite candid about what it actually means:
Everyone has an individual background. Someone may come from Python, someone else may come from Perl, and they may be surprised by different aspects of the language. Then they come up to me and say, 'I was surprised by this feature of the language, so Ruby violates the principle of least surprise.' Wait. Wait. The principle of least surprise is not for you only. The principle of least surprise means principle of least my surprise. And it means the principle of least surprise after you learn Ruby very well. For example, I was a C++ programmer before I started designing Ruby. I programmed in C++ exclusively for two or three years. And after two years of C++ programming, it still surprises me.
So, the rational here is really anyone's guess.
One possibility is that it allows for or is consistent with use-cases where you want to conditionally run something expensive:
arr.detect(lambda { do_something_expensive }) { |i| is_i_ok? i }
Or as hinted by #majioa, perhaps to pass a method:
arr.detect(method(:some_method)) { |i| is_i_ok? i }
Accepting a callable object allows allows "lazy" and generic solutions, for example in cases where you'd want to do something expensive, raise an exception, etc...
I can't see a reason why detect couldn't accept non callable arguments, though, especially now in Ruby 2.1 where it's easy to create cheap frozen litterals. I've opened a feature request to that effect.
It is probably so you can generate an appropiate result from the input. You can then do something like
arr = (1..10).to_a
arr.detect(lambda{ arr.length }){|i| i == 11} #=> 10
As you said, returning a constant value is very easy with a lambda anyway.
Really it is interestion question. I can understand why authors have added the feature with method call, you can just pass method variable, containing a Method object or similar, as an argument. I think it is simply voluntaristic solution for the :detect method, because it could be easy to add switch on type of passed argument to select weither it is the Method or not.
I've reverified the examples, and got:
(1..10).detect(proc {'wqw'}) { |i| i % 5 == 0 and i % 7 == 0 } #=> nil
# => "wqw"
(1..10).detect('wqw') { |i| i % 5 == 0 and i % 7 == 0 } #=> nil
# NoMethodError: undefined method `call' for "wqw":String
That is amazing. =)
The best use case for having a lambda is to raise a custom exception
arr = (1..10).to_a
arr.detect(lambda{ raise "not found" }){|i| i == 11} #=> RuntimeError: not found
So, as the K is trivial (just surround with ->{ }) there is not much sense in checking for fallback behavior.
The similar case of passing a &-ed symbol instead of a block is in fact not similar at all, as in that case it indicates something that will be called on the items of the enumerable.

Why are else statements discouraged in Ruby?

I was looking for a Ruby code quality tool the other day, and I came across the pelusa gem, which looks interesting. One of the things it checks for is the number of else statements used in a given Ruby file.
My question is, why are these bad? I understand that if/else statements often add a great deal of complexity (and I get that the goal is to reduce code complexity) but how can a method that checks two cases be written without an else?
To recap, I have two questions:
1) Is there a reason other than reducing code complexity that else statements could be avoided?
2) Here's a sample method from the app I'm working on that uses an else statement. How would you write this without one? The only option I could think of would be a ternary statement, but there's enough logic in here that I think a ternary statement would actually be more complex and harder to read.
def deliver_email_verification_instructions
if Rails.env.test? || Rails.env.development?
deliver_email_verification_instructions!
else
delay.deliver_email_verification_instructions!
end
end
If you wrote this with a ternary operator, it would be:
def deliver_email_verification_instructions
(Rails.env.test? || Rails.env.development?) ? deliver_email_verification_instructions! : delay.deliver_email_verification_instructions!
end
Is that right? If so, isn't that way harder to read? Doesn't an else statement help break this up? Is there another, better, else-less way to write this that I'm not thinking of?
I guess I'm looking for stylistic considerations here.
Let me begin by saying that there isn't really anything wrong with your code, and generally you should be aware that whatever a code quality tool tells you might be complete nonsense, because it lacks the context to evaluate what you are actually doing.
But back to the code. If there was a class that had exactly one method where the snippet
if Rails.env.test? || Rails.env.development?
# Do stuff
else
# Do other stuff
end
occurred, that would be completely fine (there are always different approaches to a given thing, but you need not worry about that, even if programmers will hate you for not arguing with them about it :D).
Now comes the tricky part. People are lazy as hell, and thusly code snippets like the one above are easy targets for copy/paste coding (this is why people will argue that one should avoid them in the first place, because if you expand a class later you are more likely to just copy and paste stuff than to actually refactor it).
Let's look at your code snippet as an example. I'm basically proposing the same thing as #Mik_Die, however his example is equally prone to be copy/pasted as yours. Therefore, would should be done (IMO) is this:
class Foo
def initialize
#target = (Rails.env.test? || Rails.env.development?) ? self : delay
end
def deliver_email_verification_instructions
#target.deliver_email_verification_instructions!
end
end
This might not be applicable to your app as is, but I hope you get the idea, which is: Don't repeat yourself. Ever. Every time you repeat yourself, not only are you making your code less maintainable, but as a result also more prone to errors in the future, because one or even 99/100 occurrences of whatever you've copied and pasted might be changed, but the one remaining occurrence is what causes the #disasterOfEpicProportions in the end :)
Another point that I've forgotten was brought up by #RayToal (thanks :), which is that if/else constructs are often used in combination with boolean input parameters, resulting in constructs such as this one (actual code from a project I have to maintain):
class String
def uc(only_first=false)
if only_first
capitalize
else
upcase
end
end
end
Let us ignore the obvious method naming and monkey patching issues here, and focus on the if/else construct, which is used to give the uc method two different behaviors depending on the parameter only_first. Such code is a violation of the Single Responsibility Principle, because your method is doing more than one thing, which is why you should've written two methods in the first place.
def deliver_email_verification_instructions
subj = (Rails.env.test? || Rails.env.development?) ? self : delay
subj.deliver_email_verification_instructions!
end

style opinion re. empty If block

I'm trying to curb some of the bad habits of a self-proclaimed "senior programmer." He insists on writing If blocks like this:
if (expression) {}
else {
statements
}
Or as he usually writes it in classic ASP VBScript:
If expression Then
Else
statements
End If
The expression could be something as easily negated as:
if (x == 0) {}
else {
statements
}
Other than clarity of coding style, what other reasons can I provide for my opinion that the following is preferred?
if (x != 0) {
statements
}
Or even the more general case (again in VBScript):
If Not expression Then
statements
End If
Reasons that come to my mind for supporting your opinion (which I agree with BTW) are:
Easier to read (which implies easier to understand)
Easier to maintain (because of point #1)
Consistent with 'established' coding styles in most major programming languages
I have NEVER come across the coding-style/form that your co-worker insists on using.
I've tried it both ways. McConnell in Code Complete says one should always include both the then and the else to demonstrate that one has thought about both conditions, even if the operation is nothing (NOP). It looks like your friend is doing this.
I've found this practice to add no value in the field because unit testing handles this or it is unnecessary. YMMV, of course.
If you really want to burn his bacon, calculate how much time he's spending writing the empty statements, multiply by 1.5 (for testing) and then multiply that number by his hourly rate. Send him a bill for the amount.
As an aside, I'd move the close curly bracket to the else line:
if (expression) {
} else {
statements
}
The reason being that it is tempting to (or easy to accidentally) add some statement outside the block.
For this reason, I abhor single-line (bare) statements, of the form
if (expression)
statement
Because it can get fugly (and buggy) really fast
if (expression)
statement1
statement2
statement2 will always run, even though it might look like it should be subject to expression. Getting in the habit of always using brackets will kill this stumbling point dead.

Resources