Concatenate State and Case

Nested logic is difficult to get right the first time and really difficult to maintain. Here’s a simple technique that flattens nested logic into a single level case statement that is easier to get right and far easier to maintain. The technique has 2 parts: concatenate state, and case.

The goal is to transform a nested “if” into a flattened case statement.

    if a == 'x' then
      if b == 'y' then
        print 'a=x, b=y'
      else
        print 'a=x, b!=y'
      end
    else
      if b == 'y' then
        print 'a!=x, b=y'
      else
        print 'a!=x, b!=y'
      end
    end

becomes

    case
      when a == 'x' && b == 'y'
        print 'a=x, b=y'
      when a == 'x' && b != 'y'
        print 'a=x, b!=y'
      when a != 'x' && b == 'y'
        print 'a!=x, b=y'
      when a != 'x' && b != 'y'
        print 'a!=x, b!=y'
    end

Why?

This is easier to understand. It is very easy to enumerate all permutations of the possible state components.

When you do this the initial implementation is easier. But the really big win is when you return to the code weeks, months, or years later. By then you’ve forgotten all of the interconnected logic that led you to the original implementation. You are forced to stare at the implementation and attempt to rebuild your mental model. With concatenate state, and case all of this is laid out in simple order.

How do you do it? First you bundle all of the state into a single variable. Then you drop into a case statement with all posible permutations of those states to find the code to execute.

Some languages let you have multiple value case expressions, with syntax something like:

case
when a>b && c<d
when a>b && c=>d
when a<=b && c<d
when a<=b && c=>d
end

If your language supports this and your state can be expressed cleanly then you’re done — you’ve achieved the simple enumeration of all possible states.

Often the state cannot be expressed so succinctly, so the expressions alone bloat and obfuscate the logic.

To get around this, simply reduce the expressions to easy to read and understand strings, then concatenate them together for a single composite state. For example:

ab_state = a>b ? 'a>b' : 'a<=b'
cd_state = c<d ? 'c<d' : 'c>=d'

case ab_state + ' and ' + cd_state
when 'a>b and c<d'
when 'a>b and c>=d'
when 'a<=b and c<d'
when 'a<=b and c>=d'

In this contrived example the strings are just as verbose as their non-string expressions, so there is no win. But often the expressions are not so succinct but the strings can be as succinct as you make them, while hopefully communicating the status clearly.

Here’s a real life example pulled from some code I wrote a few years ago. The problem is to validate passwords in a user object on save. There are 3 attributes that are of interest: a password hash, a new password, and a new password confirmation.

The logic goes like this: We need either an existing password hash or a new password and matching password confirmation. If we have a new password or a new password confirmation, then we are attempting to change or set the password, and both items must be present and they must match. If neither are present, but we have an existing password hash, then we are valid, meaning we are not trying to change our password. If neither the new password or new password confirmation are present, and the password hash is blank, then we are invalid since all user records must have a password.

A first implementation might look something like this:

if password_hash.blank?
  if new_password.blank?
    errors.add('new_password', 'is required')
  else
    if new_password_again.blank?
      errors.add('new_password_again', 'new password must be entered twice')
    else
      if new_password != new_password_again
        errors.add('new_password_again', 'does not match your new password')
      end
    end
  end
else
  if new_password.blank?
    if new_password_again.present?
      errors.add('new_password', 'new password must be entered twice')
      #else
      # ok
    end
  else
    if new_password_again.blank?
      errors.add('new_password_again', 'new password must be entered twice')
    else
      if new_password != new_password_again
        errors.add('new_password_again', 'does not match your new password')
      end
    end
  end
end

That’s 27 lines of code (and 2 comment lines). And it is treacherous code. I think it works because I have a test suite and it passed. Without that test suite my confidence in this code would be near zero.

Let’s try cleaning it up a bit.

if password_hash.blank? && new_password.blank? && new_password_again.blank?
  errors.add('new_password', 'is required')
end
if new_password_again.present? && new_password.blank?
  errors.add('new_password', 'new password must be entered twice')
end
if new_password.present? && new_password_again.blank?
  errors.add('new_password_again', 'new password must be entered twice')
end
if new_password.present? && new_password_again.present?
  if new_password != new_password_again
    errors.add('new_password_again', 'does not match your new password')
  end
end

This is a little easier to read but no easier to ensure that it is correct.

Here is the code with concatenated state and case:

newpw_key = new_password.blank? ? 'nonew' : 'new'
newpw_again_key = new_password_again.blank? ? 'noagain' : 'again'
newpw_equal_key = new_password == new_password_again ? 'eql' : 'noteql'
pw_key = password_hash.blank? ? 'nopw' : 'pw'

case '#{newpw_key}-#{newpw_again_key}-#{newpw_equal_key}-#{pw_key}'
  when 'nonew-noagain-eql-pw'
  when 'nonew-noagain-eql-nopw'
    errors.add('new_password', 'is required')
  # when 'nonew-noagain-noteql-pw'
  #   not possible
  # when 'nonew-noagain-noteql-nopw'
  #   not possible
  # when 'nonew-again-eql-pw'
  #   not possible
  # when 'nonew-again-eql-nopw'
  #   not possible
  when 'nonew-again-noteql-pw'
    errors.add('new_password', 'new password must be entered twice')
  when 'nonew-again-noteql-nopw'
    errors.add('new_password', 'new password must be entered twice')
  # when 'new-noagain-eql-pw'
  #   not possible
  # when 'new-noagain-eql-nopw'
  #   not possible
  when 'new-noagain-noteql-pw'
    errors.add('new_password_again', 'new password must be entered twice')
  when 'new-noagain-noteql-nopw'
    errors.add('new_password_again', 'new password must be entered twice')
  when 'new-again-eql-pw'
  when 'new-again-eql-nopw'
  when 'new-again-noteql-pw'
    errors.add('new_password_again', 'does not match your new password')
  when 'new-again-noteql-nopw'
    errors.add('new_password_again', 'does not match your new password')
  else
    raise 'unexpected case #{newpw_key}-#{newpw_equal_key}-#{pw_key}'
end

This weighs in at 26 lines and 12 comments — still a pretty heavy method. However, this one carefully enumerates all the possible permutations of the state values — 4 state values, each with 2 possible values, for 16 possible states.

Of the 16 possible states, some are logically impossible. For example, you can’t say that the new password was specified and the new password confirmation was not specified but that the new password and the new password confirmation are the same — it can’t happen, so you can safely ignore that case.

The remaining states are either valid or invalid. When they are invalid some action is taken.

This code is as clear to me today as it was when I wrote it over 3 years ago.

I hope this helps you write cleaner and easier to maintain code.

-Kelly