How to debug
-
Introduction
Debugging is probably the most challenging process for most students. When our program “does not work”, we often feel helpless and frustrated.
However, we must learn how to debug, as almost no one can guarantee to write the correct program in one try, so we all have to fix some issues in our programs.
To help you get started, we will discuss how to think about the debugging process and a few techniques. After you practice these basic techniques for a while, debugging will start to feel like a fun puzzle game.
2 Ways of Thinking: “Wrong” vs “Different”
When a program has a “bug,” new learners tend to think, “This program is wrong,” and then try to find which part is wrong. However, that does not help us solve the issue because, most of the time, our program is running correctly, just not the way we thought.
Instead, we should consider it this way: The program is doing something different from my expectation. Most of the time, the project is doing exactly what our code told it to do — it’s just not what we expected.
This new way of thinking will make a massive difference in debugging.
Searching for the Bug Topdown
If we think of bugs as the difference between exacted and actual outcomes, then debugging is simply about locating where that difference comes from.
We can usually narrow down the issue gradually. For example, suppose the program has 3 sprites, then we first need to decide which sprite is most likely causing the difference.
Suppose we pick the first sprite, then we look at the stacks of blocks in that sprite. For example, if that sprite has 5 stacks of blocks, then we need to determine which stack is doing something unexpected.
Suppose we have narrowed it down to one of these stacks, then the next question is which part of this stack or even which specific block is not producing the outcome we want.
Of course, we might be wrong in one of these steps, then we have to pick a new path. For example, if it turns out the first sprite is doing exactly what we want, then we would have to pick another sprite to investigate.
Common Search Techniques
Sometimes, we may get stuck in our search process. There are a few commonly used techniques to help us move forward:
Logical DeductionMost of the time, we can simply rely on logical deduction to identify bugs. Essentially, we need to ask ourselves the most likely reason for the issue. For example, if some sprite is not visible, then it is most likely hidden; if there is no response when we press a key, then most likely the code that handles the key press event is not being used; if a sprite is moving in an unexpected direction, then we probably did not set the expected direction correctly.
This may not be easy for beginners, but with practice and experience, this will become our best tool.
Simplify the Program
For example, suppose a stack of blocks carry out 4 tasks one by one, but we are not sure where it starts to behave differently than we expect. In this case, we can detach all the blocks except those for the first task. Now if we run this simplified program, we can check if it is doing what we expect it to. If so, we add back the blocks for the second task, then run the program again. We can keep doing this until the difference starts to appear.
LoggingAnother very useful technique is “logging”, which means writing down important information as your program runs, such as the value of a variable, or which task your program is working on. This can be done using the print block in the CreatiCode playground. Logging is helpful because it helps us understand the program better. Most often, we will find out the issue comes from not really understanding what our code is doing. And once we do, we can easily fix the issue. A typical use case for logging is to narrow down the reason for why something is happening. For example, suppose we are guessing there may be 2 possible causes, then we add some logging blocks to verify if they are real.
Explain Your Code To Another PersonIf you can find another person who is willing to listen to you (such as your teacher or classmate), you can try to explain your program to them. This forces you to focus on what the code is actually doing instead of what you want to achieve, and very often, you will realize the difference between them as you speak.
In fact, people have found that this method is very effective even if the person you talk to doesn’t say anything, as the key is for you to hear yourself. So some people simply use a robber duck (or any cute animal) as the listener, and explain the code to it.
Step-by-Step ModeFor beginners, we strongly recommend running the program in the step-by-step mode. In this mode, the program will pause after each step, so it clearly shows the workflow of the program. Also, while the program is paused, you can check the value of any variable, or read the position/direction of any sprite.
BreakpointAnother similar technique is using the breakpoint block. In this mode, the program will run as usual until it arrives at the next breakpoint block. The program is then paused until you continue to run it. While the program is paused, you can check the value of variables and the state of the sprites. For more complex programs, this method is much faster than the step-by-step mode. You can add as many breakpoints as you like.
Debugging Example - Water Simulator
As an example, let’s discuss how to debug this project:
play.creaticode.com/projects/67166007037517b65095ef7d
- Step 1
First, let’s establish the difference between the expected and actual outcomes. When we press down the mouse pointer, we expect a new ball to be generated. However, we don’t see that when we try it.
Next, Let’s try to narrow down the issue. There are 2 sprites, “Ball” and “Bucket”. The bucket is visible and spinning as expected, so the issue must come from the “Ball” sprite.
There are 2 stacks of blocks in the Ball sprite. The first generates new clones of itself, and the second initializes each clone. Now it is less straightforward as to which stack contains the issue.
- Step 2Now let’s apply some logical reasoning. Since we are not seeing new balls, there can be 2 possible reasons:
- new balls are not generated
- new balls are generated, but somehow we can’t see them.
This is a good time to use the logging tool, since it can easily tell us whether new ball sprites are indeed being generated. For example, we can add the print block like this:
Now if we run the program again, and click the mouse button a few times, we will see these messages in the console panel at the bottom of the playground:
That means the new ball sprites are being generated, but somehow, we can’t see them.
- Step 3Now, let’s dig further. What are the most likely reasons why we don’t see the new clones? There are at least 3 possibilities:
- The clone is hidden
- The clone’s size is too small
- The clone’s position is outside the stage
If we add some more “print” blocks to print out the size and position of every new clone, we would be able to eliminate #2 and #3. Now, we just need to focus on why the new clone is hidden.
- Step 4At this point, we can simply re-examine the Ball sprite to check why the new clone is hidden. To make sure we don’t miss any details, we can use the “robber duck” technique. Let’s try to describe our program to a duck like this:
- When I click the green flag, I first initialize the physics engine with some gravity
- Then I broadcast the “add bucket” message, which will tell the Bucket sprite to add the bucket
- Then I hide this original Ball sprite, so no one sees it…
At this point, we would realize we are hiding the original Ball sprite at the beginning, so all its clones are hidden by default!
Therefore, the issue can be easily fixed by adding a “show” block when the clone is born: