Programming Rules to write Safe Code

Programming Rules

Here are a list of programming rules which I think are good practices. These rules are designed to help you write code that is:

  • Memory Safe
  • Is readable, debug-gable, and can be understood by other developers
  • Avoid your game from breaking in production to ensure a smooth player experience.

Why you ask?
Well I believe that these practices will help players stay longer and be more accessible to a wide range of devices (this is more for stability so having well designed UI and Controls for different devices is important too).
This is going to give you more favourable data to the Roblox recommendation algorithm meaning your game in theory should be more successful.

1. Bound all loops

Ensure every loop has a predictable exit. Use counters or table limits—no infinite loops.

2. Limit function size

Keep functions short and focused—ideally under 60 lines. Split logic into helpers.

local persistence = {}

—Bad
local function savePlrData(player: Player)
—over 60 lines of code lots to read through to understand everything it is doing.
end

—Good
local function getPlrIdFromPlrInst(player: Player)
       local playerId = player.PlayerId 
       return playerId
end
local function getPlotFromPlrId(playerId: integer)
end
local function serializePlrPlot(playerId: integer)
      
end
function persistence.savePlrData(player: Player)
      local playerId = getPlrIdFromPlrInst(player)
      local plot = getPlotFromPlrId(playerId)
      local plotData = serializePlrPlot(plot)
      return success
end

return persistence

3. Use assertions and warnings

Use assert() or warn() to catch unexpected values or states early.

4. Minimize variable scope

Prefer local variables. Avoid polluting the global environment or using shared state unnecessarily.

5. Always handle return values

Check results from functions like pcall, FindFirstChild, or HttpService:RequestAsync.

6. Limit use of references and pointers

Avoid complex table references or circular structures unless absolutely necessary.

-- Bad
local table = {}
table.self = table  -- circular reference

7. Always sanitize input to prevent unexpected data

Validate all inputs to functions, methods, and remote events.
Enforce type checks, value ranges, and fallback defaults to guard against malformed or malicious data.

8. Manage event connections to avoid leaks

Track every :Connect return value, store it with the owning object, and disconnect in Destroying, AncestryChanged, or explicit Destroy() paths. Clear tables after disconnecting so long-lived services don’t retain dead UI or model references.
This will prevent memory leaks which can cause your game to crash or run badly. This is especially important if you want your game accessible to mobile platforms as a lot of devices have very low amounts of RAM to work with. Using the micro profiler can help find potential memory leaks. You can also open the dev console and select the ‘Memory’ tab. Search for: LuaHeap, Instances. If the value keeps climbing and does not go down it is likely you have a memory leak.

local buttonConnection = button.MouseButton1Click:Connect(function()
    print("Clicked")
end)

button.Destroying:Connect(function()
    buttonConnection:Disconnect()
end)

9. API‑accessible methods must be top level

Any function or method that is part of your module’s public API (i.e. intended to be called by other scripts, services, or developers) must be defined at the top level of the class or module, not inside another function or method. If you have a function or method which is publicly accessible tools like Moonwave will not be able to correctly document them. This also makes your code easier to read. This best works in practice with big teams with multiple programmers, though it is also useful for small teams or a solo dev as realistically once you have 50,000+ lines of code to maintain it becomes harder to keep track of what everything does.

10. Document every public interface

All modules, classes, and API‑accessible methods must include clear documentation comments describing their purpose, parameters, return values, and side effects.

11. Zero‑warning publishing policy

Before publishing your game, ensure that the Roblox Studio Output window and linter are completely free of errors and warnings. Treat warnings as bugs to fix not noise to ignore.

I will continue to improve this guide based on feedback over time and add more snippets of code when I have time.

Here are some of the rules I use. What would you add or change?

11 Likes

is there even a reason for this thread to exist? this is not a tutorial or informative set of guidelines, it’s just a vague, surface-level list of rules, completely unhelpful

7 Likes

Maybe not to you, and that is your opinion. This serves as a guide of potential rules which you can implement, they are based of the Power of Ten from NASA’s Gerard J. Holzmann. I have adapted them for Roblox Development using LuaU.

real ones use infinite loops whenever possible #rigged

6 Likes

Those set of rules were mainly for C programming. C is quite different to lua. Memory Safety isn’t really a topic in lua, only memory leaks are of concern. Pointers are also, not really a thing in luau (there is just the notion that some things are stored by value, and others by reference). The zero warning publishing policy is about compiler errors (which would be equivalent to warnings from the linter). Runtime warnings are much harder to track, as they can be caused by specific actions. Still a good idea to fix bugs instead of ignoring them

Also, these coding rules are mainly for safety-critical code, some of these things are probably not worth it when it comes to a game, for example, ensuring every loop is bounded is probably quite a good idea when you’d like to avoid your rocket’s cpu from freezing, but for a game, that is unnecessary

I think a good amount of these are good practice, but a little overboard. It would also be nice if you went more in depth, or included examples

8 Likes

As time goes on and I continue to revise these I will most likely update this thread with more detail as to what my reasoning is. You could argue that this is overkill but things like keeping memory usage low actually can help your game be more successful as the recommendation algorithm picks it up. Though I guess I could have included that in the original post.

arent asserts unoptimized as hell as i remember

The example in number 8 is utterly pointless.

Yes, you should remember to disconnect your events, but destroying an object will automatically disconnect all connections. The code example does literally nothing, except creating an additional connection for the duration the instance exists.

3 Likes

This is literally just NASA’s coding guidelines ported to ChatGPT and asked to make it in lua. I know because I did it and it gave the same trash ass it did here.

2 Likes

I love when posts are clearly AI-generated (nobody uses em dashes anymore, lmao) and they serve almost no purpose beyond being vague. These are things you’d learn in an introductory computer science course at the post-secondary level.

3 Likes

but i don’t like comments and i hate writing them
actually i almost never read comments i just read the code

1 Like

Well it makes sense when you need to onboard new programmers or have a large team, it will also help you with a large codebase. This does not apply to small codebases as much.

ive had multiple 5k line codebases and the only comments were unused code

1 Like

your code can NOT be that self-documenting :sob:

how about you dont tell me what to do

1 Like

it is tho, im the only one that can read it too

1 Like

Em dashes aren’t a great sign to go by, imo. Plenty of people, including myself, use em dashes.

1 Like

i use em dashes every day :pleading_face:

2 Likes

If this were the case, you would’ve enclosed “including myself” in em dashes, lol.

1 Like

maybe he didn’t want to emphasize “including myself” as much :person_shrugging: