I did one screening interview as a freshman on campus where I was rejected without mercy. Apparently the answer to “Can you tell me what was the most difficult bug you faced while programming and what you did to resolve it?” isn’t “My programs don’t have bugs.”There's a huge body of lore around Microsoft interview questions. This is a common one, not used exclusively at Microsoft.
As I read that post, I started wondering what would my answer be? The longer I thought about it, the more surprised I was that nothing came to mind. This could mean a few things: that I'm not a good candidate for working at Microsoft, that I haven't been programming very long, or that I'm not a very good programmer.
Anyone who knows me knows that I would never want to work at Microsoft, so let's ignore that point. I've been slinging code for about 20 years, professionally for about 15 years, so my lack of a response doesn't necessarily imply a lack of experience. On most projects, I'm one of the developers that's brought in to solve the hard problems, so I don't think a lack of response is an indication that I'm just pulling a paycheck and not doing any real work.
The point of a question like this is to see how well and how deeply a candidate relates to his code. It's an obvious trap to elicit a response like "my programs don't have bugs", which is merely an indication that the interviewee (a) hasn't been programming very long and (b) hasn't worked on any project larger than a homework assignment. But if it elicits an animated and involved response from the interviewee, it shows he has a deep and detailed knowledge about his code and the platform he uses to develop software.
Unfortunately, this particular question places the focus on bugs. I wonder if this offers some insight into software development practices at Microsoft. Could it really be that their software development regimen is simply a never-ending bug squashing expedition into an ever-increasing blob of code? It's amusing to think so, but I doubt it.
The more I thought about it, the more I realized that I don't think about my career as moving from one bug to another. Over the years, I've picked up a variety of tools and techniques for isolating and fixing bugs. Some are mundane, like peppering print statements or invoking a source level debugger like
gdb. Some are more complex, like using test fixtures, constructing test data, or gathering diagnostics with a profiler. The skills are important; the bugs aren't.
But I occasionally do remember the bug. One that does come to mind involves a grammar I was writing from an incomplete and contradictory spec. The operator precedence was specified one way, but the expectation was the inverse. I implemented the precedence as specified, which did not result in the desired behavior. This led to many hours of meetings over a few weeks to determine that the spec was, in fact, wrong. The solution was to twiddle a couple lines of code, maybe 5 minutes to fix. Thirty minutes if you include the obligatory stop at the coffee shop to blow of some steam.
What matters for a professional software developer is preventing bugs from occurring in the first place, or quickly isolating bugs when they do occur. On that same project, I needed to test the grammar, so I wrote a test harness to run the parser capture its output, and compare the output to an expected value. I wrote another program to generate these inputs via brute force: running through all combinations of terms and operators, hunting for both successful parses and expected failures. In the end, I had a few thousand test cases that acted as a pretty complete set of regression tests.
Since I started working in Haskell, I find that I think even less about the bugs.
In many cases, a bug is a symptom that you're working on a large system, and the assumptions made in one part of the system violate the assumptions made in another part. A classic case is a memory leak, where the library assumes the caller manages a block of memory, but the caller fails to call
free()when necessary. Or the opposite situation, where the library manages memory, and the caller calls
However, in a Haskell program, these kinds of problems simply don't happen, or at least don't happen with nearly the same frequency. Each function acts as its own little world. The net result is that when debugging a particular function, you can ignore the rest of the universe and focus on one small piece of the puzzle in isolation.
Another advantage to Haskell is that many bugs don't happen because the type checker catches them. For example, if I want a single value out of a list, then I can't use
map, which returns a list. Instead, I need to use a fold, or post-process the result of map with a function like
last. In either case, the type checker will prevent me from running a program with this kind of bug.
All of which makes it easier to focus on the problems to be solved, and the tools needed to solve these problems, and ignore mundane details like bugs.