Avoiding the Ruby Run-On Sentence

The run-on sentence can be defined as two or more independent clauses that have been joined without an accompanying coordinating conjunction. Run-on sentences carry multiple thoughts, which confuses the reader.

So what does this have to do with Ruby? Well, it is this author’s opinion that run-on sentences can be a sign of danger when describing your program objectives. Each objective within your program should be conveyed in as simple terms as possible, without having to glue together lots of disparate thoughts.

Fortunately, this is fairly easy in Ruby, as it really excels in describing complex concepts in a very terse manner, allowing you to express yourself clearly.

For example, take a look at the following code:

    shift_type = 'unassigned'
    (0...NUMBER_OF_SLOTS).each do |i|
      if @shifts[i] != 'unassigned' && @shifts[i] != 'unavailable'
        if shift_type != 'unassigned' && shift_type != 'unavailable'
          if shift_type != @shifts[i]
            cost_array.push cost
          end
        end
      end
      shift_type = @shifts[i]
    end

There’s a lot going on here, so let’s break it down into bullets:

  1. The @shifts array holds an individual worker’s shift for each day within a calendar. The length of the calendar is described the the constant, NUMBER_OF_SLOTS.
  2. Each shift within the @shifts array describes the nature of the shift. It can be one of several values, depending on the hours that the worker is scheduled to work. For example, an entry might say, “7-330”, which implies the worker is expected to show up for the 7:00AM to 3:30PM shift.
  3. Each shift may also be designated as “unavailable” or “unassigned.” The former implies that the worker is on vacation, on leave, or, in general, unavailable to work any shift on that day. The “unassigned” designation implies that the worker is available to work, but has not yet been assigned to work on this particular day.
  4. Each worker generally works a number of contiguous days before having one or more “unassigned” or “unavailable” days. It is highly preferred that each worker have the same shift during contiguous days. For example, a worker shows up for the 3:00PM – 11:30PM shift for five straight days before having one or more “unassigned” days. Thereafter, the worker can be assigned another set of contiguous days, but with the preference that he/she works the same shift on those contiguous days.
  5. If a worker is NOT scheduled to work a consistent shift on contiguous days, then a “cost” factor is added to the overall cost of the schedule.

The above bullets could be glued together into one huge run-on sentence, and if one were to read such a huge sentence, he/she may well get lost. Languages like C++, and even, sad to say, JavaScript,  force you into these structures because they do not effectively mirror higher level concepts of human thought. Fortunately, the human brain is a very pliable device, so programmers get used to this concept just as easily as a person can get used to run-on sentences. But the problem with these structures is that it takes a really long time to learn and understand the underlying concepts, and because of the complexity, we often experience a lapse in our true understanding. It therefore becomes a support nightmare.

So what can we do with the above bullets to simplify them? How about a simple, terse sentence like this:

“Make sure each worker is assigned the same shift on contiguous working days.”

So it would seem that the simpler statement is easier to understand, and therefore, the simpler we can make the statement in our programming language, the more clearly it can be understood and supported.

Fortunately, Ruby has lots of tricks up its sleeve to help in this process.

First, let’s shorten the compound comparison statements using a Ruby Array structure.

FROM: if @shifts[i] != 'unassigned' && @shifts[i] != 'unavailable'
TO: unless %w(unassigned unavailable).include?(@shifts[i])

While this may seem a trivial replacement, it can come in handy when making numerous comparisons. Your eyes don’t have to scan all the way down to the end of the line (and beyond) to see what’s being done here. You need only look as far as the temporary array structure to quickly understand the comparison.

Still, we have three levels of “if” statements, so we begin simplifying by converting the innermost to a single statement.

FROM:
          if shift_type != @shifts[i]
            cost_array.push cost
          end
TO:
          cost_array.push cost if shift_type != @shifts[i]

Keep in mind we’re not simply combining three lines into one just for the sake of looking good; on the contrary, we can now read and understand in one line a simple concept that previously required three lines.

But let’s not stop there! We can move up to the previous “if” statement and perform a similar simplification:

FROM:
        unless %w(unassigned unavailable).include?(shift_type)
          cost_array.push cost if shift_type != @shifts[i]
        end
TO:
        cost_array.push cost if shift_type != @shifts[i] unless %w(unassigned unavailable).include?(shift_type)

We can go even one level higher and include the containing “if” clause in a single statement like this:

FROM:
      unless %w(unassigned unavailable).include?(@shifts[i])
        cost_array.push cost if shift_type != @shifts[i] unless %w(unassigned unavailable).include?(shift_type)
      end
TO:
      cost_array.push cost if shift_type != @shifts[i] unless %w(unassigned unavailable).include?(shift_type) unless %w(unassigned unavailable).include?(@shifts[i])

Okay, admittedly, this line is looking rather long, but it is this author’s belief that this one single long line is easier to understand than working through three nested “if” statements.

The final program section now looks like this:

    shift_type = 'unassigned'
    (0...NUMBER_OF_SLOTS).each do |i|
      cost_array.push cost if shift_type != @shifts[i] unless %w(unassigned unavailable).include?(shift_type) unless %w(unassigned unavailable).include?(@shifts[i])
      shift_type = @shifts[i]
    end

Now that the loop is essentially only two lines, it’s easy to see what’s happening. For example, it’s easy to see that no matter what the comparisons yield, the second line (shift_type = @shifts[i]) will always get executed. In this particular case, it may be trivial for any programmer to identify this fact from the original listing; however, imagine a listing that is so long that this second line cannot be seen without scrolling down the page. The fact that this second line always gets executed may get lost in all the details of the huge run-on sentence, and therefore the programmer will have a less-than-optimal understanding of the overall objective and functionality of the code.

Your high school English teacher may have dinged you for your run-on sentences. Turns out that run-on sentences are equally as bad for your code!

Have fun!

Dan