Dev Lessons Learned From Factorio
August 15, 2020
Factorio is one of my all time favorites games all about automation. You create an ever growing factory, with increasing complexity, dependencies, and threats. It probably shouldn’t be surprising, and some of these may seem fairly obvious but this game taught me many lessons about development. There is a lot of shared language between this game and development work in general. The lessons I’ve learned are:
- Refactoring is inevitable
- Modular code and shared libraries will save you a lot of time
- Don’t let perfect be the enemy of the good
- Prioritize your work and eliminate bottlenecks
In Factorio, at some point or another, you’re going to refactor the work you’ve done before. You might tear it all up and start fresh or replace it with something else. Or maybe you’ll just upgrade your belts, inserters, and furnaces to the next tier. Theoretically some of your original factory might survive until you win or stop playing, but likely it will be contributing very little, and is probably just in the way.
In many ways the same can be said of code in large projects. Almost all parts of it will be refactored, rewritten, or upgraded, and that’s okay. It’s even necessary. Keeping code fresh can help improve performance, fix unknown issues, and keep developers familiar with different parts of the code base. I wasn’t able to find a source, but I swear I once saw that at Google, a project’s codebase gets completely rewritten every three to five years. Refactoring for refactoring sake is obviously not what you should be doing, but the point is to not put off refactoring if it is necessary or could help.
In Factorio, there is a blueprint system that allows you to copy/paste parts of your factory. If you’ve managed to research and make construction robots, they’ll even place all the objects for you when you paste. What’s even better is when you create or find blueprints that are “tileable”, meaning you can place them side by side and keep expanding the factory with no additional modifications. As you can imagine, this saves you a lot of time and clicking when constructing your factory.
There are many ways this lesson applies to code as well. You might write a quick script to automate a step of your regular workflow that saves your team a lot of time. You also might create or use a loosely coupled interface for your logging that allows you to easily use/swap out different logging outputs, such as console output, writing to a file, or dumping logs into Elasticsearch. Small productivity wins can really compound to save you a lot of time. If you spend two hours automating a task that normally takes you 30 seconds, but it’s something everyone on your team of five does twice a day, you’ll make up the time in less than a month. If it’s a task that takes five minutes a day, you’ll make up the time in 3 days.
So often in Factorio I found myself trying to make a perfect ratio setup that would be optimized to consume 100% of the input and ensure a constant steady flow of maximum output. However I’d take so long trying to make this perfect setup that a different part of my factory would turn into a larger issue and get neglected, eventually causing more problems. If instead I had just gotten the new setup working 50%, I could have dealt with the other parts of the factory and come back to optimize later on.
This pops up all the time when you have competing priorities when working on a product or feature. Someone will want to do the best job and make it the most efficient they possibly can. Someone else will want the feature out the door ASAP. Find your happy medium. Don’t deliver something broken, but don’t hold back others or neglect larger problems by trying to squeeze every ounce of performance possible out of your code, unless that’s something that’s explicitly needed. Come back and refactor in the future (see point one about refactoring).
In line with the last point, in Factorio it’s really important to find what your current biggest issue or bottleneck is and address that first. It doesn’t make sense to scale up the production of green circuits when you’re not even maximizing current production because you don’t have enough copper wire to support the throughput needed. If you do, you’ll just have a bunch of assembling machines sitting idle, waiting for input resources.
This applies to so many things in software. I’ve been guilty of this myself, where I spend way too much time optimizing a system that doesn’t even need it, or making a feature that nobody wants. That time would be much better spent working on an issue that is actually affecting users, or on making a troubled system more scalable. This is where having monitoring of various kinds comes in handy. What actions are your users taking most often? Where is your application having performance problems? Spend time there instead of elsewhere.
Finding bottlenecks can have huge benefits to your team. If you don’t eliminate bottlenecks, eventually there will be a backup of work in front of the bottleneck. The more you keep things the same, the more things will backup. Map out your value stream and figure out where things are getting held up. Stop piling more things into the backup, and instead spend time helping things get through the bottleneck, or even improving the throughput of the systems. As an example, maybe you have a QA team that manually reviews every ticket that gets completed. When they first started, most tickets got completed in the same day, but over time, a larger and larger backlog of tickets has piled up. Now it can take upwards of two to three weeks for tickets to get reviewed. If the status quo continues, things will only continue to get worse. You may feel really productive, since you “complete” and pass on more tickets to QA than the rest of your team, but really you’re just exacerbating the problem! Your team should prioritize changing or improving the processes around QA to improve throughput. Can you automate tasks? Have developers rotate in to help out on QA?