Debugging is a useful skill in programming that will help you find bugs much quicker that could otherwise be hiding in your code.
Everyone writes bugs, even the best programmers. The best programmers know how to find them, so lets go over how to catch the most sneaky bugs in Roblox.
Lets assume you have some code
local function discombobulator(t)
t.cat = 5
if t.x == 10 then
t.x = 9
end
if t.x == 10 then
print("boop")
end
end
The bug should be obvious straight away, but imagine this bug is in a much bigger codebase, this could be hiding away and you’d never spot it, so lets give some ways of catching this
The stack trace
Before we go into methods of debugging, we should use the information Lua gives to us when an error occurs, this is known as the stack trace, it provides a useful call stack for finding the exact location of a bug, and what functions are calling the stack
This code:
local function c()
error("NOOOOOOO")
end
local function b()
c()
end
local function a()
b()
end
a()
… will give us this stack trace
Workspace.Script:2: NOOOOOOO - Script:2
Stack Begin
Script 'Workspace.Script', Line 2 - function c - Script:2
Script 'Workspace.Script', Line 6 - function b - Script:6
Script 'Workspace.Script', Line 10 - function a - Script:10
Script 'Workspace.Script', Line 13 - Script:13
Stack End
As you can see, the function c
is throwing the error, but we can also look into what functions were called that results in c
throwing the error. In this exact case, the c function itself is throwing the error regardless of what it gets, but lets change the code to this
local function b(t)
t.didDoSomething = true
end
local function a()
b {}
end
local function c()
b "cat"
end
a()
c()
The stack trace is now
Workspace.Script:2: attempt to index string with 'didDoSomething' - Script:2
Stack Begin
Script 'Workspace.Script', Line 2 - function b - Script:2
Script 'Workspace.Script', Line 10 - function c - Script:10
Script 'Workspace.Script', Line 14 - Script:14
Stack End
The stack trace will let us see that the error occured indirectly through the function c
, while the function a
worked as intended.
The caveman method
Using print
will allow you to catch 99% of bugs if you can output the info that you need at that exact location, Roblox pretty prints table, so if we add a print statement after the first if statement, we can spot that x
is somehow being set to 9
print(t) --> {x = 9}
The reason I call this the caveman method is because its very primitive, and there are a few bugs where this wont be that useful for those bugs in specific (ie coroutines or objects that are somehow being set to nil when it shouldn’t be)
The Roblox debugger
Roblox Studio ships with an insanely powerful debugger, you can quickly stop a script on a line by adding a breakpoint
You cant use breakpoints inside callbacks that cant yield. the Roblox documentation should already tell you what callbacks cant yield, for example:
DockWidget:BindToClose(function() print("Hello World") --> Cannot yield end)
You can do this by clicking a line of code:
If we now run the code, Roblox will stop the game and start the debugger, the debugger will now give us a few buttons to control how we step through this code
- Step Into: Perform the next line of code
- Step Over: Perform the next line of code, but do not look into the function being called
- Step Out: Escape the current function if inside one
But these buttons aren’t very powerful on their own since it just lets us step over code, if we cant get anything out of this, whats the point of it
This is where the Watch comes in (or you could just put print
s everywhere, lol)
WATCH TODO (the new watch window is still a little janky to me)
There’s a few other cool things you can do with breakpoints, by right-clicking it and pressing Edit Breakpoint;
Log Message
This outputs a message when the breakpoint is hit, this is the same as putting a print directly after the breakpoint
Conditional Breakpoint
The breakpoint will only stop if a condition is met, this is a Luau expression based on the current variables in scope, I have not found a good usecase for this, however I imagine it is;