Writing

Software, technology, sysadmin war stories, and more. Feed
Monday, March 5, 2012

Cats can't beep

I recently had to describe a situation involving object-oriented code and problems with a particular service to someone who doesn't do this sort of stuff for a living. Naturally, I wound up having to explain it a bit differently than usual, and there were some analogies applied. I'll re-tell it here as a way of getting to the underlying problem.

I started by saying there was this concept called objects, and they are the stars of the so-called object-oriented programming thing, and this was a story about them. Let's say you wanted to control some robots, so you wrote a robot controller. It has five things it can do: forward, reverse, turn left, turn right, and beep. You'd probably roll all of that code up into one collection and it would then be called a "class".

You could then create an instance of that class to control a given robot. You could even create a second instance of that class to control a second robot at the same time. It might look like this:

r1 = new Robot("a")
r2 = new Robot("b")

That makes r1 and r2 your instances of the Robot class. If you then did "r1.forward()", that first robot would move. Likewise, "r2.forward()" would move the other one. The basic idea is that you can recycle the movement code since they are the same kind of robot, kind of like how you can use a cat toy to amuse more than one cat. They tend to be compatible in that regard.

So then there is the matter of actually doing this in a real language. Some of them don't really care what you do as long as it makes sense. To demonstrate this, I created a second class called Cat. It's not nearly as responsive to commands. It can ignore, sleep, eat, or move forward (if you're lucky).

This particular language lets you make a Cat just like you'd make a Robot.

c1 = new Cat("x")
c2 = new Cat("y")

You can even do c1.forward() and c2.forward() here, in addition to all of the other things a Cat allows. So far, so good, right?

Now let's say someone adds a little abstraction. They create a subroutine somewhere which takes a generic object and tells it to move forward. They call this subroutine (function, method, ...) "advance", since all it does is move things forward. It looks like this:

Advance(minion):
  minion.forward()

Now you can do "Advance(r1)" or "Advance(c1)" and it'll send your Robot *or* your Cat forward. It doesn't care that they are different things because they both happen to have "forward" behavior.

Time passes, and a bunch of other classes are created like this. They all have their respective traits and they all have forward(), so when they happen to be handed to Advance, it still just works. The program is mostly controlling Robots, but about 1/10000 of the time, it does something else based on what the users request.

Then, one day, a new regulation is handed down. It turns out that all things which are being used as minions are required to make noise every time they advance. It seems that some poor pedestrians were run down by the ultra-quiet hybrid cars which lack the roaring engines of regular cars.

So, some maintenance programmer drops into the code and amends it a little bit:

Advance(minion):
  minion.beep()
  minion.forward()

They start it back up, and the first thing it's given is a Robot. The robot beeps, then moves forward. It's successful! Another Robot is picked, and it beeps and moves forward as well. This goes on for a while and it looks good, so the programmer packs it up and pushes it out. The new code makes it out in advance of the deadline, and they have a party to celebrate "shipping".

A couple of weeks pass. Then, on "bring your child to work day", one of the kids gets a chance to try the machine. The kid doesn't care for robots, but really likes cats. She picks "Cat" from the menu, and one enters the queue.

Eventually, her Cat object is given to Advance, and then it blows up.

Why did it blow up? That's simple. Cats can't beep!

Since the Cat class didn't have any way to beep, the program tried and failed to call it. Then, because this is how this particular programming language works, it decided to puke a bunch of nerd speak all over the screen and die.

This is just how this programming language responds to an unfulfillable request while a program is running. You didn't give it any hint as to what to do next, so it fell back to the default behavior: give up.

So okay, what now? Well, they restart the program, and it goes back to the queue, where the still-unprocessed Cat is at the front. It grabs that object, tries to Advance it again, and **boom**. It's dead again. This happens again and again until finally someone digs around in the database and manually deletes the request. Then they restart it and it goes back to pushing Robots around.

The Cat request never happens. Their user is disappointed.

Later, someone tries to clean up this mess so it can't happen again. They decide to drop into the Cat class and write a "beep" method which just tells the cat to meow. They figure that's good enough. They queue up a Cat request, and watch as it meows and moves forward. Satisfied, they push that out to production and go play foosball.

A few weeks after that, someone requests a Dog and the same thing happens. Dogs can't beep, either.

This kind of whack-a-mole insanity keeps happening. The VP of something or other makes an administrative decree that all changes must now be accompanied by an exhaustive search through all classes to make sure that new methods are added when something tries to call them. Like every other administrative decree that tries to solve a technical problem with raw human effort, it fails. Things slip through the cracks despite the best attempts of the programmers.

After suffering through this for far too long, someone decides to learn about these things called exceptions. They add some more magic to Advance. Again, more pseudocode:

Advance(minion):
  begin block this_might_fail:
    minion.beep()
  end block
 
  if it failed:
    display "tried and failed to make minion beep"
    return to the caller without doing anything else
 
  minion.forward()

Now, instead of generating that screen full of garbage and dying, the program just jumps to the "if it failed" thing, prints a warning message, and gives up. Again, they are happy, so they push it out to production.

Not long after that point, someone puts in a request to have it run a MotorolaPager. Guess what. Those things can beep like crazy, but they sure can't move forward by themselves. The program blows up again, but this time it's down at the part where it's running minion.forward(). It never occurred to them that they might encounter something without that particular feature.

Yet another maintenance programmer parachutes in and adds more boilerplate code to handle this by recycling the existing workaround.

Advance(minion):
  begin block this_might_fail:
    minion.beep()
  end block
 
  if it failed:
    display "tried and failed to make minion beep"
    return to the caller without doing anything else
 
  begin block this_might_fail_2:
    minion.forward()
  end block
 
  if it failed:
    display "tried and failed to move minion forward"
    return to the caller without doing anything else

This pattern plays out over and over and over. Every time, they find out the hard way by having the program blow up while it's running and then fail to restart since the first thing in the queue is a "request of death". Every time this happens, a bunch of pagers go off and the poor sysadmins have to scramble to clean up the job queue and restart things before users notice.

The users notice. Some get tired of this and take their business elsewhere. The others just sigh and put up with the flaky service.

There are languages which permit these kinds of shenanigans and there are others which do not. One view of it is "kinder and gentler" versus "mean and strict". Another view of it is "kind of wobbly" versus "tends to just work". Somehow, this sort of thing seems to be open to debate instead of being a solved problem.

You can guess which side of the fence I'm on.