How to Write Professional Code

There is no official docstring format for Lua AFAIK. So whatever comments you add above a function it will appear in the autofill. It’s not like JavaDoc which has strict syntax and format

2 Likes

Then you’d need to press 2 additional keys in-between the words of your variable names. It’s just unnecessary noise when you can separate words by using capitalization.

3 Likes

-- Check through all the players has no reason to be there.

-- Returns if a player is an admin or not.
-- @param name - The player name to check for.
-- @return player - The user closet to the name.
function ProAdmin:PlayerFromName(name : string)
	if not name then warn("ProAdmin(): PlayerFromName nil parameter") return end
	name = string.lower(name)
	
	-- Check through all players
	for _, player : Player in pairs(Players:GetPlayers()) do
		local displayName = string.lower(player.DisplayName)
		local username = string.lower(player.Name)
		
		if string.find(displayName, name, 1, true) or string.find(username, name, 1, true) then
			return player
		end
	end
	return nil
end

And there’s also no need to add this

-- Returns if a player is an admin or not.
-- @param name - The player name to check for.
-- @return player - The user closet to the name.

Just rename the function to :GetPlayerByName

Believe me when I say you’re not going to be seeing comments like those in “professional code.” If anything, you’d often see functions with no comments; if you do, they exist to explain the reasoning of the said function. Internal wikis are preferred for more detailed explanations. Now, I’m not saying there’ll be no comments; I’m saying that comments are used sparingly.

My honest opinion of “clean code” is that code will never be clean. Clean code only exists for those that wrote it. But still do strive to make understandable code.

2 Likes

Thanks for your criticism. For my example at the end, since it was getting late I just took one of my past projects and slapped it in there.

As for the unnecessary comments in it, that was because it needed to be documented, as that was like a public script. Thanks for your opinions.

3 Likes

The example had to be documented, because the script was a public script meant to be read by others. That is why it had so many comments, making it easier to use.

For a script in your game, of course I’m not going to add those documentation comments above a function, neither for getting all the players.

But if other people will see it, then I will.

2 Likes

I like it overall… If you don’t mind though I’ll comment on a few things:

1. Write your code such that you don’t need comments.

Name things crystal clear and keep your logic (as) simple (as possible). With the sole exception of comments for function/API documentation, never write a comment explaining what a piece of code does — if you need that comment, your code isn’t clear enough. Comments are a brilliant tool for explaining why you’re doing something, though. Let your code explain the what and your comments explain the why.

(Yes, document your functions with comments!!! (I sometimes don’t lol))
(Also section header comments are fine, but subjective as to whether to use them)

2. Naming conventions

Over the more-than-half-a-century that people have been developing software, here is the total amount of universal naming conventions and standards that all software engineers have been able to agree on: 0.

Naming things is subjective. There are only three rules:

  1. Stay consistent.
  2. Stay consistent.
  3. Stay consistent.

For new projects, I use camelCase for local variables and basically anything that doesn’t leave the script. But for table members, exported fn names, etc. I use PascalCase to be consistent with the way the Roblox Engine names things. If i require a service or a module, the variable name for it is exactly the name of said service or module. Then, if I’m onboarded to a project that doesn’t do the same, I’m prepared to throw all of these conventions out the window to stay consistent with the project. Consistency is key — if you can’t do anything else, at least be consistent.

3. Type checking is a tool for you to use, not a god for you to serve

Paraphrasing the wise grugbrain.dev, 90% of the benefit of type checking is “I can type a ‘.’ and see in a little menu all the things I can do with this object.” Type correctness is cool and all, but Luau isn’t statically typed, and is built on a language that was never meant to be statically typed. As such, if you blindly decree that “All types shall be correct, thus sayeth the Lord,” you’re going to build all kinds of tables with metatables and functions and type unions as is possible in lua, all the while fighting the awkward type system in a futile effort to please some type checking god of your imagination — all the while never actually changing any of the functionality of your code with the type annotations you add.

I’m not saying that type checking is bad, I’m just saying that I prefer to use it for what it’s useful for, and nothing else. You will never catch me typing --!strict at the top of my editor (just as before, I am prepared to throw this convention out the window if it’s required to be consistent with a project).

An important note about --!native
While a script marked as native won’t permit you to pass in arguments of improper types, that’s not what native code gen is for. Native code generation takes your script, which would usually be interpreted, and causes it to be compiled to native machine code so it’ll run without the slowdowns of the interpreter. This is very cool, but has some drawbacks (which you can read about here) meaning you don’t want to enable this on every script in your game. So I do not recommend using --!native to handle type issues. Let the type checker handle type issues.

5 Likes

I love this feedback, this could be a whole page in of itself…

2 Likes

I personally recommend following the official Luau style guide from Roblox:

https://roblox.github.io/lua-style-guide/#naming


Naming

  • Spell out words fully! Abbreviations generally make code easier to write, but harder to read.
  • Use PascalCase names for class and enum-like objects.
  • Use PascalCase for all Roblox APIs. camelCase APIs are mostly deprecated, but still work for now.
  • Use camelCase names for local variables, member values, and functions.
  • For acronyms within names, don’t capitalize the whole thing. For example, aJsonVariable or MakeHttpCall.
  • The exception to this is when the abbreviation represents a set. For example, in anRGBValue or GetXYZ. In these cases, RGB should be treated as an abbreviation of RedGreenBlue and not as an acronym.
  • Use LOUD_SNAKE_CASE names for local constants.
  • Prefix private members with an underscore, like _camelCase.
    • Lua does not have visibility rules, but using a character like an underscore helps make private access stand out.

Most code on the platform at least loosely follows the official style guide.

4 Likes

WHAT THE (how did this get through editing lol)

quite the opposite for capitalization, normally it would be isDayTime

anyway, you should refer more to the roblox lua style guide

3 Likes

RS is a very commonly used abbreviation for ReplicatedStorage. Why do u hate it?

Any reduction reduces the self-documentation of the code. It is better to use full variable names, especially if they are used repeatedly in the code. This improves readability. Using RS as an example, such a name does not accurately indicate that it is a ReplicatedStorage. What about that handwritten service?.. Well, let’s call it “RemoteService”. What should I name his variable? Well, probably RS2​:sob::skull_and_crossbones:. Logically, if a variable can be called “RS”, then RS2 is also a good name.(no). I think you get my point.

1 Like

In fact, luau has encapsulation (metamethods + typing), you just need to be able to do it. For private fields, there are metamethods __index and __newindex, for public fields, there are attributes (or function arguments). By the way, this is easier to do with the separation of logic:

--!strict
local Class = {}
local ClassMeta = {}
ClassMeta.__index = function(self, key)
	if key == "B" then
		error("Handle error")
	else
		return Class[key]
	end
end
ClassMeta.__newindex = function(self, key, value)
	if key == "B" then
		error("Handle error")
	else
		rawset(self, key, value)
	end
end

function Class:GetA()
	return self.A
end

function Class:SetA(value: number)
	self.A = value
end

local _B = newproxy(true)

function Class:getB(): number
	return rawget(self, _B) :: number
end

function Class:setB(number: number): ()
	rawset(self, _B, number)
end

type ClassType = {
	A: number
} & typeof(Class)

local function new(number: number): ClassType
	local self = setmetatable({}, ClassMeta):: ClassType
	self.A = number
	rawset(self, _B, 2)

	return self
end

type Module = {
	new: (number: number) -> ClassType
}

return { new = new } :: Module

By the way, this method does not allow you to call __index cyclically, does not allow you to call a constructor from a constructor, does not break the distribution of annotations “.” and “:”

1 Like

I think they mean by default. That’s from Roblox. You definitely can use metatables to program in access rules.

1 Like

Interesting ressource. If you’re interested, something that’s also very clean and productive is to put things in a same location in a dictionary. I do mostly do this with services, and so you just have to put Services.Players to use the player service. Looks professional, and spares you from an extra “local”!

local Services = {
        ContextActionService = game:GetService("ContextActionService"),
        CollectionService = game:GetService("CollectionService")
}

Services.CollectionService:GetTagged("Lava")

It’s also an extremely simple way to have a good DRY level. Putting your services in a table often reminds me to create a variable where all my modules are located e. g.

please dont do this, just call your services manually with different variables

if you actually want to do something like this, I’d make a module for it, but it’s recommended to create a new variable with GetService at the top of the script for every service

Why? Why is it painful? You can even use the indexer if you forgot how a service’s called.

Like I am not doing this:

local l = "lava" local services = {CAS = game:GetService("ContextActionService"), CS = game:GetService("CollectionService")} services.CS:GetTagged(l)

Source? I think having one place to find every of my services is more comfortable. But it’s pretty subjective.

https://roblox.github.io/lua-style-guide/#file-structure

it is subjective but it just hurts my brain to have a table at the very top of a script ngl

Putting them under your function won’t work either lol.

Anyway, I understand. It might sound strange first, but if used correctly, this can even help sometimes.

RS is a much more commonly used service compared to the only other service that could be abbreviated with RS, which is RunService

It’s used in videos on youtube, posts on devforum and comments on discord, really anywhere, bc its abbreviation is ubiquitous and is known to be associated with ReplicatedStorage. If I see RS in code, I know it’s replicated storage and so do others. So, it doesn’t reduce any self-documentation since anyone who’s been scripting enough on Roblox should know that RS = ReplicatedStorage.

It’s one of the more special abbreviations bc so many ppl use it, maybe the only one.

If you have a variable that u wanna call RS, call it something more meaningful then bc it’s pretty safe to say that it’s not more important than ReplicatedStorage for it to be taking its well known “RS” name.

1 Like

Source:

1 Like