Your Five Step Debugging Guide: Part 4

This week, we're working through the 5-step debugging script, detailing one step each day:

  1. Ensure you can clearly articulate the symptoms of the defect and reliably reproduce it
  2. Define the boundaries in your code within which the defect could exist
  3. Form a hypothesis about the cause
  4. Test hypothesis (You are here)
  5. Begin again at Step 1 until you can demonstrate unambiguously that the defect has been resolved

Today, we're going to get wild and finish it off with steps 4 and 5.

4. Test Your Hypothesis

Yesterday I left you with this little nugget. The function below is supposed to return the number of occurrences of a given value within a given array, but instead is always returning the length of the given array. Did you spot the problem?


function countValueInCollection(value, collection) {
  const foundValues = collection.filter(v => v = value)
  return collection.length
}

You might have noticed that the return statement is simply returning the length of the original collection, rather than the length of foundValues. If you copied this into an editor or REPL and ran it, you would find, however, that that didn't fix the problem.

Whaaaaa...😤??

This is why I advised yesterday to frame each fix as a hypothesis, no matter how sure you are of its correctness. If you said to yourself "Oh, this is obviously returning the wrong value", then you'd be left saying WTF?? when you implemented your fix of return foundValues.length and nothing changed. Your frustration level would probably begin to rise, even if it was just a little. You might begin to doubt that this function was being called at all and start looking elsewhere for fixes, basically undoing all the work you did to narrow down the defect to just two lines of code.

This solution is not wrong, it's just not complete. Let's try again, using a testable hypothesis:

"I think the problem is that the function is returning collection.length instead of foundValues.length. If I change the last line of the function to return foundValues.length, I should see the function return the correct count of values."

It may seem overly pedantic, but it's keeping emotional distance and more importantly, it keeps you in the mindset of the process. That way, if (when) it doesn't work, you still have a way forward to figure out why.

So you now test your hypothesis, and it fails. That's fine. That's just more information. We move on to...

5. Begin again at Step 1 until you can demonstrate unambiguously that the defect has been resolved.


function countValueInCollection(value, collection) {
  const foundValues = collection.filter(v => v = value)
  return foundValues.length // changed in previous step
}

So we begin again at step 1, but within the constraints of what we already know from going through the steps once. In this case, nothing changes for Step 1. The defect is still presenting with the same symptoms, and we can still reproduce it in the same way.

We'll move on now to step 2, further defining our boundaries. We determined previously that the bug exists somewhere within the countValueInCollection function. However, in our hypothesis we only tested a change to a single line of that function. That means that the defect could still reside somewhere else in the function. Remember from a couple days ago that in Step 2, we are fact-finding only. We're looking for the point where reality diverges from expectation, but suspending our theorizing for the moment. Since the function is just two lines, we can easily inspect the state of our variables at each line, either through console.log() or the debugger.

If you do this, you'll find that foundValues is coming back as an array identical to collection. That's your point of divergence. You've now found the exact line where the remainder of the defect resides.

Now we can move on to Step 3, forming a new hypothesis. Any ideas? You may have noticed that the callback provided to collection.filter is not checking equality (===), it's assigning value to v. This means the return value of our callback will always be value. Put another way, the callback is evaluating the truthiness of the value you're searching for, not whether it equals the value of each array element.

Now don't get cocky. Frame it as a hypothesis.

"I think the problem is that the callback provided to collection.filter is assigning to v rather than checking equality. If I change = to ===, I should see that foundValues is equal to the filtered array."

Now you can move on to step 4, testing your hypothesis by changing = to ===. After making this change, the function appears to behave correctly. Whew! Congratulations! Tomorrow we'll move on to Step 5 (again) and talk about what it means to demonstrate "unambiguously" that the defect has been resolved.

Read Step 5

Next Up:
Your Five Step Debugging Guide: Part 5

Previously:
Your Five Step Debugging Guide: Part 3


Want to impress your boss?

Useful articles delivered to your inbox. Learn to to think about software development like a professional.

Icon