How to think like a programmer

Just turn every question into yes or no answers

When dealing with software bugs, how you ask the question determines how likely you are to get a good answer. To me programming is talking with a computer. The computer understands zeroes and ones, nothing in between. So when someone has a bug and tells a story instead of giving the relevant zeroes and ones, chances are they will never find a solution.

If you have been programming for at least a couple of years, your mind is conditioned to make the switch between talking to humans and talking to a machines. One is fascinated by your story telling, the other wants just the data to process.

When you talk to people like you do with a machine, it won't take long before they start avoiding you. It's no surprise that when you talk to a machine the same way you talk to your friends, it does not work well for you either.

Talking to people is hard. There are dedicated websites just for that. Reading Dale Carnegie's How to win friends and influence people is a very good start. Today however, I want to go the other way, and help you talk to machines. This will help you understand and debug your programs better.

At least as of 2015, the structure of a program is no different then 30 years ago. It is a series of functions, statements and flow controls. Which is very different then the way we deliver messages to our fellow humans.

To Humans: I've been sitting in my car for 30 minutes trying to start it. It just refuses to start. It must be just broken.

To Machines:

var car = Assets.get("mycar");
car.start();
if (!car.isOn){
    print(car.errorno+": "+ car.error);
    exit;
}
...

These two examples are both appropriate for their situation. The car is a complex machine, depending on who you are talking with, chances are you don't need to give them the intricate details of what exactly is broken.

With code, the exact error can be printed so we can either give it to our mechanic or fix it ourselves.

Let's explore real code, and see how we can debug an obscure error.

var allImages = [];
(function (){
    // preload images
    var imgList = ["/images/icon0.png","/images/icon1.png","/images/icon2.png"];
    for (var i = 0;i < imgList.length;i++){
        var img = new Image();
        img.index = i;
        img.src = imgList[i];
        allImages.push(img);
    }
})()
(function(){
    // load non blocking script
    var script = document.createElement("script");
    script.async = true;
    script.src = "http://example.org/async.js";
    document.scripts[0].parentNode.insertBefore(script,document.scripts[0]);
})();

If you run this script, it simply fail. The line number is where the second keyword function is. Does that make any sense? The function is anonymous so of course it has no name. So how do we debug this? By thinking like programmers.

Thinking like a programmer

Ask yourself the questions. There is no need to jump to stackoverflow right away. Don't blame the compiler, don't blame yourself, just take a moment to breath. This is going to happen a million and a half times in your career. So let's see how we can turn our problem into little bits we can easily deal with.

We tried to run a program and it failed. As problem solvers, our first question is:

What error do we get?

As familiar as I am with JavaScript, there is no way I can write more than a few lines of code without introducing a syntax error. You will be surprised how often this is the error in large applications.

In our case we have an error, so let's analyze it:

Uncaught TypeError: undefined is not a function

Let's ask ourselves the right questions.

Do we know what it means?

If no, this is the time to google the error. The goal is not to find a fix we can copy and paste in our code but to understand what the error really means. Sometimes they can be cryptic.

In our case, this error is common enough that we know that we are trying to call a variable that is not defined as function. Quick example:

In JavaScript, when a variable is declared but not set to any value, its default value is equal to undefined.

var a;
console.log(a); // undefined

Meaning, a is not a Boolean, string, number, object or anything yet. So if we call a like a function, we will get the exact same error.

var a;
a(); -- > Uncaught TypeError: undefined is not a function

Note, we didn't even try to find where the error is in our code yet. We just wanted to know what the error meant first, and now we know.

Next question.

Do we know where the error occurred?

The error number points to this line:

        ...
    }
})() 
(function(){ // <--- Error points here
    // load non blocking script
    var script = document.createElement("script");
    ...

We know the line number, but it doesn't make sense. That line has very little information so it is cryptic why our error happened there. So let's see what we can do with the other lines of codes around it. Let's see if the first function is called at all, by placing a console.log('test') right before we close it to see if it runs.

function (){
    // preload images
    ...
    console.log("test");
}

In our case it does. If we check the console, the word test will be printed. This means there are no errors blocking the first section. We can do the same on the second part. Turns out, the second part does not run at all. So our error happens before the second function. This starts to make our error code more interesting.

Does the code work if we remove everything from the function?

We remove everything from the first function to see if it works, leaving only its definition.

var allImages = [];
(function (){
    // preload images
})()
(function(){
    // load non blocking script
    var script = document.createElement("script");
    script.async = true;
    script.src = "http://example.org/async.js";
    document.scripts[0].parentNode.insertBefore(script,document.scripts[0]);
})();

This still gives us the error in the same place. So the next logical step is to remove the code of the second function as well.

(function (){
    // preload images
})()
(function(){
    // load non blocking script
})();

All the extra code is removed and we still have the same error. Now things are getting really interesting. Let's see, we only have two statement left. The first statement defines a function inside the parentheses then the next parentheses calls it. Let me rewrite them to make it simpler.

// Rewrite
(function(){})()
(function(){})();

Let's trace what JavaScript is doing when we run this code. I will label each section of the code;

(Function Definition)(Function caller)
(Function Definition)(Function caller)

Good, now let's evaluate this line by line. In JavaScript, when a function is not assigned a return value, the default value returned is undefined (This is important). I will evaluate the first line now.

> (Function Definition)(function caller with no arguments) --> run the function and return undefined

This is how our code looks like now:

undefined 
(function(){})();

or more appropriately:

undefined(function(){})();

This is starting to make sense. We called our first function and it returned undefined. Now the new code looks like we are calling undefined as a function with a parameter that is an anonymous function definition. But, undefined is not a function. This makes sense. Now we know why the compiler gave us this error, it wasn't just being mean to us after all.

Ok, now that we have a clear understanding of what went wrong, the important question is:

Do we know how to fix it?

If you can, good for you. If you can't well let's try a few things. Let's start by removing the second function, we will be left with this;

(function(){})() 

All it does is return undefined on its own. There is nothing left to process; in other words, it's the end. Well, that reminds me of something. In most programming languages, we end a statement with a semicolon ;, including in JavaScript. Let's try adding that.

(function(){})();
(function(){})();

And just like that, our code work. The beauty of it is that now, you know that you should end every statement in JavaScript with a semicolon.

We fixed this bug by turning everything into simple yes or no questions. At most, we can ask questions were the answer is a direct answer, no philosophizing over it.


I have very short memory span. If I had to memorize a whole story in order to fix code I will be lost before the story ends. But answering a series of simple questions is much easier. Even when your application become large and complicated, if you focus only on the problem you are having, and reformulate it into a yes or no questions, you will have more chances to fixing it.

This is a key skill when it comes to problem solving. Don't think this has anything to do with JavaScript, I was merely using it as an example. You can tackle much more complicated problems by applying the same principle.

There is a very big difference between a) why is my program not working and b) do you know why the program is not working. One entices discussion, the other is "yes" or "no" and can be followed by another question that narrows down the problem.

That is how you think like a programmer.


Comments

There are no comments added yet.

Let's hear your thoughts

For my eyes only