5

I was writing some code and it ended up being way too ugly to my liking. Is there anyway that I can refactor it so that I don't use nested if statements?

def hours_occupied(date)
  #assuming date is a valid date object    
  availability = get_work_hours(date)
  focus = "work"

  if availability.nil
    availability = get_family_hours(date)
    focus = "family"

    if availability.nil
      availability = get_friend_hours(date)
      focus = "friends"
    end
  end
end

I know I'll be able to do something like this for availability

availability = get_work_hours(date) || get_family_hours(date) || get_friend_hours(date)

but how do I set the focus variable accordingly?

2
  • 3
    Can we assume you are not showing the complete method? otherwise it makes no sense to assign those unused variables. If that's the case, add "..." as placeholder at the bottom of the method. Commented Jun 19, 2013 at 15:22
  • 1
    @tokland: I find myself completely unable to understande the subject and the purpose of his code, which renders me unable to respond. Commented Jun 19, 2013 at 15:46

4 Answers 4

7

I would do something like the following as it makes it clear that each case is mutually exclusive:

def hours_occupied(date)
  if availability = get_work_hours(date)
    focus = "work"
  elsif availability = get_family_hours(date)
    focus = "family"
  elsif availability = get_friend_hours(date)
    focus = "friends"
  end
end
Sign up to request clarification or add additional context in comments.

1 Comment

Ruby conditionals are expression, so no need to repeat focus 3 times
3

I'd write:

def hours_occupied(date)
  focus = if (availability = get_work_hours(date))
    "work"
  elsif (availability = get_family_hours(date))
    "family"
  elsif (availability = get_friend_hours(date))
    "friends"
  end
  # I guess there is more code here that uses availability and focus.
end

However, I am not sure having different methods for different types is a good idea, it makes code harder to write. A different approach using Enumerable#map_detect:

focus, availability = [:work, :family, :friends].map_detect do |type|
  availability = get_hours(date, type)
  availability ? [type, availability] : nil
end

2 Comments

I never like seeing assignments inside the conditional test. It's too much like C or Perl and gives me nightmares. Thanks a lot. :-)
@theTinMan: You're welcome :-) Some people would say that if the language allows it (for example Python does not), why avoid it? can't you tell a == from a =? But I won't :-) I like this construction because it simplifies the code with less nested expressions. Note that the parens make it more conspicous.
2

One more way is just to reassign values if there is a need:

def hours_occupied(date)
  availability, focus = get_work_hours(date), "work"
  availability, focus = get_family_hours(date), "family" unless availability
  availability, focus = get_friend_hours(date), "friend" unless availability
end

or using an iterator:

def hours_occupied(date)
  availability = focus = nil
  %w(work family friend).each {|type| availability, focus = self.send(:"get_#{type}_hours", date), type unless availability}
end

Comments

1

A case when is also an option:

focus = case availability
when get_work_hours(date)
  "work"
when get_family_hours(date)
  "family"
when get_friend_hours(date)
  "friends"
end

6 Comments

either this is wrong or case works differently than I thought :-)
but availability must hold the first non-nil result of the method calls, it's not known beforehand.
Just use case, not case availability
Guys, I am a bit puzzled by your comments, check the question, availability is not known! or am I missing something?
@tokland that's why you have to use case without a parameter: focus = case when get_work_hours...
|

Your Answer

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

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.