Game Design, Programming and running a one-man games business…

Technical debt… or investment?

Coders talk a lot about technical debt. here is a rough definition:

“Technical debt” (also known as design debt or code debt) is a concept in software development that reflects the implied cost of additional rework caused by choosing an easy solution now instead of using a better approach that would take longer.”

It’s often used in the context of justifying redoing some work. As in… “we have a lot of technical debt. The best thing to do is rewrite all this properly from scratch”. To some extent that can be justified. The engine for your games is effectively your house foundations. Don’t start building a house without ensuring you have decent foundations, and all that sort of thing. The problem with this mindset is that people (or rather…computer programmers) often get into the situation where they want to re-code everything from scratch again, and again, and again.

The thing is, the more experienced you get, the more you realize that the big, messy, patched, complicated looking smorgasbord of code that you are working with probably HAS to look like that. it probably looks like that for a reason. Its not pretty, its not clean, it doesn’t make for nice diagrams but it WORKS and thats important.

If you think thats not true I point you towards the apache web server (literally a pun on ‘a patchy web server’) and Microsoft windows, an absolute towering pile of code mess that is the very definition of technical debt. How can I possibly view these as good things…?

The simple answer is: Practicality and commerce. Apache is not the most beuatiful work of art in terms of highly structured, elegant, perfectly designed code, and windows is literally the state of the art in horrible hacks. (Like this one, to ensure sim city still runs). To quote:

They reported this to the Windows developers, who disassembled SimCity, stepped through it in a debugger, found the bug, and added special code that checked if SimCity was running, and if it did, ran the memory allocator in a special mode in which you could still use memory after freeing it.

And it you are someone who considers code to be beautiful, who likes to describe themselves as ‘a software architect’ instead of ‘hacker’ (or code monkey), then stories like this will fill you with rage but…

…Apache sure is installed on a lot of servers, and a huge number of PCs still run windows. Why? Because accepting that your patchy, confusing, held-together-with-string piece of code is actually MORE reliable than new stuff you could code today is actually a sign of coding maturity.

I do not open source my engine, and TBH nobody would likely use it if they did, but part of my reasoning is that it would be embarrassing. I couldn’t even decide on a naming convention. At one point I didn’t care, and I had classes called things like IniLoader. Then I thought it would be cool to have a ‘G’ prefix to indicate game engine, so I have GArc GFile, GHashTable. At some crazy point I had my classes in all caps, hence DEBUGENGINE.h. Some of the code separates functions with /************/ some of it with //////////////////////////////. Pretty much the only coding standard was a member variable naming convention with FirstLetterCapped.

By far the two biggest embarrassing pieces of dodgy code in the ‘more or less part of the engine’ GUI code are the following. Please try not to laugh.

Stupidity one: I have a GUI_ButtonBase class, and a GUI_WindowBase
class . They are DIFFERENT things, with no connection, despite a button obviously being a derived class of a window in any sensible system. Thus my GUI_WindowBase class contains separate lists of buttons, and child windows. *sigh*.

Stupidity two: All my windows have a virtual function call CheckClick(int x,int y), which they process, then call on any children. You would think that this took the x,y of the current mouse position and operated on it, but for some reason I gave up bothering and use helper functions like IsMouseInside() to check such stuff, which ignores the passed-in x and y. I still go to all the bother of passing x,y, down the hierarchy, but its ignored. I’m just paranoid about it.

Sure I could fix this, I could rewrite my button code, junk that dumb x,y, thing and rename all my classes sensibly while I’m about it. I would then need to spend several days changing all the code in Production Line, AND checking it, and I *ASSURE* you, I would miss something. There would be a new crash bug. I wouldn’t spot it, and maybe after a dozen players encountered it, someone would tell me about it. I would have achieved nothing, but frustration for my players, and a smug feeling that my code was less embarrassing.

I’m not giving into that. My current code WORKS, it has less bugs than it *ever* has. My current game (Production Line) is more stable than ever, and more stable than any game I have ever shipped. Going back and changing parts of my engine because they look messy, or because modern code fashions have changed or because there is a new API or code pattern would be MADNESS.

Don’t be too hard on your old code. Sometimes age brings wisdom.


5 thoughts on Technical debt… or investment?

  1. Most of the things you mention can both be fixed by automatic refactoring tools and verified at compile time (e.g. using the right class name everywhere, and passing the right number of args). These are simple, though broad, changes and relatively quite safe. Coding standards, likewise, can be enforced by tools, freeing up your headspace for stuff that really matters. The window vs. button thing is the only example here that I’d consider significant tech debt.

  2. yup, class name changes are way easier these days than they used to be, although I like to change filenames to match them as well, which involves changing #include references AND source control names at the same time.

    1. I haven’t used Visual Studio in a while, but IntelliJ IDEs today could do all those things for you. When you change a filename they prompt for renaming a class inside and vice versa. Includes and VCS references are also handled for you.

      Regarding the main topic, I don’t think any of the mentioned things should be considered a technical debt.
      They are clearly some code and design smells. Even so I find it more practical to define technical debt in terms of interest paid. If you don’t pay any interest it’s probably not a debt that should not be repaid. If an issue eventually becomes a debt very much depends on context and project goals.

      Code style is not a debt until you have a team member with certain OCD threshold whose work will be disrupted by inconsistent naming.

      A window/button thing is not a technical debt until it prevents you from reaching a certain goal and you can measure an impact.

      On the other hand, even the code that has no apparent design flaws can suddenly become a technical debt if one of the APIs it uses gets deprecated.

      Also there is nothing wrong open sourcing ugly code. Even if no one will use it, open sourcing is a way of communication just like your blog is.

  3. I think naming conventions are only a technical debt if you find yourself constantly typing the wrong version of something. Each time you do that you’re paying a little bit of interest, so it’s a debt.
    Same with including too many headers in a file. Each little increase in compilation time is interest on that debt.

    These you might want to fix, because you’re paying a price. You shouldn’t ‘fix’ something just because it’s messy. A lot of that ‘mess’ is actually the accumulated wisdom of years of bug fixes and handling edge cases.

    On the other hand, you should never be so scared of the consequences of changing any part of the code that it becomes untouchable. That’s a really bad place to be, because it’s become a mysterious black box governed by superstition. When you next need to go in there write some unit tests to cover it, then refactor (but only when it needs changing, not before).

  4. I disagree that technical debt is just a way to justify rewrites or that bad code is how things should be.

    Working on your own engine as sole developer is a whole different story than most other software engineers face. The technical debt I run into at work isn’t because it just has to be that way, but it’s because that was the shortcut taken to meet the release or sprint deadline. It’s because we have legacy code that was built when the requirements looked totally different. And it’s the part of the code that slows down every bug fixing or adding new features.
    We do know of better solutions and it will accelerate the general development a lot, but it will take quite a bit of work to refactor, which is hard to “sell” to the management, as it doesn’t directly create business value.

    Sure some perceived technical debt, can turn out as something that needs to be that way for a variety of reasons, but in most cases it’s a real thing.

Comments are currently closed.