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?

jokklan
  • 3,520
  • 17
  • 37
rlhh
  • 893
  • 3
  • 17
  • 32
  • 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. – tokland Jun 19 '13 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. – Boris Stitnicky Jun 19 '13 at 15:46

4 Answers4

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
Don Cruickshank
  • 5,641
  • 6
  • 48
  • 48
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
tokland
  • 66,169
  • 13
  • 144
  • 170
  • I never like seeing assignments inside the conditional test. It's too much like C or Perl and gives me nightmares. Thanks a lot. :-) – the Tin Man Jun 19 '13 at 16:14
  • @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. – tokland Jun 19 '13 at 16:29
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
trushkevich
  • 2,657
  • 1
  • 28
  • 37
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
steenslag
  • 79,051
  • 16
  • 138
  • 171