boatbomber
Programmer | Game Developer | Open Source Creator
Your Code Is Too Complicated
Your Code Is Too Complicated
Your code is too complicated. That's why you have to relearn it every Monday when you come back from the weekend break. That's why debugging is a nightmare, and that's why new features take so long to integrate. Stop it.
This post will not be telling you to use a formatter for consistent style, or leave proper comments, or use good git practices (although you should do all of those things) but will instead focus on re-educating you on the way that you tackle software engineering at its core. This is about how you problem solve, not about how your workflow should look.
KISS. Keep It Simple, Stupid.
Seriously. I know you've heard of KISS before. But have you really been practicing it? Or have you been keeping things neat and conflating that with simple?
Writing simple code doesn't make you a bad programmer. In fact, writing simpler code can often be quite difficult. From what I've seen, code complexity relative to industry experience looks something like this:
As you learn new tricks and language/engine features, you start using them when you get the opportunity. As you get more experience, you go back to keeping things simple. By the time you're a senior you'll have experienced reading code you wrote 5 years ago and asking yourself, "What the #### was I thinking?" That's when you decide to go back to simple code. Let's skip to that part and avoid the awful code phase in the middle, shall we?
Before we go further we need to define "simple" and "complex" code. A program being complex doesn't mean it is esoteric or obscure- it could be easy to read and understand while still being needlessly complex.
adjective /ˌkämˈpleks,kəmˈpleks,ˈkämˌpleks/
consisting of many different and connected parts.
"a complex network of water channels"
The thing is, it's super common to write "complex" code without realizing it and it's a really hard habit to break.
Let's talk about that through the use of a common example:
You need to run a doUpdate() function on a 45Hz frequency. This is a pretty universally applicable scenario.
Here's a very common solution that I see used everywhere:
Every frame, check if 1/45th of a second has passed. If it has, call doUpdate(). If it hasn't, do nothing.
You've probably done this, right? A pretty "simple" solution that is very commonplace.
Well, I don't like it. Think about what this problem asked you to do, and think about why your code failed to do that in the simplest way.
Here's why:
You're running code every frame when you were asked to run something only once every 1/45th of a second.
You call os.clock(), perform a subtraction, and evaluate a >= comparison, even when it's not time to update.
You are getting and setting a variable, lastUpdateClock.
Even if you used a deltaTime summation instead of os.clock subtraction, you're still adding and comparing a variable.
Furthermore, this code can mislead some at first glance into believing that this update function is in some way connected/relevant to the rendering or framerate.
Take a step back from this common pattern you always reach to, and think about the problem from the perspective of someone who doesn't know how to code. What is this problem asking you to do at the most simple fundamental level?
You were asked to call doUpdate() every 1/45th of a second.
So that's what we should do. Just that. The simplest, purest answer.
This code is so deliciously simple. You get the exact same result as the previous solution, with much less code complexity.
That new intern won't need to tap you on the shoulder and ask what it does. The future you, back from vacation, won't have any trouble understanding it. Best of all, the code runs really performantly- it only does the minimum that the situation calls for: every 1/45th of a second, it calls doUpdate(). It's readable, maintainable, debuggable, adjustable. It's simple.
Look at the two side by side- it helps make the complexity difference quite stark. It's clear which is more maintainable and more performant.
This was a basic example for the sake of keeping the article accessible, but the process applies to every bit of code you write. You aim to accomplish some goal, and you want to do it in the simplest way.
Writing simple code means being able to break out of your own head and look at the problem as a series of logical steps. If you do anything more than the minimum steps, your code isn't KISS.
In other words, write out your code in sentence form and compare it to the problem sentence.
If the problem was "run doUpdate every 1/45th of a second" and your code does "Check time elapsed, reset elapsed counter and run doUpdate if 1/45th of a second has passed, otherwise do nothing" you can see that your sentence is much more complex than the problem asked for, even if you don't think the code itself is hard to understand.
Now, doing this isn't easy. It's hard to write code that can follow the problem statement in a minimalistic way, especially with more complex problems.
However, if you just take the time to really stop and think about the code complexity versus the problem complexity, you're already on the path towards simple code. I believe in you. You got this.
Bonus material: Some common areas I see people writing needlessly complex code-
Metatable solutions for no good reason
Writing custom libraries and frameworks for every little thing
Doing things manually when the engine supports it natively
Using metamethod OOP for systems with no actual inheritance