24
votes
Is it normal to stress about your codebase becoming bad?
Lately, I've been working on a game in Godot. I've having a lot of fun, but I keep stressing out about my codebase becoming bad. I'm worrying about amassing technical debt, and having to rewrite huge amounts of code because of bad practices.
I've been reading best practices documentation for Godot, and learning about game programming patterns, but it's still a worry for me.
I'm wondering if this is a normal feeling, and how more experienced developers manage it.
To be fair, that says more about your code than you - but it's completely normal :) As you go on with your project, you'll learn more about what you're building and you also grow as a developer, hence you see the permanent need to refactor older stuff, I've been in the exact same situation before.
For me, the most important part is that I don't forget about these improvements and technical debts, so for me personally it's important that they're written down so that I don't forget them. That helps a lot at putting my mind to ease. Obviously, it's generally better to rather do it now than some time (tm). You have to find a good balance between new stuff and things that excite you and refactoring older stuff that makes you spent days and (ideally) literally nothing has changed functionality-wise.
And to refactor easily and without panicking if you're breaking stuff, two things are elemental in my opinion: first, most if not all programming languages and frameworks nowadays offer ways to modularize your code and give each of them a specific use cases and clear limits. In case you're newer to coding: It's called Separation of concerns and makes your life much, much easier :) Look at bigger Godot projects and see how they went about splitting their code into modules and ask yourself why. Once a task becomes just a sum of working on small, distinct modules, it feels a lot less overwhelming.
And number two: I know it sounds boring, but cover functionality with tests. At least the very central ones, no one forces you through a quality gate with 90% test coverage. Being able to just refactor something, then run some tests again and it still does the same really helps you build confidence in your solution and removes a ton of manual effort.
All in all it sounds like you're on a good path! The fact that you notice this in the process and look at game programming patterns shows that :) The next challenge will be to prepare your code base for these patterns (SoC) and to find the right ones for the correct situation and application - this is potentially the biggest difference between a junior developer and a senior developer. But everyone started from zero :) Again, go to github and look at repos using your framework and look what they've done. Maybe watch some tutorials on YT from start to finish and pay attention to how they structure their code and ask yourself why (good teachers explain it, but it is often not covered as well, especially in amateur sessions on YT).
Oh, and one last thing: in case you're new to coding: no one, absolutely no one, just sits down, writes code for 8 hours, leaves their desk and has the perfect code. After the iteration is before the iteration, the code is always evolving and changing, but you need a framework that doesn't make this hell for yourself :)
Most games are a technical mess that are held together by paperclips and bubblegum. There are situations where you will need to refactor a system or redo portions of a game due to poor optimization or a large machine incompatibility, but I don't think it's something that should be a reason to stop developing the game, sounds like your brain is trying to use it as an excuse for you to not work on the game. Don't get psyched out.
A good example of what game devs feel about their code are the code comments of Simpsons Hit & Run
I think it's since been deleted, but there was an amazing Twitter thread where Toby Fox was posting screenshots of the Undertale codebase, and there were lines like:
case 864: //fuck this vertical croissant bullshit
Someone asked him "please tell me there isn't actually an 864-case switch statement" and he replied "no it's way more than that".
Game dev is a wild place.
I just need to know why.
It's actually the best! I love when I work on a game alone and can just get shit done instead of worrying about making the code perfect. Also I think Toby Fox isn't too much of a programmer and more of an artist so that's probably the real reason.
Technical debt is "worseness" deliberately added to a codebase in order to trade off for some near-term benefit. So, you should be doing one of two things:
With either of those courses of action, it's fine, because you have a good reason to do it. However, in a given instance, if you're doing neither, technical debt is not what's in question. You're just probably about to introduce some poor code into your codebase. Don't do that; think of how to write better code to solve the problem at hand.
If you don't think some code you're writing is bad, you can't really fault yourself for learning later that it was bad. Mistakes are made, suboptimal code is written. Just correct and improve things once you find out that some code is bad.
Testing will increase your confidence about your code. Testing of all types can help: unit, integration, regression, end-to-end, manual, fuzzy. Employ some, any, or all of them.
Having tests gives me a safe feeling. I know I can safely refactor bad code without introducing any new bugs.
(Well, if your tests are good, of course)
Tests are a funny thing. They're very useful, but I've worked a few projects where fixing the test suite due to major version changes was harder than the actual code, because of the extensive mockups that needed done for the testing.
Unit tests can soon devolve into a second codebase filled with their own kludges, hard-coded ugliness, dependency nightmares, configuration labyrinths and technical debt.
Add to that the common feeling among devs that unit tests "aren't real code" and don't merit the same kind of attention to detail and best practices that the "actual" production code gets. (For example, few of our unit tests do useful logging, which can be a problem when those that require complex mockups fail on startup).
Unit tests are absolutely essential, but they're definitely not easy.
I think that's it exactly. Doing it right means doubling the workload.
In the short term anyway
The cost of an untested code base however can dwarf that of a system with good tests, to the point where a system becomes entirely impossible to iterate on without breaking in incomprehensible ways.
I guess that not updating dependencies on a regular basis is also a trade off in this context. But I did want to explicitly name it as something that can also turn into technical debt.
Rewriting code to some degree is not something you can really avoid. The older a project is the more refactoring you will do at certain stages. Even if you had a solid design at the start you might realize down the line you want to add a feature you didn't account for. That doesn't mean your code is bad, it just means you need to sometimes redesign parts of your code to facilitate new things.
Languages and frameworks you use might also evolve over time. Gaining new methods and ways to do things. So when moving to newer versions (which you should, on a semi regular basis) it might be possible to do things in new more efficient manners.
You might also just gain new insights over time, making you realize there are other ways to do things. A lot of devs fall for the trap of thinking their old code is bad. But if it isn't actually flaky code and has worked in a stable manner then it simply can't be that bad.
In a similar manner, many devs forget why they sometimes have things in a way. Then come across the code, think it is bad, attempt to refactor only to discover that there was a good reason it was working in the way it did.
There are entire wars fought out on the internet about having comments in your code. Generally I agree with the train of thought that good code explains it self. However, that is only half of the story. Good code explains what it does not why it works in the way it does. So you still need comments from time to time explaining why functions/methods exist. So even if you write bad code, with the right comments you at least can piece together how it came to be and more effectively refactor it down the line.
I wouldn't stress out about it. I always learned in projects for the next.
I've developed a lot of things in a lot of different environments (including godot), and the sense that you've created a shaky pile of hacks by the seat of your pants never really goes away. I find that I'm always aware of the places where I know I could improve something, or places where I was never really satisfied.
I wonder if it would help to describe the kinds of things you're worried about. Is there a particular bit of code smell that is bothering you? A particular pattern you're using that you feel has gotten out of hand?
I think the observer pattern is one I'm a bit worried about. It can be hard to track how signals are connected across the codebase, and I feel like I'm using them in the wrong places (one is getting emitted 60 times per second.) The problem is, I haven't found anything better to handle what they're doing in some cases.
I'm also a bit worried about certain areas not being compartmentalized enough. I think I've done a good job of seperating logic into components (scripts attached to nodes), but there are always places it could be better.
I wouldn't worry about a signal being fired every frame. The collision system runs on signals as well, and those can be fired every frame across (potentially) thousands of objects.
It certainly can be hard to track how signals are wired up. I prefer doing my signal wiring in code/script so that I can more easily use a "Find All" search to track down what is referencing them. On the other hand, navigating around code that isn't using the observer pattern to compartmentalize can be a different sort of nightmare.
It’s way worse when you’re working with a team that’s not aligned with your ideals. For side projects I take my time and usually over engineer things to learn what I like.
I don't work in video games, but from what I've heard from developers who are, there is no such thing as a "clean" or organized codebase in games. Commercially released games from the big studios are said to be an impossibly tangled mess of spaghetti code that only just barely works enough for what they're doing, and it's been that way forever. I actually just saw a video of someone who started disassembling Lego Island and one of their first things they found was something they described was a mistake in a window drawing call that was "objectively wrong". Not only that, but some of the programs you commonly use are coded so badly that Microsoft and Apple actually build code into their operating systems that fix some of those problems.
At the end of the day, the only thing that matters is that the code you deliver works reasonably well. The only problems you might run into is if you have someone else trying to help you. So maybe don't release the source code for your game. 😉
Well there's the problem, my game is open source. I think it's reasonably organized though.
I'm a mobile app dev rather than a game dev, but speaking personally the weak parts of my codebases frequently cross my mind. Especially with larger, older projects there are parts I've written that I'd love to rip out and rewrite from scratch instead of working on whatever new feature the PM has decided on.
I don't think it'll ever not be that way though, because as long as I'm writing code I'm improving my craft, and so naturally the longer ago I've written a particular piece of code the less I'll think of it. It's just part of being a programmer.
I utililze patterns, in games or other projects, only when I realise I need them.
However, since I'm mostly self taught, I've noticed I use patterns even without knowing it is a pattern :P
Anyhow, don't worry about it too much. If you see something weird, refactor it immediately. You will forget otherwise. Sometimes you need to make a big mess to figure out a thing, then refactor it to look nice when you realise how to get it to work.
Don't try pre-emptively optimize or abstract too much. That will lead into a mess! Do it when you actually need to.