Tips & Tricks 1: Debugging with print() Statements
Posted on August 27, 2017 by adark
A lot of times when writing code you will have something that doesn't error but doesn't work either. These, very simply, are bugs in your code.
TL;DR: When your code isn't working but you don't know why, add print()
statements to every unique code block and figure out exactly which lines of code are and, more importantly, are not running! Use these print()
s to check what your variables are as well.
This article will be about how to use print()
as your most basic tool for "debugging" your code!
Bugs in Code
Most of the time, bugs happen because your code's "logic" is incorrect or inconsistent. Code "logic", or the "flow" of a program, determines what specific statements run and which do not.
The most basic "logical operation", typically called a control statement is the if
statement:
if someExpression then print("it's true!") elseif someOtherExpression then print("It wasn't but something else was!") else print("neither were true!") end
Based on the values of someExpression
and someOtherExpression
, only one of those three print()
statements will run, along with any other code in those blocks.
Introducing print()
Where debugging with print()
comes in is when the logic isn't working like you think it should be. We all write code that we think works, and something proves us wrong, so we go back and change it. Figuring out what to change is an important and sometimes difficult process.
Take, for example, this if
statement which is part of checking if a move is valid in chess:
-- to is the square the piece is moving to -- from is the square the piece is moving from if from.HasPiece and to.HasPiece and from.Piece.Color ~= to.Piece.Color then -- capture piece elseif from.Piece.Color == to.Piece.Color then -- invalid move, throw an error return else -- move piece to open square end
A savvy programmer will notice a few bugs in that code without needing to write any debug print()
s, but can you?
While testing the chess game, you notice that you can't actually move pieces to open spaces! Instead, you get an error: attempt to index 'Piece', a nil value
, on line 5! If you go a little further, you might also notice that you get the same error if you try to move a piece from an empty tile, no matter what the target tile is!
Line 5, elseif from.Piece.Color == to.Piece.Color then
shouldn't be erroring, you think. But it is, and you know that either from.Piece
or to.Piece
don't exist when this line runs in those two known conditions. Again, a savvy programming would notice immediately what the problem is, but let's assume (or for some of you this might still be true!) you don't know what's going on.
Rather than make random edits to that line to try and get the code working, we first need to figure out what is working. One of the most useful ways to do this is print()
statements holding some debug info.
Specifically, we should start by adding them to every unique code block:
print("code is running") print("from.HasPiece", from.HasPiece) print("to.HasPiece", to.HasPiece) if from.HasPiece and to.HasPiece and from.Piece.Color ~= to.Piece.Color then -- capture piece print("Capturing piece" .. to.Piece.Name) elseif from.Piece.Color == to.Piece.Color then print("Invalid move!") -- invalid move, throw an error return else print("Moving piece") -- move piece to open square end
The point of this is two-fold: following the computer as it executes the code, and checking the values of our variables at various points in the code.
For something this simple, all we do is validate what we know: Capturing piece
prints correctly, Invalid move!
prints when trying to move pieces of the same color on top of each other, but the same line errors, the elseif
, when trying to move a piece.
However, we also see the values of from.HasPiece
and to.HasPiece
! The line only errors when one or both of them is false
!
Since we know Capturing works fine, we can remove that print()
entirely. However, we will leave the other two. Moving piece
because it does not fire at all, the variable prints because our error only happens based on their values and Invalid move!
because, for some reason, it is the branch that is erroring.
This process of removing the prints as you verify what code is working until you get down to only a few that don't work correctly should be after putting them in in the first place. If you don't have enough prints, you won't be able to see what the code is doing and will be no closer to fixing the problem. There's no fee for adding or removing them, so use what looks like too many to start with and remove them as you narrow down the bug!
Fixing the Bugs
Look closely at the line that is erroring:
elseif from.Piece.Color == to.Piece.Color then
Using the print()
s we added, we know that this only errors when HasPiece
is false, meaning that Piece
is nil
for that square (either to
or from
).
And this makes perfect sense! The very first thing we try to do is check a property of Piece
! Additionally, when we're trying to move a piece, it's actually to.Piece
erroring, for the same reason!
You may have written the code thinking that this elseif
wouldn't run if the original if
statement found that the pieces didn't exist, because we do check for it there, or you may simply have forgotten to check. As you can see, assumptions aren't always correct!
By adding those two checks to the elseif
expression, all of the prints are now working!
print("from.HasPiece", from.HasPiece) print("to.HasPiece", to.HasPiece) if from.HasPiece and to.HasPiece and from.Piece.Color ~= to.Piece.Color then -- capture piece elseif from.HasPiece and to.HasPiece and from.Piece.Color == to.Piece.Color then print("Invalid move!") -- invalid move, throw an error return else print("Moving piece") -- move piece to open square end
However, they're not correct! Moving piece
will print (and potentially a line in that block will error!) when from.HasPiece
is false, even if a piece is present at to
!
Following the same process as before, we simply need to check that from
has a piece and to
doesn't:
print("from.HasPiece", from.HasPiece) print("to.HasPiece", to.HasPiece) if from.HasPiece and to.HasPiece and from.Piece.Color ~= to.Piece.Color then -- capture piece elseif from.HasPiece and to.HasPiece and from.Piece.Color == to.Piece.Color then -- invalid move, throw an error return elseif from.HasPiece and not to.HasPiece then print("Moving piece") -- move piece to open square end
Again, this introduces a new bug! If the code after this if
statement relied on the fact that the move being made was, in fact, valid, if from
doesn't have a piece, this will still error!
This can, of course, can be fixed by adding back an else
branch that is identical to the first elseif
, or we can check in the first elseif
if from.HasPiece
is false:
elseif not from.HasPiece or (from.HasPiece and to.HasPiece and from.Piece.Color == to.Piece.Color) then
Either way works fine, but having them separate allows you to have different "errors" to tell the client without any additional if
statements.
Now that we know all the logic works, we can reorganize it some using nested if
statements to avoid repeating from.HasPiece
and to.HasPiece
so much!
if from.HasPiece then if to.HasPiece then if from.Piece.Color == to.Piece.Color then -- invalid move, throw an error return else --if from.Piece.Color ~= to.Piece.Color -- capturing piece end else --if not to.HasPiece -- move piece to open square end else --if not from.HasPiece -- invalid move, throw an error return end
While this does take up more lines of code, in my opinion this better because it is easier to understand the "flow" of the code when the logic is written out in a nested fashion like this, as it more closely represents my thought process when I am checking if a move is valid in a real game of chess.
TL;DR
- Add more
print()
s than you think you need, to every unique code block. print()
the values of variables used in your controlling logic.- When you know a section of code is working, remove the
print()
s for that section. - You're done when you have no
print()
s left to remove!
About the Author
Valentine Albee - ROBLOX Developer and Professional Programmer
- Discord: FuriousProgrammer#0842
- Twitter: @FuriousProgramm
- ROBLOX: adark
Feel free to shoot me a message on Discord!
Ideas and questions are always welcome. :)
Commentary
Leave a Comment