{ The Rithm Blog. }

Debugging Like a Scientist April 29, 2019

When you're first learning how to code, error messages can be intimidating. Because of this, beginners sometimes ignore error messages entirely. Instead, they will favor more of a "guess-and-check" approach to fixing bugs. While we don't encourage this approach at Rithm, we recognize that in the context of solving small, isolated problems, it can be successful. But when it comes to working on larger applications, professional engineers need a more sophisticated set of tools to fix problems.

Fortunately, one of the most successful strategies for fixing errors is also one of the oldest: applying the scientific method. In this post, I'd like to review the scientific method, and highlight how you can use it to solve programming problems more quickly.

Most people learned about the scientific method at some point during their schooling, but unless you self-identify as a scientist, it's probably not something you spend a ton of time thinking about in your daily life. Let's start with a brief review of the method, and then we'll apply each step of the method to a specific programming problem.

The Scientific Method: An Overview

The scientific method consists of these five parts:

  1. Make an observation.
  2. Ask a question.
  3. Generate a hypothesis.
  4. Test your hypothesis.
  5. Analyze the results of your test.

What makes this method great for programming is that it forces us to be more thoughtful about our debugging strategy. If we're adhering to the scientific method, then before any debugging step we take, we should always be able to answer the question "What question are you trying to answer with what you're about to do?"

An Example

Let's imagine that you've got an array of movie data that looks like this:

let movies = [{
  title: "Black panther",
  dollars: {
    opening: 202003951,
    total: 700059566
  }
}, {
  title: "Avengers: Infinity War",
  dollars: {
    opening: 257698183,
    total: 678815482
  }
}, {
  title: "Incredibles 2",
  dollars: {
    opening: 182687905,
    total: 608581744
  }
}, {
  title: "Jurassic World: Fallen Kingdom",
  dollars: {
    opening: 148024610,
    total: 417719760
  }
}, {
  title: "Aquaman",
  dollars: {
    opening: 67873522,
    total: 335061807
  }
}];

For each movie, you're given the title, as well as two pieces of information about how much money the movie made: the total gross and the opening weekend gross.

Now, let's imagine that you want to calculate how much money these movies made in the aggregate. So you write the following bit of code:

let grandTotal = 0;

for (let movie in movies) {
  grandTotal += movie.dollars.total;
}

console.log("GRAND TOTAL IS", grandTotal);

Unfortunately for you, when you run this code, you get an error. But fortunately for you, you're familiar with the scientific method! Let's see how taking a methodical approach can help.

students working in lab

Make an observation.

In cases like this, the most obvious thing to observe is the fact that there is an error. But don't stop there; you should try to read and understand as much of the error as possible. In this case, the error reads:

Uncaught TypeError: Cannot read property 'total' of undefined

So it's an error, but more specifically, it's a TypeError. Also, the word undefined shows up, which has very specific meaning in JavaScript. Both of these observations offer clues to the underlying problem.

Note that in many cases your programs may have bugs, but won't necessarily throw errors. You may expect to get a number, but receive NaN, for instance. In this case, the observation will have less to do with a specific error, and more with how the output differs from your expectations.

Ask a question.

In terms of debugging, the most common question you'll ask here is basically "Why doesn't it work?" We rarely anticipate bugs, and the first reaction to encountering one should be curiosity.

Generate a hypothesis.

Here's where beginners to programming often deviate from the scientific method, and begin trying to fix their bug before they've generated a hypothesis. Without a hypothesis, it's much harder to generate reasonable ideas for how to move forward. Even worse, if you have several ideas, there's no way for you to assess their merits without a working hypothesis of what's wrong.

In the current example, one hypothesis might be "at some point, one of the variables I'm considering has an undefined value." This would agree with the error message, which references undefined.

(Note that this is where professional programmers often have a leg up. Experience gives you a great deal of intuition into why problems occur. If you want to become a better programmer, putting some intentional effort behind how you generate hypotheses is a good place to start.)

Test your hypothesis.

Here's where you should feel free to start brainstorming next steps. Just make sure you keep the hypothesis in mind. No matter what you decide to do, you should be able to answer the question, "How will the results of your test support or refute your hypothesis?"

Poorly thought out tests typically involve throwing console.log statements on every line, or changing six different things in the code and hoping for the best. Typically, the most helpful tests are small in scope; a single console.log statement, or a single tweak to the code. This is because smaller changes are easier to reason about, and allow us to eliminate possible sources of the bug more quickly. And after all, we're most often interested in narrowing the universe of potential sources of the bug, because once we know the source of a problem, it's easier to fix.

Or, as Sherlock Holmes so often put it: "When you have eliminated all which is impossible, then whatever remains, however improbable, must be the truth." Your tests should eliminate possibilities, so that the space which remains gets smaller.

In this case, one approach might involve console.logging the movie variable, to make sure it's the object we think it is.

If you need specific ideas about things to try out, we've got you covered.

Analyze the results of your test.

If we modify our code as follows, we get a big clue:

let grandTotal = 0;

for (let movie in movies) {
  console.log("Movie is:", movie);
  grandTotal += movie.dollars.total;
}

console.log("GRAND TOTAL IS", grandTotal);

In the console, before our error, the message Movie is: 0 appears. Our movie isn't actually a movie at all - it's a number!

Depending on your experience, this may be enough for you to successfully troubleshoot the issue. If it isn't, you can generate a more specific hypothesis and develop a new test. For example, maybe you think that the movie variable is referring to the index in the array, rather than the element. You could test this by commenting out the second line in the loop, and running the code again to see a different message logged for each iteration through the loop.

Indeed, this is exactly the problem. One way to fix it is to use a for...of loop instead of a for...in loop:

let grandTotal = 0;

for (let movie of movies) {
  grandTotal += movie.dollars.total;
}

console.log("GRAND TOTAL IS", grandTotal);

Alternatively, if you want to stick with the for...in loop, a more sensible variable name will help clarify what's going on:

let grandTotal = 0;

for (let movieIdx in movies) {
  let movie = movies[movieIdx];
  grandTotal += movie.dollars.total;
}

console.log("GRAND TOTAL IS", grandTotal);

Either way, you should now see how much money these films made!

Conclusion

Applying the scientific method doesn't always mean that you'll squash your bugs on the first try, as we've done here. But it does provide you with a framework to solve problems systematically.

When you're first learning how to code, there's a ton to learn, and it's easy to get overwhelmed when problems arise. The next time you get stuck, try to frame your problem in terms of the scientific method, and see if that helps you chart a way forward.

Written by Matt Matt

Back to all posts

Get Started with Rithm School