Debugging tips that don’t suck


You write code. You are proud of your career as a coder. Your job pays bills and has certain benefits that make you a rather unique individual. Maybe you’ve published a few apps in the market places. Maybe your software is running a multi-million dollar business. Whatever the case, you have street cred and you feel good. All is right with the world, or is it? Hi, I’m Cliff. You’re here because you think nothing can go wrong in your glass tower. I’m here because I recognize how everything goes wrong in everyone’s tower. Today I wanna talk about some stuff that few people ever talk about.

I’ve been paying LOTS of attention to the lesser glorified parts of computer programming these days, testing, documentation, build systems, but most notably debugging. It’s an obvious blind spot for some. Others see it as a necessary evil. In almost all cases it’s not something that people like to do or even discuss at dinner parties. When was the last time you started a conversation like, “So I was examining the core dump from my segfault and I immediately recognized I had a buffer overrun from assuming my arrays was not zero based in this one edge case…” See most engineers are solution driven. That is to say we are fueled by providing answers. So much so that we often provide answers to questions before the question is even formulated. I think of it as Answer First Development! (We make terrible Jeopardy contestants.) In my career I learned to take an opposite approach to most things we do as programmers. I learned that it pays huge dividends when I do so. Inverting the common practice of striving for a solution means focusing on the problem or the actual question being posed. Emphasizing debugging is one form of inverting our common thought patterns as it forces us to think about the problem rather than race towards the solution.

This post takes a Mr. Miyagi approach towards learning to debug. In the movie, “The Karate Kid” Danielson was in a rush to learn karate so he could beat up the bullies in his high school that were picking on him. You may be in the same rush to learn how to debug your programs. Danielson took the first few lessons seriously because he knew his new sensei had experience fighting. Mr. Miyagi wanted to share this experience with Danielson because he knew it would make him a better person and not a better fighter. I am attempting to share my experience here because it can make you a better programmer, not a better debugger. Danielson became frustrated part way into his lessons because his sensei was not showing him how to punch or how to kick. Instead he was making him wax the cars and paint the fences. I will not show you any magic tools here that make the bugs leap off your screen and run in fear. Danielson confronted his sensei about punching and kicking when Mr. Miyagi called his waxing and painting experience to memory. This was a dramatic scene where Danielson realized he had been gaining experience all along. As you read these tips you may get anxious and feel like you know the stuff already. Bear with me and follow through to the end. I promise the experience will make your bug punching and kicking all the more powerful.

What is Debugging, What is a bug?
Debugging is the science of discovering bugs in your software. To discover a bug you have to understand what a bug is. The first computer bug ever discovered was an actual insect observed in a machine by a woman named Grace Hopper. This makes Grace the 1st and probably greatest debugger of all time! It also gives important context to the topic. Before I digress, let’s define a computer bug. A bug is an unintended or unexpected behavior in a computer application. Because application behaviors are also considered features a bug can actually be thought of as a class of a feature in your software. These are features we don’t expect or don’t want. Sometimes we classify these features as “by design”. Features or application behaviors are created by steps followed by the code in your application. A bug is actually a set of steps or a path through your code that you don’t expect to happen.

A bug is a feature
A bug is a feature

#1 Expect the unexpected!
The best debugging advice I can ever share is to get comfortable with bugs. As a kid I was terrified of bugs. We would get those loud chirpy crickets in our house and it would scare the pajamas off of me! As I got older I got slightly more comfortable with crickets (but then started to see those 100 legged millipedes which made me twice as scared!) Had I been exposed or forced to touch bugs as a baby I would not have been as frightened. I’m saying all this to say that you have to approach every project knowing there will be bugs and familiarizing with this idea from the start. If you think your code will only run down the path you expect it to then you are setting yourself up for disaster. You might say, “Cliff, I do this all tahm! I know my code has bugs, you ain’t sayin’ nothing new!” Hold on a minute because I’m framing the picture for the most powerful of all debugging tips. (I know it’s taking me a while to get to the point but I have this verbosity issue!) All programs begin with two possible paths of program flows: The happy path, or the path you wish your program will follow and the unhappy path or the path you don’t expect your program to follow.

#2 Break your code before it breaks you
Create errors in your code early on. I’m suggestion you create and explore the unhappy path before you design the happy path. The reason is not immediately obvious, but this is the path you will spend most of your time in. The idea is that most programs rarely follow the happy path until very late in development. This means that even though you wish you were on this path, the vast majority of your time developing is follow paths you don’t want. If you had to move and I gave you the option of living in two neighborhoods, one was nice looking with decent schools and the other was crime riddled you would probably spend most of your time getting familiar with the nicer neighborhood. You’d visit the shopping plazas and speak to potential neighbors. Then you would look at your budget and realize you never really had the option of living in the nice place just prior to moving to the slums. You would be uncomfortable, not knowing the dialect, which streets to avoid and the people would all look crazy/funny. If instead, you took the initial visit to the slums instead of anticipating the nice neighborhood, you could make friends with the community, learn how to role through the streets and blend in, and see which side of the street to steer towards to avoid huge potholes. Life would not be what you wished for but you would be comfortable and you could actually thrive. Programming is similar. Run your functions with incorrect input and force your logic down paths you don’t want to get familiar. Meet with the Exception drug lord on the corner and have a talk with him. Get to know his schedule so that you don’t interrupt his business and get on his nerves. I’m making too many analogies, let me share some actual code examples.

XML Parsing

var parseString = require('xml2js').parseString;
function doParse(xml) {
   var data;
   parseString(xml, function (err, result) {
   if(err) throw err;
   data = result;
   });
   return data;
}
var coders = [];
coders.push(doParse("<person name='Clifton' age='40'><company name='GE'/></person>"));
coders.push(doParse("<person name='Stephen' age='36'><company name='Apple'/></person>"));
coders.push(doParse("<person name='Gloria' age='53'><company name='Skype'/></person>"));
//end code

The above example defines a function that parses arbitrary XML strings and converts them into JavaScript objects. It then uses the function to parse various XML strings and push a bunch of person objects onto an array. Dropping this into your an app that needs to create people out of XML would give you a flawless victory. You would go home at night, pet your dog, feed your kids, catch the latest episode of Judge Judy then do a goofy dance just before going to bed at night for a job well done. Now let’s have some REAL fun. Change 11th line by removing the final “/” character in the closing person tag.

coders.push(doParse(""));

If you understand the rules of XML, you’ll note that this creates an uneven or not well-formed XML document where there is an no closing tag. Running the code will now produce something similar to:

Error: Unclosed root tag
Line: 0
Column: 60
Char:
    at error (/Users/212474815/dev/nodeexample/node_modules/sax/lib/sax.js:667:10)
    at strictFail (/Users/212474815/dev/nodeexample/node_modules/sax/lib/sax.js:693:7)
    at end (/Users/212474815/dev/nodeexample/node_modules/sax/lib/sax.js:674:47)
    at Object.write (/Users/212474815/dev/nodeexample/node_modules/sax/lib/sax.js:991:14)
    at Object.SAXParser.close (/Users/212474815/dev/nodeexample/node_modules/sax/lib/sax.js:157:38)
    at Parser.exports.Parser.Parser.parseString (/Users/212474815/dev/nodeexample/node_modules/xml2js/lib/xml2js.js:503:42)
    at Parser.parseString (/Users/212474815/dev/nodeexample/node_modules/xml2js/lib/xml2js.js:7:59)
    at exports.parseString (/Users/212474815/dev/nodeexample/node_modules/xml2js/lib/xml2js.js:535:19)
    at doParse (repl:3:1)
    at repl:1:13

This is the type of thing that would commonly occur in a project and the type of error that could send shivers down your spine when you’re not familiar with it. The error is telling you what we already expected, the root tag, , is unclosed. Just by making this one minor breakage YOU have taken control and gained the experience of knowing how your program will behave when it gets invalid XML in this location. LEt’s go a little further and break it in a different way.

coders.push(doParse(""));
Here we have intentionally removed the quotes from around the company tag’s name attribute. WE get an exception similar to:

Error: Unquoted attribute value
Line: 0
Column: 47
Char: G
    at error (/Users/212474815/dev/nodeexample/node_modules/sax/lib/sax.js:667:10)
    at strictFail (/Users/212474815/dev/nodeexample/node_modules/sax/lib/sax.js:693:7)
    at Object.write (/Users/212474815/dev/nodeexample/node_modules/sax/lib/sax.js:1380:13)
    at Parser.exports.Parser.Parser.parseString (/Users/212474815/dev/nodeexample/node_modules/xml2js/lib/xml2js.js:503:31)
    at Parser.parseString (/Users/212474815/dev/nodeexample/node_modules/xml2js/lib/xml2js.js:7:59)
    at exports.parseString (/Users/212474815/dev/nodeexample/node_modules/xml2js/lib/xml2js.js:535:19)
    at doParse (repl:3:1)
    at repl:1:13
    at REPLServer.defaultEval (repl.js:252:27)
    at bound (domain.js:287:14)

Nothing surprising here. The error is telling you exactly what mistake you’ve made. (Pay close attention to the detail in the error message as we will go over errors in detail a little later.) Internally your mind is familiarizing with the outcome and making important associations that will become important later on. These examples are stand alone snippets but if they were part of a larger program that included a web page and several other pieces you would note where the program stops, or does not stop. You might also see other errors in your program’s output logs that result from the intentional error. Paying attention to how things behave with random incorrect input can be scary but gives you experience and confidence much like you would get from making a turn down a random road while driving. LEt’s take the example one step further and restore only one of the quotes you intentionally removed:

coders.push(doParse(""));

You now would get an error similar to the following:

Error: Unclosed root tag
Line: 0
Column: 60
Char:
    at error (/Users/212474815/dev/nodeexample/node_modules/sax/lib/sax.js:667:10)
    at strictFail (/Users/212474815/dev/nodeexample/node_modules/sax/lib/sax.js:693:7)
    at end (/Users/212474815/dev/nodeexample/node_modules/sax/lib/sax.js:674:47)
    at Object.write (/Users/212474815/dev/nodeexample/node_modules/sax/lib/sax.js:991:14)
    at Object.SAXParser.close (/Users/212474815/dev/nodeexample/node_modules/sax/lib/sax.js:157:38)
    at Parser.exports.Parser.Parser.parseString (/Users/212474815/dev/nodeexample/node_modules/xml2js/lib/xml2js.js:503:42)
    at Parser.parseString (/Users/212474815/dev/nodeexample/node_modules/xml2js/lib/xml2js.js:7:59)
    at exports.parseString (/Users/212474815/dev/nodeexample/node_modules/xml2js/lib/xml2js.js:535:19)
    at doParse (repl:3:1)
    at repl:1:13

This is exactly the same error as the first intentional mistake! What’s happening here is the XML parsing logic is not seeing any elements after the name attribute because there is no closing quote. The rest of the XML is considered part of the value for the name attribute! Even though you have a closing take the parser thinks this is part of the attribute so the error you get is “Unclosed root tag”. Imagine if this were to happen in a slightly larger program with a few more XML tags in the input. A cursory glance at the error then the XML would leave you stuck for an answer. It is extremely easy to overlook the missing quote while counting the opening and closing tags in the XML. In our case we KNOW that we’ve omitted the closing quote and we now know that this can cause an error indicating unbalanced tags. This is an extremely valuable bit of experience as you now know not to take the error message at face value! Now your mind has made another critical association unclosed root tags errors could result from a missing attribute.

You would be surprised how common this type of exception is and how many developer hours are lost chasing issues like this. If you are just beginning to learn JavaScript and XML parsing you can already begin to assume the diagnostic ability of a seasoned engineer wth 20+ years experience with that one piece of knowledge alone! This comes just from removing one character and making an extra round trip running through your program. If you make a regular habit of breaking your code and watching its behavior you will triple and quadruple your ability to diagnose and debug program failures.

#3 Read your error messages
In order to follow tip #2 above you have to actually READ your error messages! Don’t tel Google to read them! Let me ask you a question. How do you feel when reading this message in your console? (Be brutally honest with me! Type your feelings immediately in the comment box.)

'uncaught TypeError: undefined is not a function' on line 49

Do you get a sense of anxiety? Does your muscle memory kick in and pull your mouse towards the swipe to select and copy gesture? (I feel like a Google error messages anonymous group is a good idea!) Fighting the urge to pull the error into the Google search box is an important milestone. Let’s look closely at the error message.The first part says “uncaught TypeError”. This can be intimidating in its own right because it’s uncaught! I certainly wouldn’t want to catch anything and I could understand a novice programmer believing her/his computer is at risk of a virus. The type error also makes it sound obscure. Then you have the following text: “undefined is not a function”. Words like “undefined” and “function” can turn your stomach because you spent all your time defining the logic and you want your program to function! However, let’s put this all into context. The context is actually specified as the last piece of the error message, “on line 49”. (Usually there’s a filename in the error message but I’m just simplifying.) If this were a JavaScript shopping app you might see, on line 49, some code that looks like this:

var salesTax = order.getLineItemsTotal() * .05;

The error is saying something is “undefined”. On this very line you are defining a variable, salesTax, so the salesTax can’t be undefined. Look at the three other pieces on this line, “order”, “getLineItemsTotal()”, and “.05”. The “.05″ is what we call a literal which has a definition by its value which leaves us with a coin flip between order” and “getLineItemsTotal()”. Here’s where it gets tricky as most people would gravitate towards the “order” and question why or how it could be undefined. Go back to the error message. The “is not a function” part of the error message indicates there is a problem with a function somewhere on the line and the only thing that looks like or resembles a function is the “getLineItemsTotal()” part. Putting the pieces together you would gather that “getLineItemsTotal()” is undefined. To resolve and fix the problem you need to see what is in the “order” variable. This variable has to hold a value of some particular type and this type probably does not define the “getLineItemsTotal()” function.

#3b Understand your stack traces
Taking the above example you might have enough information to track trace through your code and find the source of the problem. There’s another piece of the error message that can speed this process. This is called the stack trace. If you’re relatively new to coding you may have never heard of a stack or a stack trace before. The stack is the set of function (or method if you’re in Java) calls that happened up to the current point of your program. When your program has an error you get a message that includes the stack and you can use this stack to trace backward through your code to see what happened.The above error might have a stack trace like this:


uncaught TypeError: undefined is not a function' on line 49
at submitOrder (http://localhost/orderform.htm:49:22)
at handleFormPost (http://localhost/orderform.htm:572:1)
at http://localhost/orderform.htm:234:91

The trace shows you the sequence of events, in reverse, that happened up to the point of the error. The stack follows the error message as a set of lines each pointing to prior function calls. The first line in the stack points you to the line that failed, order form.html line 49, column 22. (An interesting part here is how column 22 points to the start of the getLineITemsTotal() function call.) The next line points to the spot where submitOrder was called. This is within a function named “handleFormPost” at line 571 column 1. The next line points to the code where handleFormPost is call inside the orderForm.htm page at line 234 and column 91. This is possibly in an action attribute of an HTML tag definition. From the top you could see where the error happened inside the submitOrder function and the function, handleFormPost, that called submitOrder. Looking at line 572 in handleFormPost you might see something like:


var orderId = doc.getElelementById("hiddenInputOrderId").value();
submitOrder(orderId);

Here you see on 571, the line prior, we get an orderId from the value of a hidden input field and pass it to the submitOrder function. This becomes the thing the that submit order function operates on. Because this value is a “string” data type, there would be no “getLineItemsTotal()” function defined for it. Looking elsewhere in the code you might find the definition for the “getLineItemsTotal()” function and see that it is part of an object that you could create using the order id. A possible fix would be to create an order object that defines the “getLineItemsTotal()” function using the orderId from line 571. This might look like the following:


var orderId = doc.getElelementById("hiddenInputOrderId").value();
var orderObject = new Order(orderId);
submitOrder(orderObject);

#4 Control your environment
The biggest time sink in any debugging session is identifying random behavior. If you cannot reliably reproduce a problem then you have no hope of fixing it. These are the “It works on my machine” bugs that can send you out the window to the ledge of your building, begging for a second life. When a bug manifests on a particular computer the first thing you must do is identify the environment. Ask questions like which operating system and version is running? What environment variables are set? Which version of the compiler was used to build the program? which machine was used to build the program. which steps were taken to reproduce the bug? It may not be practical to recreate the exact scenario but capturing as much of the problematic environment as possible can save triple time it cost to find the problem. These problems impact seasoned professionals just as much as newcomers. I recently found myself chasing a bug that was related to a particular set of data in a database which lead to me closing a bug multiple times only to have it repeatedly reopened. I ended up changing code that was not related to the original problem and spent a ton of time that I could have saved by re-using the problematic data set.

Identifying your environment is the 1st part of the tip. Controlling your environment is the second equally critical part. If a bug only happens when a certain file exists on the computer, always save the file somewhere safe and restore the file to its original location/condition after you’ve reproduced the problem. Running the program can change the data and the shape of your system. and if you don’t pay close attention you will mistakenly think a recent code change has fixed the problem when actually the problem is only hidden because the state of your system has changed. If you are an advanced programmer then you can take advantage of virtual machines and use snapshots to restore state and assist you in debugging. (I use Virtualbox snapshots all the time and they are a life saver.) If you have no idea what I’m talking about then you can try to rely on a version control tools like Subversion or git to undo your program and file changes while recreating the bug. Don’t let another developer or your manager introduce variables into your environment while investigating. By variables, I am referring to anything that can vary or change, compiler tools, third party libraries, databases, system updates and especially the internet! (Controlling the state of the internet is a slightly more advanced topic I’ll leave until later.) It is incredibly important that you maintain focus and watch the state of the machine, the program source code, any new or changed files and system updates until you’ve identified the problem.

You may feel overwhelmed by the amount of things you need to control when reproducing an error as it can be taxing and near impossible. The idea is not to actually control everything but to observe which things you can reasonably control while identifying the things you cannot. Equally as important as maintaining consistency in your environment is paying attention when things change. While iterating over a scenario that involves multiple steps to reproduce a problem you can get fatigued and begin to unintentionally take shortcuts. There might be a different, not as complicated path through your program that gets to the spot where the error occurs and you might take this path after a few unsuccessful attempts to fix the problem. Avoid doing this as the shorter path may exercise different code that can mask the effect of your latest source changes. You might also super from a particular type of highway hypnosis. It’s where errors start to look like the closing credits of your favorite movie. Have you ever paid attention to anything beyond the starring actors/actresses in the closing credits? Do you know who the “best boy” was in the movie the Titanic? (hint: it was NOT Leonardo Dicaprio!) If you make a change and still see the error message make sure it is THE error message and not a different error message. It is far too easy to assume you are still seeing the same error you just tried to fix when a stack trace whizzes by in the log output.

#E Avoid assumptions
One of my favorite TV shows of all time is House M.D. because of this quote, “When something doesn’t make sense one of your assumptions is wrong.” It is my goto answer whenever I get stuck. The arch enemy of the debugging programmer is the assumption. It robs you of any information you possibly discover and hits a hard reset on your progress. Humans often generalize make assumptions about their environment. Computers are extremely specific. Understanding random behavior that doesn’t make sense means ruling out your assumptions one by one. You often assume you are in fact seeing the same error when sometimes the error changes. You might assume you are performing the same steps as the person who reported the issue. You assume the program is running in the same environment it did yesterday but an automatic update on your computer can cause subtle yet important things to change. Get in the practice of calling everyone of your assumptions into question, even the more obvious ones.

It’s late and I promised to have this post up by the end of the week so I’ll wrap up here. Check back here for tips on dealing with things you have no control over. What I am doing here is laying the groundwork for my next series of debugging tips that don’t suck. You should now be building familiarity of how to properly debug a program which should always start with a question. It takes a bit of patience as you should never rush into debugging looking for an answer. Also note that I have not once spoken about using an actual interactive debugger or any Integrated Development Environment (IDE) tools. This was intentional. Even though these tools are invaluable you want to begin to debug programs without such tools. I imagine how you will feel when you start pulling the bug or problem out of just the error message. Imagine a much more senior engineer firing up a debugger while you’ve already pin pointed the source of the problem. These tips are core everything else that debugging involves.

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s