Complexity is the Tradeoff That Keeps on Giving

Joshua Hunsche Jones
4 min readSep 3, 2020

If you spend any amount of time brainstorming solutions to a technical problem with an engineer, it will not be long before you arrive at the discussion of tradeoffs. More than any other domain I have worked in, I have found that software engineers are the most open to considering both the risks and opportunity costs of selecting one path over another. As this natural problem solving mindset overlaps with a passion for new technologies and interesting systems design, I believe there still remains one additional category of tradeoffs that we should all be talking about more, and that is complexity.

But the problem is complex!

As software engineers, some of the coolest problems we get to solve are those with layers of interrelated components and requirements. No matter how complected* a problem may seem, it is rarely if ever the case that the solution has to be just as complex. Building complex solutions where a simpler one is possible is like intentionally shouldering a monthly bill that your team and organization will have to continue to pay for as long as that solution is in operation. This cost will show up when onboarding new teammates, performing ongoing maintenance, or simply in a loss of agility when faced with large changes or new requirements. Complexity is a risk and a tradeoff that should be treated as such when designing solutions to complicated problems.

An everyday example

Teammates who have discussed frontend web development with me are quick to recognize that I am not the largest fan of the React JavaScript framework. I am sure that it has its place in the rare scenarios where a native app experience is required on a web page, but I also believe that more often than not it is instead an excellent example of the tradeoffs that complexity can entail. Somehow, the web development community has gotten the notion into our heads that a framework like React means all our code will be better organized and more readable. Unfortunately, I often find that quite the opposite is true.

On a recent project I was tasked with adding a button and a modal to an existing page to allow a user to kick off the workflow we were putting together. In order to do this I had to first dive into the bowels of a hefty React/Redux codebase. Once I grasped the design patterns the original creators were following, I was able to get the new elements implemented, but it took way longer than it should have. I could write up a modal and button that submits a POST request in under an hour in plain HTML and javascript — or even using a more streamlined framework like Stimulus.js. The layers of unnecessary abstraction and time consuming re-implementation of native browser features that React requires will quickly eat away at any gains obtained by using a framework with standardized design patterns (somehow, nobody agrees on what these are still.) In the end, we are left with a recipe for rigid UIs, slow iteration, and much higher maintenance burden. (It does not take long for a React project’s package.lock to exceed one million dependencies and child dependencies. That’s a mind numbing number for just building a web page.)

So this is just a frontend thing?

I used React as an example here, but the same thing can be said for backend architectures that use swarms of micro-services to solve a problem that could much more easily be solved in a single, medium-sized monolith. Single purpose, data-owning services are great on paper, but the real world often requires data to be used in ways that were not considered in the original design. Sooner than we might expect, we find ourselves making requests across five services every time we want to perform a simple step in a workflow, and we are worse off than before we started splitting up our monoliths.

Wait, not ever?

There is, of course, a role for complexity in software design. I am under no illusion that every problem can be solved with a simple, easy-to-comprehend solution. Instead I would like to suggest that we simply consider the cost of complexity with more weight when designing a solution. Finding ways to break up the problem or build helpful abstractions that keep each layer understandable and flexible can be challenging, but in my experience it is worth the time investment. The elegant solution you will be able to build as a result will be easier to extend, adapt, and maintain in perpetuity.

*Complect is an outdated term that a coworker resurrected in a talk last year and it has stuck in my head ever since. Its meaning is “to interweave,” which feels like a very contemporary description of the way multiple software systems mingle to form detailed infrastructures and networks. This is often what we actually mean when we say something is “complex,” which is why I’m passing this word on to you as well!

--

--

Joshua Hunsche Jones
0 Followers

I am a determined life-long learner and creator, as passionate about well-designed technology and software products as I am about meticulously crafted music.