Rules that I Follow for my Lua/u Programming!

About a year and a half ago, I shared a post discussing how I structure my scripts that I write. Anyone who has explored the world of programming outside of just Roblox has probably been exposed to the numerous programming languages that exist for varying use-cases, also accompanied by the equally numerous rules and practices to follow for each. However, there exists some programming rules/practices that are generally accepted to be language-agnostic. A common example is the avoidance of “magic numbers”, unnamed numerical values in code whose purpose isn’t immediately clear.

Unfortunately, while there may be some generally accepted rules, there exists far more that programmers will endless debate about; tabs vs. spaces, variable casing styles, and more. Over time, every programmer forms their own set of rules and practices to follow (for better or worse). After my few years of experience programming, I’ve developed a collection of personal rules that guide how I write and organize my code.

A quick disclaimer: these rules are not intended to be perfect or universally “correct”. In fact, several of these rules are highly contradictory to practices known to be “idiomatic” in Lua/u. Some common programming concepts and practices are made heavily complicated to implement while following these rules, some just simply disallowed. I maintain that these rules have reasoning behind them based on my experience and personal opinions about varying programming concepts. However, due to the fact that however many reasons I could have, there exists far more reasons to argue against my rules. Therefore, I have tried to keep my justification and reasoning for these rules to a minimum, but am more than happy to engage in discussion about it. I recognize that these rules are not meant for everyone, and some might say that these rules could even be damaging to newer programmers coming to the platform because it might incentivize bad habits. I maintain that these rules have helped me improve my consistency across my work and ultimately produce more maintainable and better structured code.

I am very curious to see how others might view these rules and welcome any discussion in agreement or disagreement.

For some background, I’m currently in college studying computer science and computer engineering and consider myself an intermediate programmer in the context of Roblox game development. Although I technically have been on the platform programming/scripting for about 4 years, it would be dishonest to say that I have the equivalent of 4 years of experience within Roblox programming.

Luau Coding Standards & Practices

1. Script Templates

All scripts must adhere to the following templates to ensure a consistent and organized structure.

1.1 Module Script Template

Template
--!strict

--[[
	@module Module Name
	@description Module Description.
]]--



--	========
--	SERVICES
--	========



--	==============
--	MODULE IMPORTS
--	==============



--	=================
--	MODULE DEFINITION
--	=================

local Module = {}

--	==============
--	VARIABLE TYPES
--	==============



--	=================
--	PRIVATE VARIABLES
--	=================



--	================
--	PUBLIC VARIABLES
--	================



--	================================
--	PRIVATE FUNCTION INITIALIZATIONS
--	================================



--	================
--	PUBLIC FUNCTIONS
--	================



--	============================
--	PRIVATE FUNCTION DEFINITIONS
--	============================



--	=============
--	MODULE EXPORT
--	=============

return Module

1.2 LocalScript / Script Template

Template
--!strict

--[[
	@script Script
	@description Description of the Script.
]]--



--	========
--	SERVICES
--	========

--	==============
--	MODULE IMPORTS
--	==============

--	==============
--	VARIABLE TYPES
--	==============


--	=========
--  VARIABLES
--	=========


--	========================
--	FUNCTION INITIALIZATIONS
--	========================

--	====================
--	FUNCTION DEFINITIONS
--	====================

--[[
	@function main

	@description Description of main function.
]]
function main() : ()

end

--	================
--	SCRIPT EXECUTION
--	================
main()

2. Naming Conventions

Identifier Type Case Example
Module Names / File Names PascalCase DataManager
Roblox Instances PascalCase MainGui, ReplicatedStorage
Custom Type Names PascalCase type PlayerData = {}
Constant Variables MACRO_CASE MAX_HEALTH
Non-Constant Variables snake_case current_health
Function Parameters snake_case function on_player_added(player)
Module Public Variables PascalCase Module.IsReady
Module Private Functions snake_case calculate_damage()
Module Public Functions PascalCase Module.GetCharacter()

3. Type Checking & Variables

3.1 Strict Type Declaration

All variables must have their types explicitly specified. The only exception is for variables serving as a reference to an Instance present in the Explorer for the sake of indexing children without type-errors.

3.2 Advanced Type Syntax

  • All tables must be strictly typed.
  • The any type is disallowed except for scenarios where functionality demands it.
  • Union types (|) and intersections (&) are permitted but must be used sparingly and with immediately apparent justification for their necessity present in their use case/implementation.

3.3 Naming Conventions

Variables that reference constant instances in the Explorer will default to the PascalCase instead of MACRO_CASE regardless of if the reference or value categorizes the variable as constant.

3.4 Constant Variables

  • Constants must be declared in either the PUBLIC VARIABLES or PRIVATE VARIABLES sections.
  • Within a variable declaration section, all constants must be defined before any non-constant variables.
  • Constants cannot be declared locally within a function.

3.5 Variable Declaration & Grouping

  • Variables should be organized into logical groups based on their use case.
  • Each logical group must be separated by a single blank line.
  • Due to the subjective nature of variable categorization for use case, the general guideline to follow is that relation of their use cases should be immediately and intuitively relevant.

4. Function Definitions

4.1 General Rules

  • Syntax Enforcement: The only permitted syntax for defining a function is assigning it to a variable. All other forms, such as function Module.MyFunction() end, are disallowed. The only exception to this is the main function to be defined in Local/Server Scripts.
  • No Anonymous Functions: Anonymous functions are disallowed. All functions, including callbacks for event connections, must be declared as named local functions following the intialize-then-define pattern.
  • Mandatory Return Types: All functions must explicitly define their retype type. For functions that do not return a value, the empty tuple () must be used to indicate intention behind the lack of an explicit return value.
  • Method Definition: Functions that operate on self must be defined using dot-syntax and include self as the first parameter with its type explicitly defined. The exception to this rule is the use of : to call functions for Roblox defined methods, generally methods belonging to Roblox defined classes/objects.

4.2 Public Module Definitions

Public functions are defined as key-value pairs directly on the Module table within the PUBLIC FUNCTION DEFINITIONS section.

4.3 Private & Local Functions

The initialize-then-define pattern is mandatory for all local functions.


5. Error Handling

  • All errors must be directly handled and processed in the form of strict guard clauses.

6. Code Style & Readability

(This is arguably the most controversial section after functions)

6.1 Line-by-Line Simplicity

Logic must be broken into the simplest possible steps. A single line of code must not perform more than one operation, which primarily disallows chaining accessors. An exception is to be made for navigation to a location in the Explorer Hierarchy.

6.2 Constructors

Calling a constructor (e.g., Vector3.new()) is an exception to the “one operation” rule. However, a line containing a constructor call must only be a variable assignment.

6.3 Variable Naming Philosophy

  • No Abbreviations: Variable names must be fully descriptive and self-documenting.
  • Semantic Duplication: If a variable’s role changes, a new variable must be created for that context to make the code’s intent intuitively logical. The need to search for context to understand a variable indicates failure in naming.

6.4 Table Formatting

  • Tables must be defined in a multi-line format, with each element on a new, indented line and a trailing comma on the final element.
  • Exception: Single-line table definitions are permitted only for inline, single-use tables, such as in a setmetatable call.

6.5 Handling Edge Cases

Edge cases that can be solved with a single line guard clause should be. If the option to return early is possible, then it should be taken to avoid unnecessary code being ran.

6.6 Disallowing Magic Numbers

“Magic numbers” are disallowed and must be defined as named constants or variables. The only exception is for values intuitively understandable within their literal value such as checking for a 0, nil, or boolean value. Additionally, the use of select number values to use in narrow tolerance checks for floating-point comparisons is allowed.


7. Script Dependencies

7.1 Services

All necessary Roblox services must be declared in the SERVICES section, retrieved using game:GetService(), and alphabetized.

7.2 Modules

  • All module dependencies must be declared in the MODULE IMPORTS section using require(). Their organization should be grouped together by use-case and relevance similar to variables, including a separation from other use-case module groups by a blank line. Unlike variables, modules within their group of related use-case modules should be alphabetized.
  • Module paths must be absolute from a root service (e.g., ReplicatedStorage). Relative paths should not be used for module imports.

8. Metatable Standards

  • Metatables are permitted but must be used sparingly to avoid creating overly complex data structures. (There is beauty and elegance in simplicity)
  • The __index filed must be assigned inline within the setmetatable call to prevent it from being an accessible key on the base table.

(Rules concerning metatables are subject to discretion as I personally try to avoid using metatables as much as I can. I also acknowledge that the use of metatables can quickly become highly advanced and rules regarding their use-case and implementation requires nuance.)

12 Likes

I like camelCase for variables and functions

1 Like

And I_don_t_because_I_don_t_like_dense_source.

Do you have some code example of how your scripts look when you use these rules? It would be interesting to see

Some of these rules I find kind of crazy, others I feel will make the code needlessly longer (longer in the sense of more lines of code)

So all your code uses the local func = function() end syntax? I like the usual syntax because syntax highlighting, but that syntax has the advantage of clearly showing that functions are also normal variables

What is the reason for this one? Anonymous functions for connections is so useful. I tend to like to condense code, if doing so helps with readability (ie common pattern, or simple code)
Does this also include assigning a function to some table index (either in an array or dictionary)? like

local t = {
    function() return 1 end,
    function() return 2 end,
}

Which technically is an anonymous function, even though it kinda has a name (the index)

To make code more readable, I somethings compress simple code. The idea is kind of to reduce the mental load, by reducing the number of lines by putting multiple operations on the same line if said operations aren’t important, and very easy to follow. Then, the reader can ignore that part of the code, and focus on what is actually important. For example, if I have to set some properties of objects that isn’t really important, I sometimes put it on a single line

local AlignPosition = Instance.new("AlignPosition"); AlignPosition.MaxForce = Vector3.one*1000; ---[...]

This one seems crazy to me. If you have a system, for example, a chat system that has a main module, and submodules, then I don’t see a reason for the main module to use an absolute path when requiring the submodules. If you use an absolute path, and move the main module into a folder for organization purposes, then all the requires for the submodules are broken


Overall, way stricter coding style than I use, which is fine.
Over time, I’ve come up with ways to structure my code, but I don’t follow them as like hard rules

As for style, structure of comments has been personal preference, hierarchy of like, services, requires, constants, etc, has also been just “by feel”. I have ended up with a fairly consistent style, but it’s not something I give particular importance to

There are some things I’ve been doing “by feel” that I think are interesting. Well mainly with comments, I do

-- // For Sections // --

-- // for more important comments or sub sections?

-- for less important comments

Why //? because I think it looks nice lol

and this comment that litterally has nothing

--

for just giving better separation to some code within a function or whatever, when I see fit

Here is an example of a module that’s part of a character-state/movement system I am writing (as it is WIP, it may have some errors).

Example Module
--!strict

--[[
	@module Controller
	@description Core Controller module for the Character State Machine.
]]--



--	========
--	SERVICES
--	========

local ReplicatedStorage = game:GetService("ReplicatedStorage")

--	==============
--	MODULE IMPORTS
--	==============

-- Packages
local Signal = require(ReplicatedStorage:WaitForChild("Shared"):WaitForChild("Packages"):WaitForChild("Signal"):WaitForChild("Main"))

-- State Machine Modules
local StateMachineTypes = require(ReplicatedStorage:WaitForChild("Client"):WaitForChild("Modules"):WaitForChild("Character"):WaitForChild("StateMachine"):WaitForChild("Types"))

--	=================
--	MODULE DEFINITION
--	=================

local Controller = {}

--	==============
--	VARIABLE TYPES
--	==============

type CharacterState = StateMachineTypes.CharacterState

type Channel = StateMachineTypes.Channel
type ChannelState = StateMachineTypes.ChannelState
type SuperState = StateMachineTypes.SuperState
type SubState = StateMachineTypes.SubState

type MovementSubState = StateMachineTypes.MovementSubState
type ActionSubState = StateMachineTypes.ActionSubState
type StatusSubState = StateMachineTypes.StatusSubState

type StateInterface = {
	Name: SubState,
	SuperState: SuperState,
	RequestTransition: (current_character_state: CharacterState) -> (boolean),
	Enter: () -> (),
	Exit: () -> (),
	Update: (delta_time: number) -> (),
	TransitionCheck: (current_character_state: CharacterState) -> (SubState?),
}

--	=================
--	PRIVATE VARIABLES
--	=================

local MOVEMENT_SUPER_STATES = ReplicatedStorage:WaitForChild("Client"):WaitForChild("Modules"):WaitForChild("Character"):WaitForChild("StateMachine"):WaitForChild("States"):WaitForChild("Movement")

--[[
	MOVEMENT_SUPER_STATES
]]
	
local GROUNDED_SUPER_STATES = MOVEMENT_SUPER_STATES:WaitForChild("Grounded")
local AIRBORNE_SUPER_STATES = MOVEMENT_SUPER_STATES:WaitForChild("Airborne")
local MANEUVER_SUPER_STATES = MOVEMENT_SUPER_STATES:WaitForChild("Maneuver")
local CLIMBING_SUPER_STATES = MOVEMENT_SUPER_STATES:WaitForChild("Climbing")

--[[
	ACTION_SUPER_STATES
]]
local ACTION_SUPER_STATES = ReplicatedStorage:WaitForChild("Client"):WaitForChild("Modules"):WaitForChild("Character"):WaitForChild("StateMachine"):WaitForChild("States"):WaitForChild("Action")

--[[
	STATUS_SUPER_STATES
]]
local STATUS_SUPER_STATES = ReplicatedStorage:WaitForChild("Client"):WaitForChild("Modules"):WaitForChild("Character"):WaitForChild("StateMachine"):WaitForChild("States"):WaitForChild("Status")

--[[
	STATUS_SUPER_STATES
]]

local STATES_REGISTRY: { [string]: StateInterface } = {
	
	--[[
		MOVEMENT STATES
	]]

	-- GOUNDED
	Crouching = require(GROUNDED_SUPER_STATES:WaitForChild("Crouching")),
	CrouchWalking = require(GROUNDED_SUPER_STATES:WaitForChild("CrouchWalking")),
	Idle = require(GROUNDED_SUPER_STATES:WaitForChild("Idle")),
	Sprinting = require(GROUNDED_SUPER_STATES:WaitForChild("Sprinting")),
	Walking = require(GROUNDED_SUPER_STATES:WaitForChild("Walking")),

	-- AIRBORNE
	Falling = require(AIRBORNE_SUPER_STATES:WaitForChild("Falling")),
	Jumping = require(AIRBORNE_SUPER_STATES:WaitForChild("Jumping")),

	-- MANEUVER
	Dodging = require(MANEUVER_SUPER_STATES:WaitForChild("Dodging")),
	GroundSliding = require(MANEUVER_SUPER_STATES:WaitForChild("GroundSliding")),

	-- CLIMBING

	
	--[[
		ACTION STATES
	]]


	--[[
		STATUS STATES
	]]
}

local current_character_state: CharacterState = {
	Movement = {
		SuperState = "Grounded",
		SubState = "Idle"
	},
	Action = {
		SuperState = "Idle",
		SubState = "None"
	},
	Status = {
		SuperState = "Idle",
		SubState = "None"
	}
}

--	================
--	PUBLIC VARIABLES
--	================

Controller.StateChanged = Signal() :: Signal.Signal<Channel, string?, string?>

--	================================
--	PRIVATE FUNCTION INITIALIZATIONS
--	================================

local transition_to_state: (channel: Channel, current_state_name: string?, new_state_name: string) -> ()

--	================
--	PUBLIC FUNCTIONS
--	================

Controller.IsSuperState = function(channel: Channel, super_state_name: SuperState): boolean

	local current_state: ChannelState = current_character_state[channel]
	if not current_state then return false end

	return current_state.SuperState == super_state_name

end

Controller.IsSubState = function(channel: Channel, state_name: SubState): boolean

	local current_state: ChannelState = current_character_state[channel]
	if not current_state then return false end

	return current_state.SubState == state_name

end

Controller.RequestStateChange = function(channel: Channel, new_state_name: SubState) : ()
	
	local current_state: ChannelState = current_character_state[channel]
	local current_state_name: string? = if current_state then current_state.SubState else nil

	local new_state: StateInterface? = STATES_REGISTRY[new_state_name]
	if not new_state then return warn("State '" .. new_state_name .. "' does not exist in the STATES_REGISTRY.") end

	if new_state.RequestTransition(current_character_state) then transition_to_state(channel, current_state_name, new_state_name) end

end

Controller.Heartbeat = function(delta_time: number) : ()

	for channel: Channel, channel_state: ChannelState in current_character_state do

		local state_interface: StateInterface? = STATES_REGISTRY[channel_state.SubState]
		if not state_interface then
			warn(`State ${channel_state.SubState} does not exist in the STATES_REGISTRY.`)
			continue
		end

		state_interface.Update(delta_time)

		local next_state_name: SubState? = state_interface.TransitionCheck(current_character_state)
		if next_state_name then Controller.RequestStateChange(channel, next_state_name) end

	end

end

--	============================
--	PRIVATE FUNCTION DEFINITIONS
--	============================

transition_to_state = function(channel: Channel, current_state_name: string?, new_state_name: string) : ()
	
	if current_state_name == new_state_name then return end
	
	local current_state: ChannelState = current_character_state[channel]
	if current_state and STATES_REGISTRY[current_state.SubState] then STATES_REGISTRY[current_state.SubState].Exit() end

	local next_state: StateInterface? = STATES_REGISTRY[new_state_name]
	if not next_state then return warn("State '" .. new_state_name .. "' does not exist in the STATES_REGISTRY.") end

	current_character_state[channel] = {
		SuperState = next_state.SuperState,
		SubState = next_state.Name
	}

	next_state.Enter()

	Controller.StateChanged:Fire(channel, current_state_name, next_state.Name)

end

--	=============
--	MODULE EXPORT
--	=============

return Controller

Yes, I agree. Many of the rules are quite “crazy” by most common or “idiomatic” Lua practices, especially amongst the Roblox community from what I’ve seen. One of the key philosophies that I try to maintain in my programming (language agnostic), is that verbose simplicity is better than convenient complexity.

It is a concept that was reinforced when I explored the Go (Golang) programming language through one of Jon Bodner’s books, specifically about idiomatic Go. I could go on about what that entails, but in short, due to the fact that Go is a simple language, it’s simplicity is not a shortcut when it comes to robust programming, but instead enables you to write more verbose and robust code while maintaining simplicity. For example, Go’s error handling is notorious to be a love it or hate it amongst those who use it do to the forced explicit error handling.

This general philosophy guides a lot of the rules in that if a singular more complicated or convoluted action can be done in a few more but individually simpler steps, it is generally better code. I will say that how this applies to specifically to each rule does differ due to the nuance of its application.

Yes and no. Specifically in the FUNCTION INITIALIZATIONS sections, I write local func: (a: type, b: type) -> (type) and then define it later in the FUNCTION DEFINITIONS sections func = function(a: type, b: type) : (c) end. This is for three reasons, primarily.

  • First being for function, initializing my functions with their function headers/signatures allows me to be able to reference them freely in any function’s definition regardless of the order of definition.
  • The second is more just comfort in that I enjoy in languages where it is common practice to declare the function headers at the top to initialize the functions and then later be defined. Normally the act of calling a function “earlier” than a function is defined in an interpreted language is not okay unless the language has a feature for this, but my implementation of it makes it so that there’s no way for that to occur because all code is only ever ran by calling a function, in which all functions have to have been defined.
  • The third reason is an opinion, as you pointed out, that it is better to visualize functions in Lua for what they are, being variables containing a reference to a function object (the exact technical accuracy of that statement might be not 100% but you get the idea).

This is probably one of the more critical choices I choose to make. I do agree that anonymous functions are extremely powerful and convenient. My stance on anonymous functions is probably more philosophical than logical in that I believe most use-cases where anonymous functions are not generally necessary unless you are attempting to implement quite a complex or convoluted solution. One of the philosophical differences I have with many developers, is that functions should be treated as variables when it comes to maintaining readability.

Proper use of functions can affect readability of code just as much if not more than variables can. A properly named variable can make a night or day difference for readability of code, such is the rule of no magic-numbers. The same can be applied to functions. Most people will agree that a marker of experienced programming is knowing how to properly segment code into multiple functions. Properly segmented functions with proper names can heavily increase quality and readability of code. Just as poorly segmented functions with improper names can heavily decrease quality and readability of code.

An anonymous function by default ignores the value of properly naming a function. This can already be an issue in that it is no longer intuitive to find the meaning in an anonymous function. You can say that in many cases it is intuitive if it is an anonymous function for the sake of a callback function, while this may be true in some cases, even most, it is not always. However, anonymous functions are incredibly important for some programming concepts in Lua, object oriented programming, functional programming, and others. In regards to these concepts, I believe that these situations are already too convoluted and complex and a simpler solution can be found. This may come off as a sign of inexperience to some, but I believe if you have to dramatically increase the complexity of simple actions for the sake of pursuing a paradigm, then maybe it should shift.

In regards to the point of functions in an index, I would not consider these anonymous functions as there exists and call-able/index-able reference to the function. I would see an array of functions similar to a module containing a function where a module script is generally akin to a key-value pair with a string indexing a function.

I would say in the context of what you put, the semi-colon indicates functionally a new line. Technically the semi-colon is also functional so I will say that it is a bit of a semantic slippery slope here, but generally I mean it that in a line of instruction, there is no more than 1 operator not including the equals operator. An example of code that is unacceptable to the rule would be local target_position: Vector3 = humanoid_root_part.Position + humanoid_root_part.CFrame.LookVector. Instead, according to rules I follow, I would write it as:

local root_position: Vector3 = humanoid_root_part.Position
local root_cframe: CFrame = humanoid_root_part.CFrame
local look_vector: Vector3 = root_cframe.LookVector
local target_position: Vector3 = root_position + look_vector

There might be more lines to read, but objectively it is line-by-line simpler. When reading dense segments of code, I would rather read larger amounts of incredibly simple to understand code than have to visualize multiple operations, even if such operations are as simple as just multiple accessors. Additionally, I also believe that removing the ability for successive accessors allows for more robust error handling, ex: I nearly never run into index nil errors.

This is a great point, and I would agree that if you are programming a module or a system to be easily exportable, for example, as a “package”, then the rule does not make sense. However, in the use case of my development, I rarely develop “packages” and the frequency of how my modules are moved or refactored results in absolute paths being better. I can’t say I can give a better explanation as to why, it just happens to be that way. There is also the secondary effect in that when using absolute paths, in any script I can easily visualize the categorization and grouping of modules together as I usually place the modules in a location of the Explorer that corresponds to its related purpose. I will also add that I do not have scripts ever have children. I know that is a common practice in Roblox, but I prefer to have scripts in the Explorer be terminal points and that sub-modules are instead contained in an adjacent directory.

I definitely agree that they are quite strict rules. I don’t exactly recommend them to many, if any at all. However, I found it is a system that works for my quite well. But I will agree that at the end of the day, not everything can be deterministic. I still do make case-by-case decisions “by feel”. It’s only natural. I think comments are a perfect example of that subjective difference person to person.

2 Likes
--	========
--	SERVICES
--	========

Too verbose to me; I usually just do a single-liner with added separation for categorization of instances

--!strict
--!optimize 2

--Preload
local settings = settings()

--Instances
local Studio = settings.Studio
local plugin = script:FindFirstAncestorOfClass("Plugin")::Plugin

local Toolbar = plugin:CreateToolbar("Humanoidless R6")
local ActivateButton = Toolbar:CreateButton("HR6OpenMenu","Open menu","rbxassetid://16140823668","Open menu")
local DockWidgetPluginGui = plugin:CreateDockWidgetPluginGui("HumanoidlessR6Gui",DockWidgetPluginGuiInfo.new(
	Enum.InitialDockState.Left,
	false,
	false,
	200,
	nil,
	200,
	0
))

Preload mostly exists for me as an optional category that I use for stuff that I immediatly need even before services. Pretty much only in plugins.

Here is how my standards look:

Identifier Type Case Example
Module Names / File Names PascalCase DataManager
Roblox Instances PascalCase MainGui, ReplicatedStorage
Custom Type Names PascalCase type PlayerData = {}
Constant Variables MACRO_CASE MAX_HEALTH
Non-Constant Variables camelCase currentHealth
Function Parameters camelCase function onPlayerAdded(playerInst)
Module Public Variables PascalCase Module.IsReady
Module Private Functions _camelCase _calculateDamage()
Module Public Functions PascalCase Module.GetCharacter()

Also I try to use the syntax sugar provided by the compiler as much as possible. It is pretty common for me to use table.freeze{} syntax, although I don’t really use it for strings. Also very common for me to use string imports:
require("../Configs/Character")

It might look like a problem if the code is run from StarterPlayerScripts that requires something in replicated storage. But I resolve this problem via grouping the code; all of the code that manages server stuff is done entirely within ServerScriptService.

In conclusion, for me, your rules seem to come from Python, which is understandable. My rules of writing mostly came from C/C++. I don’t bother strictly formatting, my rules are extremely volatile and some weaken or become more important depending on the project.

1 Like

I can generally agree, tho I avoid using snake_case and prefer camelCase

I actually find this funny as I mainly developed these habits from Go and C, and ironically I have a strong distaste for python. I do agree that many of the rules can just seem like unneeded verbosity.

idk your usage of snake_case is pretty common for Python. Guess my instincts failed this time

Rules are made to be broken afterall

local fr = game:GetService("ServerStorage").Medkit
1 Like

I didn’t give this whole post a read, it’s rather late at night, however I do want to share my appreciation to you sharing your code etiquette, seems to have a lot of intention! i’ll give it a read tomorrow hopefully.

1 Like

I use snake case because that is the standard used in K&R’s C Programming Language (the original book by the creators of C for those unaware).

does this imply you break your own optimization-above-everything-else rule?

1 Like

Of course, I break my optimization to make even more optimized optimization.
I don’t see any problem with that.
Makes perfect sense to me.
Don’t make me get started about breaking optimized optimization in favor of optimized optimized optimization.

1 Like

The book is quite old; even its latest edition was released back in the 90s. My standards were derived from modern C++ standards, which, just like the language, are quite volatile with each project’s rules.