Debugging 101 - "My code is not working! Help!"

Properly Debugging your code is the most important step for success.
Let me explain.

We have all found ourselves in the situation where our code does not work. It can either be a simple error we instantly see, or something we oversee quickly. Writing good code reduces the likely-hood of needing to debug your code.

I’ve seen a lot of people rushing to the DevForum and instantly asking for help when they could have solved the problem for themselves if they properly debug their code.

This topic is to help people debug their code properly and follow best case practices. If you have feedback on this topic, please leave them in the comments.

Step 1 - Identify the Error

If any, look at your error message! Please, never ignore your error message. Most of the time, the error tells you your mistake. Simple examples might be:

Syntax error: Check for missing or misplaced characters, such as parentheses, commas, or semicolons.
Undefined variable: Ensure that all variables are declared and initialized before use.
Attempt to index a nil value: Verify that the variable you’re trying to access is properly initialized and exists.
Mismatched types: Check if you are performing operations on incompatible data types.

Not to mention that error messages also tell you on which line it happened. So you can quickly check in the given line.

Another common issue that can arise during scripting is the improper use of Roblox classes or API. Developers may encounter errors when trying to use methods on a class that does not contain the method or forgetting to use a necessary method, resulting in unexpected behavior. Recognizing and addressing these issues is crucial for robust script functionality.

Error messages related to improper use of Roblox classes or API usually provide clear indications of the problem. An example might be the following:

“Invalid argument #1 to ‘XYZ’ ([typeA] expected, got [typeB])”:

  • Suggests that you are passing a typeA value to a method that expects the typeB value.
  • Verify that the arguments you are passing to the method match the expected data types and are not nil.

In this case, the Documentation - Roblox Creator Hub can greatly help you!
It provides you with all the necessary information you need about a method, class or type you are using. It even has an in-built AI Assistant that might help you!

Don’t underestimate the Documentation, it is a cheat sheet and can greatly help. When im coding with Roblox Lua, I always have it open on my second monitor!

Also, please use Roblox in-built Debugging Features!

It’s essential to make good use of the built-in debugging features in Roblox. These tools can help you find and fix issues in your code more easily. Roblox Studio provides useful features like breakpoints, variable inspection, and the Output window. These tools give you insights into how your code runs, help you check variable values, and identify potential errors. You can better understand your code, quickly catch and address issues, and ultimately improve the overall quality and reliability of your Roblox scripts.

Step 2 - No Error, but does not work as intended!

Even when there are no obvious mistakes, if your code doesn’t work as expected, there might be a logic problem. A logic problem happens when the code doesn’t follow the planned steps, even though it runs without errors. Let’s break it down:

Start by finding where things go off track. Figure out which part of your code isn’t doing what you meant it to. Once you spot it, take a moment to think. Personally, I find it helpful to picture the code’s steps in my mind, like making a ‘Logic Flow Chart.’ Imagine going through each line like you’re the program. During this thought process, you might notice, “This isn’t doing what I wanted.” That’s when you’ve likely found the logic problem.
Here’s a simple example:

function checkGrade(score)
    if score >= 90 then
        print("Grade: A")
    end

    if score >= 80 then
        print("Grade: B")
    end

    if score >= 70 then
        print("Grade: C")
    end
end

checkGrade(85)

Prints the following:

Grade: B
Grade: C

In this code, although it runs without errors, it doesn’t give the right answer. The issue is that each if statement works independently, letting two conditions be true at the same time. The corrected version uses elseif statements, making sure only one condition is checked:

function checkGrade(score)
    if score >= 90 then
        print("Grade: A")
    elseif score >= 80 then
        print("Grade: B")
    elseif score >= 70 then
        print("Grade: C")
    end
end

checkGrade(85)

With this fix, only one condition will be true based on the number, giving the correct answer:

Grade: B

Now, the code works as intended, fixing the logic problem.
But how did I notice? Using the ‘Logical Flow Chart’!

I imagined myself being the code, and I executed myself. and I quickly noticed that I ran all if statements, but I only wanted to run the one thats the first to come true. So I quickly fixed it by using elseif conditions.

Step 3 - It is still not working!

Alright, you’ve followed all the previous steps, and your code is still not working as intended. In such cases, it’s time to ensure that you’re adhering to best practices in your code. Let’s reinforce some fundamental principles:

1. Variable Usage:

Avoid arbitrary names for your variables. Instead, choose names that clearly convey their purpose. For instance, if you’re tracking a player’s experience, opt for “playerExperience” rather than “plrXp.”

Take it a step further by specifying the variable type. For example, use local playerExperience: number = 0. This not only clarifies the variable’s role but also informs you of its type throughout your code.
Roblox Studio will also provide type hints when using the variable.

Avoid using different variables for the same purpose. For example, do not store the player’s experience in two different variables. You might end up using the first variable when you intended to use the second one. We want to prevent that, so stick to using one variable for one specific purpose.

2. Remember the type you are working with:

Be aware of the data type assigned to your variables. If you say a variable is a number, keep that choice consistent across your code.

Mixing up data types can lead to surprises and errors. This becomes especially crucial when dealing with ValueInstances.

For example, suppose you’re handling a value that represents a player’s name. It’s important to consistently treat it as a string. Trying to perform math operations on a string can lead to unexpected issues.

Personally, I find it helpful to always document the type I’m working with. This way, I avoid accidentally using a different type for a variable, which can cause puzzling problems.

This practice not only prevents errors but also saves time that might otherwise be spent troubleshooting unexpected issues in your code.

3. Optimizing Performance - Never let timing be your best friend!

I often come across code where developers may not be aware of specific events for a given type. Consequently, they resort to using loop conditions, which not only leads to performance inefficiencies but also relies heavily on timing. If the condition that the loop is checking becomes true, and the loop misses it during a check, the loop will persistently run, impacting performance. Let me illustrate this with an example:

local winner = BoolValue.Value

while not winner do
	task.wait(10)
	-- check if winner
end

In this example, the loop waits for 10 seconds before checking if the condition winner is true. Relying on a timed loop can be inefficient and error-prone. If the condition becomes true just after the loop check and before the next wait period, the loop may continue running unnecessarily, consuming resources and potentially impacting performance.

A more efficient approach would be to utilize events, which provide a way for your code to react immediately when a specific condition is met. Let’s consider an improved version:

local winner = BoolValue.Value
local winnerEvent = Instance.new("BindableEvent")

local function checkWinner()
    	if winner then
        	winnerEvent:Fire()
    	end
end

winnerEvent.Event:Connect(function()
    	-- Handle winner condition
end)

while not winner do
    	winnerEvent.Event:Wait()
end

In this revised example, we create a BindableEvent named winnerEvent. The checkWinner function checks the winner condition, and if true, it fires the winnerEvent. The loop then waits for the event to fire, immediately responding to the condition change without relying on timed intervals. This approach is more responsive, efficient, and less prone to timing-related issues. Remember, events are powerful tools for optimizing code performance and responsiveness.

Now, you might be thinking, “Well, even if I waited just 9 seconds longer, the condition turns true anyway.” That’s true; waiting longer will eventually break the loop. However, what if we have a situation where the loop misses a change entirely? Let’s say we want to check if a variable is true, and it becomes false before the next check, the loop never breaks! The condition was met but the loop missed it.

Step 4 - Debugging Strategies

Alright! Now that you’re aware of common pitfalls, let’s dive into debugging strategies. When it comes to debugging, two primary approaches stand out: using breakpoints or incorporating print statements into your code.

Print statements are a straightforward yet powerful tool in debugging. By strategically placing print statements at different points in your code, you gain visibility into the execution flow. This helps you understand where the code might be halting or not behaving as expected. For instance, you can print out the values of variables, check if certain conditions are met, or simply track the progression of your program.

Here is an example code:

print("Entering Function A")
-- Some code for Function A
print("Exiting Function A")

-- Print variable values
local playerScore = 100
print("Player Score: ", playerScore)

-- Check conditions
if playerScore > 50 then
    print("Player has a high score!")
end

Using the print statements we now gained the following insights:

  • When FunctionA started and ended
  • What the playerScore is
  • If the playerScore is higher than 50

Breakpoints are another effective strategy, especially when dealing with more complex code or stepping through functions. Placing breakpoints allows you to pause the execution of your code at specific lines. This enables you to inspect variable values, step through code line by line, and identify where the issue might be occurring.

  1. Set a breakpoint by clicking on the line number in Roblox Studio.
  2. Run your script in Debug mode.
  3. When the execution reaches the breakpoint, the code will pause, allowing you to examine variables and assess the program’s state.

Combining print statements and breakpoints offers a comprehensive approach to debugging. Print statements provide continuous insights, while breakpoints allow for a more detailed examination of your code’s behavior. Experiment with both strategies to find the balance that works best for your debugging needs.

Please, check your code every now and then!

During development, it’s essential not to just write your entire code without testing it in-between. Break down your tasks, create functions, and debug each function to ensure the code is functioning correctly. This prevents unnecessary debugging in the end. Identifying issues becomes instantaneous because you know when the error originated. If not, it can quickly become chaotic, especially if functions or code snippets depend on each other. Everything might fail, and you won’t know where to begin or what to look for.

Step 5 - Asking AI or going to the DevForum

When you revised your code properly and still can’t figure out what the issue is, trying asking an AI Model like ChatGPT, and if that does not work, ask the DevForum.

Note: ChatGPT should not be used for programming and only as an assistant in helping to figure out where the problem might arise, ChatGPT can use deprecated functions/methods and may provide inaccurate results!

Conclusion:

This topic mainly focuses on how to debug your code and avoiding common pitfalls. Following good practices can help fix your code. You don’t need to rush to the DevForum instantly, always try fixing it for yourself before! Better trying than giving up instantly.

Also, please remember that there’s plenty of issues that can arise, in which I can’t cover them all in this post.

Happy debugging!

This is my first large post, any feedback is appreciated.

15 Likes

Thank you very informative!

Not the hero we deserved, but the hero we needed.

P.S. it might be interesting to introduce these features to the reader, instead of telling them to outright use it. They probably don’t know how! (type-checking, for one.).