This post will be controversial for many of you
Most of the developers with experience above 1-2 years begin to use pre-built frameworks for convenience purposes. While it can be convenient, you really should reconsider using them if you rely on them instead of them assisting you. You’ll be able to acquire the skill of building your own reusable code with the assistance of this post. With the main idea of this post being taken away. Let me bring my points across multiple sections:
- Why not to use frameworks
- When to use frameworks
The Blame Game
What I mean by this is that a lot of devs use these frameworks to solve the problem easily, and in case it still doesn’t work, just blame the framework or Roblox itself (“This is a Roblox problem” devs say hello).
This is not the right way. Programming was never easy, no matter what CEOs or some motivators say. Programming was always about critical thinking and making the best of the current situation. Using someone else’s code to solve your problem is almost like saying, “Solve this for me”. It is extremely disrespectful to your inner C programmer.
Sometimes you really just have to stop and think: “Maybe it is worth a shot to solve it.”. I’m no coding guru, and professionally speaking, I only got into programming recently. That quote comes from my parents. Which I rely on in every activity I do.
Simplicity: Performance’s best friend
No matter what the framework states:
"
BLAZINGLY FAST™
"
or :
“Ultra fast [fancy term]-based solution”
or even sometimes:
“Native compiled framework”
IN NO WAY will any framework be faster and more efficient in terms of execution speed except the code that does exactly what you wanted to do. You want to pack data in a buffer? In no way will any replication library make it more efficient than you hardcoding it yourself. You want to make functional UIs? In no way will having a whole framework outperform a single local script managing the UI it was designed for.
Personally speaking, some of my code was able to outperform native compiled code without much trouble. While also being a lot easier to read
Bloatware and boilerplating
Unless you have a huge project with a huge team, and that being a maybe too, you are not going to use every tool of the framework. I can’t provide exact examples. But if you catch yourself thinking, “I think I could implement it a lot more easily”, you might work in a bloated framework.
Think practically: each framework has to do some other job before actually doing the work you wanted it to do. That level of bloat can range from “It’s just a convenience factor” to “Why does your code require at least 10 modules to work?”.
That would’ve been fine if that job was hidden under framework. But most of the time it is exposed to the programmer itself writing the code in the framework. Which leads to a bunch of boilerplate before actually doing any job
Simplicity: Programmer’s best friend
We begin to tackle the second section. It is that most of the frameworks are not simple. Both in terms of back-end and front-end. That leads to issues with debugging, with teaching newcomers, with memory, and with reading. And that’s what I named. Writing your own framework-independent code allows you to know what your code is actually doing. Frameworks hide that simplicity in their layer of abstraction for you to blame the framework for not doing the job properly.
For example, let’s compare 2 codes doing the exact same job, 1 being my own “Framework” HierarchyBuilder, and a formatted code:
HierarchyBuilder:
local build = require("./HierarchyBuilder")
local example1 = build{ Type = "Folder";
Parent = workspace;
Name = "Example1";
{ Type = "Configuration";
{ Name = "That is";
Type = "Folder";
{ Type = "Part";
Name = "Nested";
}
}
};
{ Type = "Part";
_count = 10;
Size = Vector3.one;
_init = function(self:Part,id:number)
self.Name = tostring(id)
self.Position = Vector3.new(id*2,0,0)
end;
};
}
Default organization:
local example2:Folder do
example2 = Instance.new("Folder")
example2.Name = "Example2"
local Configuration:Configuration do
Configuration = Instance.new("Configuration")
local ThatIs:Folder do
ThatIs = Instance.new("Folder")
ThatIs.Name = "That is"
local Nested:Part do
Nested = Instance.new("Part")
Nested.Name = "Nested"
Nested.Parent = ThatIs
end
ThatIs.Parent = Configuration
end
Configuration.Parent = example2
end
for i = 1,10 do
local Part = Instance.new("Part")
Part.Name = tostring(i)
Part.Position = Vector3.new(i*2,0,0)
Part.Size = Vector3.one
Part.Parent = example2
end
example2.Parent = workspace
end
Which of them could you understand quicker? Both do the same code; both are decently organized. But in one you know exactly what is happening. And the other only leaves you to guess what the hell is going on under the hood of HierarchyBuilder. That example was with an extremely easy example. If we scale it up to UI or other stuff, it becomes almost impossible to understand what the hell is actually going on without reading the documentation.
The Good Uses
There are some pre-built frameworks that are actually doing what you told them to do. These are the ones you might actually keep in your import list. In the end we do not want to go into full reimplementation, AKA “rewritten in Rust” syndrome. If something already does the job extremely well and is proven to be stable. Why not to keep it?
The best example I could think of is actually ProfileStore. It does exactly 1 thing: providing a reliable way of saving your player’s data as well as providing fallbacks. It doesn’t hide complexity; it embraces the complexity of safely saving data
Making reusable code yourself
If you are 1% of framework users that actually decided to rewrite your project. You might still want to reconsider due to the huge amount of work required. It’s not as simple as replacing an existing framework with a rewritten one.
For example, you are using a [buffer-based and schema-based native compiled custom replication with AES encryption] framework. It already established you in setting up both client and server to use certain protocols in order to communicate with each other. Replacing it with a simple luau data to buffer is not viable. It will literally require you to rewrite all of your existing code.
If you are the 25% of that 1% who decided to just rewrite the entire project. You need to first question why you used the framework in the first place. Is it for convenient buffer write? Is it for convenient communication with the client and server? Or is it for a false sense of security of AES encryption? Once you decide what you actually used the framework for. You design code that does exactly what you loved about the framework. If you loved simple buffer write. Make a module that automatically moves to the next [data size] bytes in your buffer implementation. That way you just do:
Buffer:WriteNumber(1212) -- automatically pushes write position by 8
Buffer:WriteF32(12131) -- automatically pushes write position by 4
And then do anything you actually wanted with that buffer. Simple and exactly what you wanted with way less bloat.
Answering common arguments
-
Arg: “Hey, but it keeps my code organized”
Ans: Learn to organize your code yourself instead of relying on a framework to do it for you. -
Arg: “This is not bloat; those are tools. You are just inexperienced and can’t use them efficiently”
Ans: Personally, I’ve never seen a single person be able to use all of the tools of framework in a single project. -
Arg: “My code that’s based off the framework is more performant than my code that’s not based off it”
Ans: Learn to optimize your code then. -
Arg: “My code keeps the complex job look quite simple”
Ans: You might want to look into that “Simple” implementation of the function you use. You might be surprised to see that it adds even more complexity to an already complex problem. That’s called hidden complexity -
Arg: “I can prototype a lot faster with frameworks than without them”
Ans: No one denies you from balancing between development speed and quality manually. Frameworks only make it automatically