When creating any large project, it's hard to know all the details up front. Think about redecorating a house. You have an idea of what the end product looks like - maybe the large furniture locations, and the theme of different decoration areas - but you don't map out in advance where every painting and knick-knack goes (or at least, I don't). Some items might end up not having a place and being thrown out, and you might end up missing something you didn't know you needed. You uncover, or, more accurately, decide these details as you start moving stuff around. You placed your large painting in the living room thinking it would be a good spot, but as more things make their way in there, you realize it would be much better placed in the den; so you have to move it again. I call this extra work "paying technical debt".
In programming, and for EXO in particular, just as in the home redecoration project, design - that is, thinking ahead - can only achieve so much progress. I can think all day about how a certain patch of code should be set up, but I can never come to a definitive conclusion to exactly how it should be designed (for a sufficiently large patch). Mind you, I'm talking here about the design and ease of use of the code, not of the game itself, although the two concepts are related. The better the code design, the easier it is to change the game design. Granted, changes to the game design that you've already made also takes extra work - I guess that would be non-technical debt - but it feels less like I owe something.
So, instead of thinking all day about how it should work, I frequently "just go". I make my code with the desired end functionality in the first (or second) way I can think of. This way often results in code that I can only describe as "jank". In other words, I have incurred technical debt. Even while programming it, at a certain point I know I will have to mostly or entirely rewrite the files related to certain features. To elaborate a bit, I want to try to explain my process with the item system, which I was working with this week.
A long while ago, when no item system existed, I figured items would have some common elements like needing a name and a description, and maybe a cost (but would that be for every item?), but for items to feel different and not just be the same thing reskinned dozens of times, they would need a lot of different functionality. So I made one superclass for items that contained those common elements, and a bunch of subclasses, each of which would be a specific item. But after working with that a bit, I realized I was making an entirely new subclass for every item and then just filling in some properties (the name, description, etc.), and not creating that much different behavior like I thought. (Each class has its own file, so you can see how this sort of feels like a waste.)
Instead, why not have just one class for items, and when the game needs to create an item, just have that other code fill in the right properties? Well, in the back of my mind I was thinking that I would need something to predefine item types - every Basic Salvager item should be identical, but I shouldn't have to go find the place where I created one and copy/paste - but I wasn't sure where to put that predefine code and how to make it mesh with the inventory code I had built around my first system. So I swapped to the one-class approach anyway.
Why is it that I can only make progress this way? Is it not a waste of time programming something when you know you'll rewrite it? Well, it is definitely out of necessity. I prefer to be able to design code ahead of time; however, as I mentioned earlier, for sufficiently large sections, it is impossible to know exactly how I want it to work up front. I can imagine the uses of the code base, but I can't actually apply it and find out how easy it is to use. I knew I wanted to be able to randomly populate inventories, buy and sell items, etc. And I knew I wanted it to be easy to create new items. I just didn't realize how much of a pain that would be with my first ideas. But the "jank" version let me actually try the functioning code while I created more features. You can't build on top of a beam in the blueprint; you have to actually put it in. But you can use cruddy wood as long as you replace it later.
Using this code, it became more and more clear how it should be designed. The problem is, the more code I build on top of a first version, the more work it is to change - that is, adding features to this functioning code incurs even more debt. But, if I decide it's time for a redesign too early, I might just be paying to refinance my loan. (I would be putting work into a second version, and making all the code I've built on top of it work with the second version, when it might have to be reworked also.) Which is exactly what happened, although in this case it might have been necessary.
The current, and hopefully final, version of the item system shares a lot with the version I used for a long time, before this week. There are two parts to an item in an inventory: you have the piece unique to that item in that location, like the number of stacks or the amount of fuel in the tank; and you have the information shared across all items of the same type, like how much total fuel that type of tank can hold. The unique piece holds a reference to the shared piece, and so the game can get the shared information from the unique piece.
If it's not totally clear how that works, that's all right; my point is this. Changing around all the items to this system took some work - the more places I created items, the more work it was. But in the newer system, it was a lot easier for me to create new item types, and easier for me to create other code in the game to use the item system. That is - pay your debts so you don't have to pay interest. So maybe the refinancing was worth it.
But, as I said, it was not the final version, and the difference was this: all of the shared pieces in the previous version were defined in a class (that is, code), and now they are in a data file. It's the difference between
It's not that much extra to put in the code syntax required in the former, but in the latter it's entirely stripped down. Every character except for the newlines and the space between "salvaging" and "1" is pure content (unique to this item type), rather than syntax. Plus, data files have the bonus of being moddable. Hurray! It took time, of course, to write the code that reads the data file into a format readable by the main game code, but the main point of this post is the debt. Of which there was a significant amount, but it could have been a lot worse. I had maybe 30-40 item types in when making the switch, which meant I had to retype or copy/paste in the information in the former format into the latter 30-40 times.
You might be wondering how I put up with typing "item. ..." so many times in the first place, like why didn't I switch earlier, and it's because I was never making a bunch of items at once. I had a system that worked for my first five item types, and here and there I just wanted to quick make a new one, knowing that I was incurring more technical debt but unwilling to take the time out of whatever other feature I was on a roll with to write a new item system.
Eventually, though, with the item system and with any piece of code, once I start to use it a lot at once, I start wanting to copy/paste fairly large portions of code, or I feel like I'm rewriting the same thing with new variables (protip: whenever you're tempted to copy/paste code, don't. Make a function. Or a parent class. Or something.), I decide it's time to pay my debts.
I find technical debt a useful concept to think about as I consider "is it worth my time right now to fix this, or can I just use this old system one more time while I make this other thing?" a question which comes up surprisingly often for how long-winded it is. I paid a lot of debt this week, with the item system, the effects system, and a bunch of my UI elements, so hopefully I'll be paying a lot less interest and I'll be able to make quicker progress.