<?xml version="1.0" encoding="utf-8" standalone="yes"?><?xml-stylesheet href="/feed_style.xsl" type="text/xsl"?><rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:media="https://www.rssboard.org/media-rss"><channel><title>Software on Mathis Dev Blog</title><link>https://blog.mathis-burger.de/tags/software/</link><description>Recent content in Software on Mathis Dev Blog</description><generator>Hugo -- gohugo.io</generator><language>en-us</language><copyright>Mathis Burger</copyright><lastBuildDate>Fri, 07 Aug 2026 01:00:00 +0100</lastBuildDate><atom:link href="https://blog.mathis-burger.de/tags/software/index.xml" rel="self" type="application/rss+xml"/><icon>https://blog.mathis-burger.de/logo.jpg</icon><item><title>Why managing complexity in software engineering is the key to everything</title><link>https://blog.mathis-burger.de/posts/008-managing-complexity/</link><pubDate>Fri, 07 Aug 2026 01:00:00 +0100</pubDate><guid>https://blog.mathis-burger.de/posts/008-managing-complexity/</guid><description><![CDATA[<p><em>A quick note before you read on: I started writing this post a while ago and never quite finished it. I&rsquo;m publishing it now as it was, rough edges and all, because I still think the core ideas hold up. That said, I&rsquo;ve learned a lot more about this topic since I first wrote these paragraphs, and I&rsquo;m sure I&rsquo;ll end up writing a more thorough, more nuanced version of this post in a few years, once I&rsquo;ve spent more time actually living with the consequences of the ideas below and doing some more scientific research.</em></p>
<p>Everytime I faced a problem in the software engineering process, it was somewhat related to poor complexity management.
You might not think it is related to complexity issues, but at some point it is.</p>
<h2 id="what-is-complexity-exactly">What is complexity exactly?</h2>
<p>Complexity is a multi-faceted term. I won&rsquo;t use some encyclopedia description made up of hard to understand phrases in order to explain it. Let me give you some probably relatable examples instead.</p>
<p>Everytime you wonder why the code looks like a mess, it&rsquo;s because you did not manage to gauge the complexity of the function beforehand and ended up building it without thinking about how to split it up or write it in a more readable way.
If you&rsquo;ve read the Clean Code book you might know that you can write a whole philosophy on the readability of code. Complexity is exactly that. Complex code is harder to read, and therefore also harder to understand. You can reduce complexity by splitting up your functions into smaller functions that each do exactly one single thing. That reduces the complexity of each individual function to a minimum and makes the overall complexity more manageable and easier to oversee.</p>
<p>This also relates to the number of parameters you can pass to a function. The more parameters, or the more complex those parameters are, the more unpredictable the actual outcome of the function becomes.
Let&rsquo;s compare those two functions:</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-rust" data-lang="rust"><span style="display:flex;"><span><span style="color:#75715e">// Function one
</span></span></span><span style="display:flex;"><span><span style="color:#75715e"></span><span style="color:#66d9ef">fn</span> <span style="color:#a6e22e">do_something</span>(a: <span style="color:#66d9ef">u32</span>) -&gt; <span style="color:#66d9ef">u32</span>
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#75715e">// Function two
</span></span></span><span style="display:flex;"><span><span style="color:#75715e"></span><span style="color:#66d9ef">fn</span> <span style="color:#a6e22e">do_something_else</span>(a: <span style="color:#66d9ef">u32</span>, b: <span style="color:#a6e22e">HashSet</span><span style="color:#f92672">&lt;</span>Vec<span style="color:#f92672">&lt;</span>HashMap<span style="color:#f92672">&lt;</span>String, String<span style="color:#f92672">&gt;&gt;&gt;</span>, c: <span style="color:#66d9ef">bool</span>) -&gt; <span style="color:#66d9ef">u32</span>
</span></span></code></pre></div><p>As you can see, the second function is a lot more complex. We have three parameters, of which the second one is a very complex datatype. Testing this is a nightmare. You won&rsquo;t even get close to an acceptable test coverage. And even if you get there, how do you ensure the thing actually works as expected?</p>
<p>Ensuring the integrity of the whole software gets even harder when a function requires interaction with other classes or services. It&rsquo;s pretty straightforward to unit test a single class, or even multiple classes independently.
It starts to get really tricky when you have to integrate different components together.</p>
<h2 id="where-is-complexity-management-most-important">Where is complexity management most important?</h2>
<p>Complexity management matters most when you have multiple services or components in a system that are loosely coupled. You often don&rsquo;t know which service will communicate with which other service. It&rsquo;s a nightmare to manage, debug and prevent.</p>
<p>Let me give you an extreme example: car software.</p>
<p>Modern cars are more like a driving smartphone than our classic understanding of a vehicle. And they have one specialty that is a nightmare in terms of complexity management: they are highly configurable. I bet everyone has, at some point, opened the website of a premium car manufacturer like Porsche, Audi, BMW or Mercedes and configured their dream car. But have you ever thought about what this configurability means for complexity?</p>
<p>Just imagine how many different configuration options for seats there are: normal or sport seats, heating or no heating, cooling or no cooling, massage or no massage, leather or fabric. All of those are almost freely configurable. That&rsquo;s 2^5 = 32 different seats! And almost all of those options have an effect on the underlying software. Now imagine all the other options you can pick during the configuration process.</p>
<p>Without proper complexity management, it&rsquo;s impossible to ensure the car works as expected at all. Of course this is an extreme example, but there are plenty of other applications that handle very loosely coupled use cases and can lead to unwanted side effects in the same way.</p>
<p>Let me give you another example, a more regular one this time. Microservices are a modern trend. Everyone implements applications using them, and they are often developed within an event driven architecture.
This means every service does not call any other service directly, but goes through a message broker instead. So we actually don&rsquo;t always know where a message is being processed and where it isn&rsquo;t. Debugging this is a nightmare, because you can run into side effects and bugs that are caused purely by the sheer complexity of those architectures. This is exactly why companies like Celonis exist. Their whole business is finding out what actually happened inside those highly loosely coupled, distributed architectures.
If you haven&rsquo;t done proper complexity management, I wish you the best of luck being on call and trying to find the cause of the random bug that just took your production environment down.</p>
<p>Log monitoring might help you find the bug, but it does not solve the root cause: complexity. If your system is too complex for you to know what happened, you must either ensure that certain things can only happen under certain conditions (and document those conditions properly, and keep that documentation up to date), or you have to rebuild parts of the system from the ground up so that you can actually handle the complexity of the overall application.</p>
<h2 id="how-can-i-manage-complexity-then">How can I manage complexity then?</h2>
<p>I already talked a little about how to manage complexity, but let me dig a bit deeper now.</p>
<p>One of the simplest tools you have is the Law of Demeter, sometimes described as &ldquo;only talk to your immediate friends.&rdquo; Instead of reaching through an object to grab something from an object it holds, and then reaching further into that, you ask the object directly for what you need. Every extra hop you allow through your codebase is another hidden dependency between parts of your system that were never supposed to know about each other. The Law of Demeter forces you to keep those chains short, which keeps the blast radius of any change small and predictable.</p>
<p>Closely related is the difference between abstraction and indirection, two things people tend to confuse. Abstraction hides complexity behind a simpler interface, so the caller genuinely has less to think about. Indirection just moves the complexity somewhere else without actually reducing it, and often adds a layer you now have to jump through to understand what&rsquo;s really going on. Good complexity management is mostly about honest abstraction. Bad complexity management is often indirection wearing an abstraction&rsquo;s clothes: an extra layer, an extra interface, an extra factory, that makes the code look cleaner while making it strictly harder to reason about.</p>
<p>Pure functions help here too. A function that only depends on its inputs and only produces an output, without touching global state or reaching out to some shared mutable variable somewhere else, is trivial to test and trivial to reason about. You can look at its signature and know exactly what it can and cannot do. The moment a function starts depending on the outside world, whether that&rsquo;s a global, a database, or some hidden singleton, you lose that guarantee, and the number of scenarios you have to think through to trust the function explodes.</p>
<p>At the architectural level, Bounded Contexts, a concept from Domain-Driven Design, are one of the most effective tools for taming complexity in larger systems. Instead of trying to build one giant, shared model of your entire domain that every team and every service has to agree on, you draw explicit boundaries around areas of the business where a model, its language and its rules stay consistent. Outside that boundary, the same word can mean something different, and that&rsquo;s fine, because the boundary itself is where translation happens. This keeps individual contexts small enough to actually understand, instead of forcing everyone to hold the entire system in their head at once.</p>
<p>CQRS, Command Query Responsibility Segregation, tackles a different kind of complexity: the complexity that comes from trying to make a single model serve both reads and writes equally well. By splitting the write side from the read side, you can let each of them evolve independently, optimize them separately, and avoid the situation where every feature added to one side quietly makes the other side more complicated too.</p>
<p>Finally, for complex distributed systems, it can be worth employing formal analysis using feature models to find dependencies and analyse the tree of connections between components. Feature models let you make the variability of your system explicit, as a structure you can actually inspect and reason about, instead of something that only lives implicitly in configuration files, environment flags and tribal knowledge. Once that variability is visible, you can start actively reducing it instead of just living with it.</p>
<p>If you ask me, feature models are the most promising tool when you have legacy systems that are just hard to refactor properly or when you are building highly complex distributed systems with shared functionality and a lot of indirect interaction. But as I am still conducting more research into that direction, I haven&rsquo;t fully made up my mind about that
certain technology. Nevertheless, feature models can definitely help to visualize your variability through feature model counting.</p>
<h2 id="lets-put-it-all-together">Let&rsquo;s put it all together</h2>
<p>None of these tools are a silver bullet on their own, and that&rsquo;s kind of the point. Complexity management isn&rsquo;t a single technique you apply once and then forget about, it&rsquo;s a mindset you carry into every decision you make while building software: how many parameters does this function need, does this class really need to know about that other class, does this boundary actually reflect how the business thinks about this concept, or is it just how the code happened to grow.</p>
<p>The car configurator example and the microservices example both point at the same underlying truth: complexity doesn&rsquo;t announce itself. It creeps in one seemingly reasonable decision at a time, one more parameter, one more service, one more shared piece of state, until one day you&rsquo;re on call at 3am trying to figure out why production is down, and the honest answer is that nobody fully understands the system anymore.</p>
<p>The good news is that every tool I&rsquo;ve mentioned here, the Law of Demeter, pure functions, bounded contexts, CQRS, feature models, exists precisely because other engineers ran into the same wall before you did. You don&rsquo;t have to rediscover all of this the hard way. Start small: keep your functions honest, keep your dependencies short, and draw your boundaries on purpose instead of by accident. Complexity will never fully go away, but it can absolutely be managed, and that, more than any specific pattern, is the actual skill worth building.</p>
]]></description></item></channel></rss>