Naive Coding, oh boy where do I even begin…
Taking a naive approach your code can be really damaging, as you’re just assuming everything is safe once it’s written.
Below I’ve provided some reasons why being naive is not a good idea
1. Security
This is the processing of handling remotes and other stuff. If you leave your remotes unsecured, exploiters could easily just fire them and give themselves extra boosts or affect other players’ experiences.
For example, lets assume we have a function that makes a store purchase
In this first example, we’ll do all the validation on the client.
local ReplicatedStorage = game:GetService("ReplicatedStorage")
local LocalPlayer = game:GetService("Players").LocalPlayer
local Cash = LocalPlayer:WaitForChild("leaderstats").Cash
local ProductMetadata = require(ReplicatedStorage.Products)
local function MakePurchase(itemId)
local item = assert(ProductMetadata[itemId], "Unrecognised Item")
if Cash.Value >= item.Cost then
Cash.Value -= item.Cost
ReplicatedStorage.DoPurchase:FireServer(itemId)
end
end
MakePurchase("Sword")
The problem with this is that the DoPurchase function will simply grant the item and not remove currency or check that the user even has enough
The solution here? Do your validation on the server, not the client.
So yet again, here we our with our NEW client script
local ReplicatedStorage = game:GetService("ReplicatedStorage")
local LocalPlayer = game:GetService("Players").LocalPlayer
local Cash = LocalPlayer:WaitForChild("leaderstats").Cash
local function MakePurchase(itemId)
ReplicatedStorage.DoPurchase:FireServer(itemId)
end
MakePurchase("Sword")
And on the server side of things we can do this
local ReplicatedStorage = game:GetService("ReplicatedStorage")
local ProductMetadata = require(ReplicatedStorage.Products
ReplicatedStorage.DoPurchase.OnServerEvent:Connect(function(p, itemId)
local item = assert(ProductMetadata[itemId], "Unrecognised Item")
local cash = p.leaderstats.Cash
if Cash.Value >= item.Cost then
Cash.Value -= item.Cost
--grant the item
end
end)
Now, whenever DoPurchase is fire from a client, it’ll do everything as well as deduct the cash from the server
2. Assuming Truthy Returns (FindFirstChild etc.)
Truthy returns is where a function will return something that is not nil or false. Certain functions such as FindFirstChild, etc. will not always return truthfully
Here’s the example why this can be bad
local Part = workspace:FindFirstChild("Part")
Part.Anchored = false
The problem? Part might not exist in workspace, meaning this code will error as you’re attempting to index a nil reference
This fix is quite simple, all you have to do is change the function to WaitForChild()
local Part = workspace:WaitForChild("Part")
Part.Anchored = false
3. Not wrapping HTTP requests in pcalls
HTTP requests can fail, this extends to the functions that need to use HTTP to work propertly (DataStore:SetAsync, TextFilterResult, Http:GetAsync)
Our example will be datastores, as SetAsync does return an error if it doesn’t work properly, however your code could end up creating new data ontop of pre-existing data
local DataStoreService = game:GetService("DataStoreService")
local DataStore = DataStoreService:GetDataStore("Example")
local Players = game:GetService("Players")
local function createNewData(userId)
--idk make a new datastore
end
Players.PlayerAdded:Connect(function(p)
local data = DataStore:GetAsync(p.UserId)
if not data then
createNewData(p.UserId)
end
end)
This code can error and result in users not receiving their data, and because it is unhandled, there is a chance that the user could lose their data and get it overwritten
The fix is simple, wrap GetAsync in a pcall and then tag the player
local DataStoreService = game:GetService("DataStoreService")
local CollectionService = game:GetService("CollectionService")
local DataStore = DataStoreService:GetDataStore("Example")
local Players = game:GetService("Players")
local function createNewData(userId)
--idk make a new datastore
end
Players.PlayerAdded:Connect(function(p)
local success, data = pcall(DataStore.GetAsync, DataStore, p.UserId)
if not success then
CollectionService:AddTag(p, "DataFetchError")
return
end
if not data then
createNewData(p.UserId)
end
end)
By just adding a pcall and a tag called ‘DataFetchError’, it allows us to handle DataStore errors, preventing data from getting overwritten (assuming you also make sure to handle the tag)
local function SetData(ds, p, newData)
if CollectionService:HasTag(p, "DataFetchError") then
return --prevents overwrite of data
end
ds:SetAsync(p.UserId, newData) --technically this should be PCALL wrapped as well, however I am not writing an entire datastore system
end
3.1 Assuming TextFilterResult’s functions will always work
There’s no need to provide code for this one as the Wiki does a good job of summarising why you should not assume this, and it extends from the previous point.
If you incorrectly assume that TextFilterResult always works, you could end up displaying unfiltered text, which can result in the game being moderated as it does not filter text correctly