In aerospace engineering, there is a saying that "mass begets mass". That is, if you want to add some mass to a plane, you can't just add that mass. You also need a bigger engine (more mass) to push that mass. So you need a stronger fuselage to hold that mass (which costs more mass), and you also need more fuel (more mass) to push that mass through the sky. Maybe now you need a bigger tank (more mass) to hold that fuel. But that means you need bigger wings (more mass) to support that mass. Etc.
I think a similar phenomenon happens in software engineering complexity. One of my favorite projects is suckless.org. They have a window manager that I used for a couple of years. It was great. It was written in C. And to configure the software, you opened the C files and edited the code, then recompiled it. It was simple.
Look at what happens if I add the feature of a separate dotfile configuration system, which is standard in many developer tools. First, I need to write code or bring in a dependency to parse the dotfile format. How do I load that dependency? Does it add steps for the build? If it's hand-rolled, how many lines of code is it? Depending on the format, it could be pretty complicated to parse and handle the errors. And both options add to the compile time.
Then, I need to write code that loads the dotfile. Where does it get it from? Typically, there's a hierarchy of places to look. Your home directory, perhaps the current directory, maybe you can set the place to look as an option on the command line or in an environment variable. And for things that are not set in the dotfile, I need to select defaults. All more code with potential bugs, things that can go wrong with necessary error messages, and a longer compile. And don't forget a longer startup time as the program chases down and parses configuration files.
The flip-side of "mass begets mass" is that if you can remove a small bit of weight from the plane, say by replacing one part with a smaller one, you can reduce the weight by much more than the savings of that individual part. We can do the same for complexity in software. Small eliminations of complexity can have outsized effects.
There's a threshold that your complexity can cross: too complex to fit in one person's mind, or say one mind and a whiteboard. At that point, it's hard to even understand how complex it is. You are looking at the system through a metaphorical keyhole, scanning around, trying to understand it. The implications of any change become harder to understand.
Here are some ways to reduce complexity:
1. Consider not having the feature.
A penny saved is a penny earned. By not adding a new feature, you avoid the compounding effect of complexity. Look for ways to avoid the problem. Maybe you don't need configuration. Even the suckless configuration solution requires more code and documentation. Is there any way you can do without it?
2. Consider the complexity of the whole system.
It's very easy to look at a problem and say "oh, I know how to solve this easily." You develop a simple solution. The solution may be simple, but it's more code. More code means more bugs. Now you need bug tracking software. And it means more programmers needed on the team, which means more communication overhead, more hiring, more onboarding, better onboarding docs, etc.
We need to consider the entire system, including Layer 1 (the work), Layer 2 (the tools and processes), and Layer 3 (the social structure of the people doing the work). See Wiring the Winning Organization. We often don't even look at the effects on the entire system.
A configuration system solves a number of problems. It makes it easy to try out different options. It's easy to share the configuration ("check out my dotfiles!"). And having a hierarchy of places where the configuration can be means you can customize the tool for different projects. What's not as clear is the cost of solving those problems with a configuration system. The complexity of the solution has its direct cost (lines of code) plus the indirect, compounding costs, which are often not predictable until after the fact.
3. Make the hard decisions.
We need to put in time and effort to reduce the complication. That effort is often plain manual work, but often it's cognitive work. It's making the tough decisions to not have that feature. It's socially easier to justify work you've done ("look, team, I spent three days adding a configuration system, which solves 3 problems!") than to justify refusing the work ("I won't put in a configuration system; we're just going to have to live with the 3 problems"). Or "we're going to use JSON even though we all think YAML is a nicer format."
4. Put in the work.
At the end of the day, we need to roll up our sleeves and force the system to be simpler. Usually, this means removing stuff. I've seen people try to build tooling to wrangle the complex system, but that tooling is just more code that needs to be managed. It's far easier to remove code than to build a tool with enough leverage to pay for itself.
Practically, in this example, this might mean getting rid of the configuration files. But you can't stop there. You must then remove all of the code and dependencies that you needed for them. The removal work also compounds. This is why preventing the complexity in the first place is much more efficient than removing it.
If you can't get rid of configuration files, store your configuration in a format that you already use elsewhere. For instance, if you already need a JSON parser somewhere else in the code, don't use a YAML parser for the configuration files. That one YAML dependency compounds. After you remove the YAML parser, rewrite all of the YAML files in JSON.
I suspect there is an andon-cord point in complexity. By that, I mean a point where you realize complexity is spiraling out of control where the best thing to do is everyone stops their work and swarms together to reduce complexity before it gets worse.
Conclusion
Complexity begets complexity. It's an insidious fact of software engineering. With deadline pressures and new feature requests, it's easy for complexity to compound quickly. We need to prevent complexity growth, minimize it when it's necessary, and actively reduce the complexity anywhere we can. We need to minimize system complexity as a whole, not just the complexity of any particular sub-solution. Complexity could be the most serious problem we face in the industry. We need to set up the incentives in our companies to prioritize preventing and reducing complexity.
This is an idea worth spreading. Suggesting not to implement config files seems a bit extreme, but I chose to read that as a way to really emphasize the need to consider trade offs. Using that example makes it clear that it’s not about espousing one right way of doing things applicable to all problems, but about encouraging one to carefully weigh the options (pun intended).
https://www.linkedin.com/feed/update/urn:li:activity:7129839037216874496?commentUrn=urn%3Ali%3Acomment%3A%28activity%3A7129839037216874496%2C7129887118809522176%29&replyUrn=urn%3Ali%3Acomment%3A%28activity%3A7129839037216874496%2C7130347754098192384%29&dashCommentUrn=urn%3Ali%3Afsd_comment%3A%287129887118809522176%2Curn%3Ali%3Aactivity%3A7129839037216874496%29&dashReplyUrn=urn%3Ali%3Afsd_comment%3A%287130347754098192384%2Curn%3Ali%3Aactivity%3A7129839037216874496%29