“So there I was, at 30 thousand feet…” We’ve all told stories that started like that, even if we were never WWII bomber pilots. Every coder who’s been on the job more than a few years has harrowing tales of late-night shifts, bug-hunting expeditions, and miraculous saves at the eleventh hour. We’ve seen good code and bad code and lived to tell the tale. I’m with you, brother. I’ve been there, too.
We’ve all written the occasional snippet of bad code. There are many reasons: the misplaced typo; the invalid assumption; the momentary brain lapse. But sometimes it’s not just a fleeting mistake. Sometimes we’re not just writing bad code, but writing the wrong code. When you’re knee-deep in the bowels of your own source code, it’s all too easy to forget what you’re writing for. To borrow an arboreal metaphor, we sometimes don’t see the forest for the trees. Worse than that, as coders, we sometimes focus on the nuances of the bark and really can’t see the forest – or the trees.
In the spirit of therapeutic confession, I’ll give you an example of my own screw-up. I was diligently working on a wired communications protocol for an embedded product, and I had been hacking away at it (ahem, carefully crafting fine software for my employer) for many weeks. It was close to working, but there were still a few small bugs that appeared during specific testing conditions. In short, I figured I was about 95% done, when in reality I was probably closer to halfway. At any rate, I had narrowed the problem down to a small set of subroutine calls. The bug(s) had something to do with the way these subroutines interacted with the operating system’s APIs. I figured either (a) there was a bug in the APIs, (b) there was a bug in my code, or (c) there was a bug in the documentation, and I was unwittingly using the API incorrectly. Whatever the cause, I was this close to solving the problem and releasing the code.
Unfortunately, being this close meant exactly nothing. I stayed this close for days. And then weeks. And then a whole month went by and I had almost nothing to show for it. I’d certainly been busy; I’d rewritten and re-tested thousands of lines of code. And I’d learned a lot about the operating system, its APIs, and my compiler. I even learned about new features of the microprocessor I was using. All interesting stuff, but it didn’t actually get me (or my employer) any closer to shipping the damn product.
A small part of me actually enjoyed chasing down those bugs, even though I knew I was behind schedule. It was good, solid, technical work, and it made me a better programmer (or so I thought at the time).
And then it happened. Another programmer wandered by, asked me a couple of questions, and said, “Why don’t you just replace the communications link with shared memory? It’ll go faster, and you can forget trying to solve that API problem.”
Well, duh. He was right, of course, and that’s more or less what we wound up doing. His insight was that, unlike me, he wasn’t up to his eyeballs in implementation details or trying to hammer out a specific fix. He was just trying to solve a problem.
There’s an old story (probably apocryphal) about how the U.S. space program spent millions of dollars developing a ball-point pen that could work in orbit. The NASA astronauts needed to fill out checklists and make notes, and normal pens didn’t work in zero gravity. Eventually, they got their elaborate “space pen” to work and everything was fine. Years later, when the U.S. and Soviet space programs cooperated on Apollo/Soyuz linkups, the question came up, “How did you guys in Russia solve the zero-gravity pen problem?”
“We use pencils,” was the answer.
These are good lessons. Whether we’re running a startup company (or a space program) or not, we need to keep our eyes on the goal, not the implementation details. In product development, it’s the user experience that matters, not the elegance of the code that delivers it. Time spent with your head down chasing a tough technical problem can lead you off track. The bigger the gulf between the customer and the person creating the product, the greater the risk of off-track-ness. Ideally, you cobble together a product, test it on the customer, revise it, and repeat indefinitely. Spending weeks (or in my case, more than a month) falling down the rabbit hole might feel like real work, but it’s likely a big distraction. Focus on the end, not the means.
I can’t be sure yet, but I think the newest negative example of this thinking is Microsoft’s Outlook 2013. One of its many new features is something called “cached Exchange mode.” The idea is to keep the Outlook mail client connected to the server at all times, but to fall back on a locally cached copy if/when the connection fails. The crossover is seamless, in the sense that you can’t tell (and Outlook doesn’t tell you) whether it’s really online or not. That may be nice in concept, but it’s frustrating in practice. Regardless of how elaborate the caching scheme is or how many thousands of man-hours the company spent developing it, the end result is confusing and (in my experience) leads to lost data. Too much focus on implementation; too little attention paid to the user experience.
Don’t forget whom you’re ultimately programming for. And always keep a pencil handy.