Bug Testing and Fixing: My Approach
I figured I would take this post to talk about one of the most important parts of being a programmer: bug testing and bug fixing! I also wanna talk about how I go about fixing these problems. I don’t have many specific examples for this post so I’m just going to talk generic and high level stuff.
Imagine that someone is playing your game, using your tool, or you yourself are trying to use a previously implemented system and something just isn’t quite right. Either the game crashes, some mechanic doesn’t work quite how it was intended to or it doesn’t work at all. Alternatively, the tool doesn’t load something how you thought it was going to or the data in your system isn’t what you think it should be. This is how it starts; a bug in the code somewhere, somehow, caused an unintentional behaviour to appear. This is where hours of frustration and yelling happen because “It should work so why isn’t it working?!”.
The process to fixing these problems begins once you realize that believing something is programmed one way doesn’t mean that it’s actually programmed that way.
Sometimes it starts with a full on bug report and reproduction steps which is great! However, often times it just comes as x mechanic isn’t working properly, for example the character is moonwalking when he should’ve rotated, or the tool is clearing every level of data when you make a new one. These are where bugs start to be really “fun.”
The formal bug testing and fixing pipeline for me and in my opinion is this:
Identify how to reproduce the bug. If it’s inconsistent good luck making it consistent! Keep reproducing it.
Find out (high level) what is breaking (the walking mechanic isn’t turning the player)
Find where in the code something is breaking
Step through and actually find out what specific piece of data isn’t working
Fix it (this could mean a lot of things, like reworking systems or mechanics or changing a single number)
Try and reproduce it. If something new pops up you’ve got a new bug, go back to step 1. If the original bug is still happening you didn’t actually fix the bug, go back to step 3.
I’m gonna apply this process to a couple bugs that we found recently in our game.
The first bug
Running the game on Android. The Duck just stopped working. When not interacting with the duck it should follow the player. When interacting with the duck the player is supposed to be able to pick up, throw, and recall the duck. When launching the game on Android none of this worked.
Alright, now let’s apply the process:
The duck doesn’t work on Android.
Launch the game on Android (it’s already consistent, yay!)
All duck interactions aren’t working in the game.
This must be something to do with the duck script that handles the data and mechanics of the duck. There’s always an initial thought for a programmer as to what is contributing to something breaking. These initial thoughts usually get more accurate the more on works in the codebase. The initial thought for this particular problem was that data wasn’t being set properly on startup and was causing things in the duck scripts to not work properly.
So I start from the pathing and check if the data it is using is not assigned. Lo’ and behold it is not being assigned. Time to look where it’s being set and step through from there. Stepping through brings us to a function that controls animation erroring and throwing us out of the ducks scripts Start() function. Alright bugception why? Something in that function isn’t set either! What is it? It’s an animator component that is set in Start().
So I’ve identified the problem. Something in one of the functions in Start() is set to null when called. This other data (the animator) is also set in Start(). However, the duck is higher than the animator script in the hierarchy so the ducks Start() gets called first. Easiest way to solve this is to simply move the animator assignment to the Awake() function.
Test it again. And it works! Simple problems with simple solutions lead to happy programmers!
The second bug
The player or duck is on a button that opens a gate near them, they path away from the button through the gate. Instead of stopping at the gate cause it is now closed they then continue their merry way and path through the gate.
Now let’s apply the process again:
Players are sometimes pathing through gates when they’re closed.
Play around with the gates and find out that they are pathing through them when they are moving off of buttons and closing the gate they’re walking through find out it is an issue with the duck also.
The player is pathing through gates that are supposed to be unpassable when closed.
Two places to look here. The first one being where the player and duck process their paths and actually move along them. The second being the gate script that is supposed to change the tile from unpassable to passable and vice versa.
The first place I looked was the gate script. The data was being assigned properly when the gate was supposed to be open or closed. So then it’s the way they’re handling pathing. The path is being created and saved when the tap happens and later being used by the individual character scripts to walk along the path. This creates the bug of walking through gates because when a player taps the gate is still open and the path walkable but it closes once you move and the path isn’t updated.
There are two ways to fix this, call the pathing every time the character gets to a new tile to keep it up to date or check the tile the character is planning on pathing to. The first one will take too many resources and we’re on mobile so that’s a no go. Then all we have to do is add a check to the pathing functions to keep them from going through objects that were updated.
Trying to reproduce this bug fails and the bug is now fixed. Another relatively simple bug but one that was a little bit more elusive.
This way of debugging is in no way a defined way to go about it. This is the way that works best for me in most scenarios. There’s also the strategy of debugging while programming, trying to anticipate bugs that will happen and either fixing them there or putting a note that something might break. In one scenario it can lead to overengineering something in the other scenario it can lead to a spaghetti and broken codebase that is difficult to parse through. Debugging is probably one of the most important skills a programmer can have because no one makes perfect code and everyone should find their own way of going through these problems whether they’re simple or hard.