Hacker News new | ask | show | jobs
by wavemode 516 days ago
The author's assertion is true - complexity has to live somewhere. The nuance, though, is that all places complexity can live are not created equal.

Let's take the example of memory management: by pushing that complexity into the type system, Rust forces the programmer to deal with it and design around it. At the expense of some performance, we could instead push this complexity into a runtime garbage collection system. Since the runtime system understands things about the runtime characteristics of the program that can't be proven via static analysis, it can also handle more things without the programmer having to intervene, thus reducing the difficulty of the programming language. For most programmers this is a positive tradeoff (since most programmers are not writing software where every microsecond matters).

Similar tradeoffs exist in many different areas of software engineering. One monolith, where all the information is in one place, is easier to write code in than two microservices, which keep having to ask each other questions via API call. Yet, sometimes we need microservices. Rendering your web application entirely on the frontend in React, or entirely on the backend with templates, where all the logic lives in one place, is much easier than doing server-sided rendering then hydrating on the frontend. Yet, sometimes we need server-sided rendering and hydration.

Complexity is an irreducible constant, yes, but cognitive load is not. Cognitive load can increase or decrease depending on where you choose to push your complexity.

4 comments

(I'm not responding directly to parent post, more adding my two cents in agreement)

'Necessary complexity' needs to live somewhere. There is often a core of complexity that is intrinsic to the problem being solved. This cannot be removed, only moved/converted/etc..

That doesn't mean everything needs to be complex, and that you need to 'collect' it somewhere. There is such a thing as unnecessary complexity, and code that has a lot of it is 'bad code'. Don't fall for the trap of thinking that you can't improve code by identifying and eliminating unnecessary complexity.

I'd add the additional nuance that cognitive load is highly dependent on the brain of the subject. One person's cognitive load is another person's effortless routine--it largely depends on how often the person works with those concepts and how quickly they can switch into the "mode". Where one person sees a wall of indecipherable matrix variables in Python, another person sees that it's obviously just a Cholesky decomposition as part of a Monte Carlo simulation.

So where to push the complexity should be dependent on who will be interacting with it and what they consider "cognitive load".

As an additional example:

> Rendering your web application entirely on the frontend in React, or entirely on the backend with templates, where all the logic lives in one place, is much easier than doing server-sided rendering then hydrating on the frontend.

For an expert front-end React developer, rendering entirely in the backend with Jinja templates would be higher cognitive load than the other options, even if it is technically simpler.

IMO, cognitive load is the complexity of 'the scale of knowledge you need to build the solution (the problem space).'

However, complexity also comes from the solution itself — caching, microservice architecture, or even poorly chosen variable names.

So, complexity is irreducible, but it’s not a constant.

Certain solutions partition the problem space, thereby partitioning the complexity. This reduces the local complexity and, consequently, the cognitive load. However, the global complexity still remains and can even increase.

The other tradeoff comes with ease of debugging. Compile-time vs runtime errors. Dredging through microservice logs vs stack traces from the monolith.