Saturday, June 16, 2012

Lunch Codes

Since the year is 2012, it's only a matter of time until everybody has fully automated kitchens full of helpful robotic appliances. Fortunately, you and I both are about to pretend that we're working for the company that writes the software for this futuristic kitchen.
Come on, humor me here.

First task: write a function for making sandwiches.
A quick glance at the interface documentation shows that we can tell the robots to obtain ingredients like so:


That's not too bad. In fact, it even returns true/false to indicate success, which is lucky because the boss insists that this system be absolutely, completely robust. After all, this thing is going to control the stove, microwave, automated flying butcher knives... we better keep every instruction safe.

if (!kitchen.fetch("bread")) {
    // To do: ask Bob how to get the error code }

Thankfully, Bob is in a meeting with Marketing, and is only staying awake by checking emails on his phone every 5 seconds, so we already have an answer.
Now we can get crackin'.

The kitchen module returns all its errors via GetLastError with an output parameter.
The boss wants every error to go on the log with our own unique error number.
Janet at QA insists that the function returns after an error.
Also it's C++ so you're a bit screwed when it comes to string formatting.
Let's see what we have so far:

if (!kitchen.fetch("bread")) {
    kitchen.GetLastError (&strErr);
    sprintf (tmp, "%s (error number %d)",strErr, 0x03);
    log.writeln (tmp);
    return false;

Could be worse.
What else... oh right, we were making a sandwich. We need to fetch cheese, tomatoes, and mayo.
Ctrl+C, Ctrl+V, Ctrl+V, Ctrl+V, change ingredient string and error number, and gaze upon your majestic code, ready to be shipped. A tiny feeling of unease is trying to sneak into your mind, but you swat it away. This is just 24 lines, even fits on the monitor, stop worrying!

On to the next task: writing the function for making a fancy wedding cake.
This involves 57 carefully-measured ingredients, 8 bowls, 14 spoons, 4 egg whisks, and a dozen different kitchen sub-modules, and each of those can potentially have an error. With our error handling system, that's going to be approximately 800 lines. Oh, and the oven requires dynamic memory allocation, and C++ has no garbage collection, so some or all of these 100 exit points are going to require that you free some memory.


Back when I wrote "Ctrl+C, Ctrl+V", you should have already gone for my throat. We all remember our 10th grade comp-sci teacher droning about how if you see a pattern or repetition in the code, it means you wrote the wrong code. Then the comp-sci intro lecturer said the same. Then the lecturer in whatever programming course you had in your second semester.
They had a point. You do not repeat code unless you have to.
Also, shorter is better.
So, on the whole, this sort of code should be a criminal offense.

This sort of coding will create something ugly and bloated and uselessly difficult to read. When you get the bug report about how PrepareWeddingCake makes the bathroom window shatter if the user's name starts with the letter M, you don't want to have to dig through all of that. In fact, if you keep it concise in the first place, it'll be easier to notice something out of line, so the bug will probably never happen in the first place.

I think it was Joel Spolsky who said that elegance means having your product do something difficult without drawing attention to it, pretending that it's no problem at all.
Well, I see no reason why you shouldn't make your code elegant. This is what you're working on all day long, have some professional pride, man! Are you just another code monkey, or a proper craftsman? Sure, you had thousands of error cases to think of... but why should I care? You already took care of them. If over 80% of your code is just error handling, it means that while I just want to find out how you made the forks do a barrel roll, 80% of the code I have to wade through is irrelevant.

Sigh, there I am, ranting about long code again.
I just want this function to be a simple list of ingredients and utensils and functions. My cake recipe doesn't tell me what to do if I'm out of flour, and I doubt anyone would claim that such source code is easier to read than a simple step-by-step recipe with an ingredient list.

This is my solution. I’m going to show just the interesting part of the code, no explanation, no clarification. I want you to read it only once, and then I’ll give you a quick pop quiz about it.

// variable declarations go here

st= st&&  kitchen.fetch (“avocado”)        || Error (0x01);
st= st&&  kitchen.fetch (“tomato”)         || Error (0x02);
st= st&&  kitchen.fetch (“cucumber”)       || Error (0x03);
st= st&&  kitchen.fetch (“lettuce”)        || Error (0x0);
st= st&&  kitchen.dice  (EVERYTHING)       || Error (0x05);
st= st&& (EVERYTHING, BOWL) || Error (0x06);

// error handling goes here

Here’s your pop quiz, you have 10 seconds per question, guess if you must:
1. Does this code handle errors?
2. What is the list of steps for making a salad?
3. A user just reported getting error 0x02 while making a salad; what caused it?
4. How can I replace the avocado in this salad with a carrot?
5. Where's the bug?

Easy so far, right? Let’s make it harder:
6. This function needs dynamic allocation added to it. How many places in this function do you need to touch to make sure the memory is properly freed?
7. The boss wants to radically change the logging format. How many places in this function do you need to touch to change it?
8. The company chef insists that this salad needs cabbage in it. How many lines of code do you need to add for that?

Please notice the lack of questions like “how does this work?”. If you don’t understand this code, the term you need to google is “lazy evaluation”.
But I’m sure that even if you don’t understand why it works, you can answer the first 5 questions in the blink of an eye. And, more importantly, this recipe could call for 30 ingredients and you could still answer without any scrolling, and if it did something more serious where the order matters, you’d be able to see the entire order in front of you at once.

Is there any simple purpose for reading a function that questions 1-5 didn't cover? Is there any such purpose that this code makes more difficult than the dozens of lines you'd have had otherwise? Please let me know.

Meanwhile, just remember to initialize st to true, and make sure Error returns false.
Bon apetit!

No comments:

Post a Comment