1. 68
  1. 34

    Accidental complexity is just essential complexity that shows its age.

    This is dangerously wrong, and I see the unimpeachable evidence that it’s wrong every day.

    A ton of complexity comes from developers choosing suboptimal designs and overly complex tools. Two people writing the same software with the exact same set of requirements, edge cases, weird assumptions, etc, can arrive at designs with radically different complexity.

    So while yes, there is essential complexity that has to live somewhere, the high-order bit is usually the complexity of the design, and the skill of the designer matters a lot.

    From one of the author’s own posts:

    It’s one small piece of data-centred functional design, where thinking a bit harder about the problem at hand greatly simplified what would have been a more straightforward and obvious implementation choice in a mutable language.

    The other problem with the POV being argued for here is that, even while it contains some truth, as soon as it accepted it is used to justify shoddy work as “inevitable.”

    1. 4

      Would the two developers also have the same knowledge, same environment, context, and time pressures? Ignoring these is ignoring fundamental aspects of software design.

      The idea is not that the shoddy work is inevitable, but that complexity is inevitable. You can’t control it, but you can manage it. The previous article of mine you referred to in terms of functional design is one example of doing work on an implementation that was slower (both to write and to execute), less simple/obvious in approach (it needed more documentation and training for newcomers), and was conceptually more complex and demanding, but ended up composing better to simplify some amounts of maintenance (the logical separation and how change sets would need to be applied in the future).

      If we had been under intense time pressure to ship before losing major contracts? Taking the time to do it right the first time would have definitely been the wrong way to go about it. If we ever had the need or shifting requirements for logs “as soon as the request is received” instead of once it is over? We would have been retrospectively wrong. You put in the time sooner or later, and context matters. It’s why I say accidental complexity is just essential complexity that shows its age.

      1. 11

        but that complexity is inevitable.

        Unnecessary, accidental complexity isn’t inevitable as illustrated by those that avoid it in their work. It’s a cultural and/or educational problem. An example is language compile times. C++ has a design that makes it really, really hard to compile. The Lisp’s and D can do either more than or as much as C++ in terms of features. Yet, they compile way, way faster since they were designed to be easy to compile. In different ways, they eliminated accidental complexity that hurt them on that metric.

        Another example is probably SystemD. I’ll say ahead of time I’m not a zealot in any camp in that debate. I just have seen simple things to run in privileged processes, user-mode components for system management, and kernel hooks for components like that to use. The SystemD solution was a massive pile of code running with high privilege. We have decades of evidence that is bad for maintenance, reliability and security. Yet, they did it anyway for who knows what reason. Meanwhile, the modular and isolating design of QNX kept those systems just running and running and running with changes being easier to make with less breakage when they made them.

        On usability side, I always thought it was strange how hard it was to set up most web servers. If one was easy, you’d have to worry about its security or something. Supporting new standards just added more complexity for both developers and users. Then, two people decide to build a usable one, Caddy, in a memory-safe language supporting modern standards. The advanced features are there if you need to put time into them. Otherwise, install, config, HTTPS, etc is super easy compared to prior systems I saw. It’s super-easy because the developers intentionally eliminated most of the accidental complexity in setup and configuration for common usage.

        So, it just looks like much of the accidental complexity isn’t inevitable. Developers either don’t know how to avoid it or are intentionally adding it in for questionable reasons. If it’s inevitable, it’s for social reasons rather than anything inherent to complexity. Maybe the social sciences, not complexity science, need to be responsible for studying how to combat accidental complexity. Might get further than the tech people. ;)

        1. 4

          Maybe the social sciences, not complexity science, need to be responsible for studying how to combat accidental complexity.

          I believe that this is much more true than commonly acknowledged. There is so much to gain by having insight in basic rules of how people behave and work together. Currently I feel like we are completely in the dark. 30 years from now, I imagine that the main topic in software development will not be which language or IDE to use but how to optimize circumstances, incentives and relationships for the most stable code.

          1. 1

            I think you’re right about the former, but in 30 years the incentives that produce the current obsessions will not have changed and neither will the willingness to ignore human factors while producing crap.

        2. 8

          “People often don’t have time to do a good job” isn’t the same thing as “Complexity is unavoidable”.

          1. 4

            “Resources are finite and decisions must be made accordingly” is how I would frame it.

            1. 8

              Which implies that complexity is optional, and people often chose to invest elsewhere – and are then surprised when making changes is hard due to poor choices.

              1. 5

                Software isn’t written in a vacuum by robots with infinite time and energy, in an immutable environment that never evolves (nor get impacted by the software that contains it). There’s a reason most of us don’t use formal proofs to write blog engines. The complexity is an inherent part of development, the same way juggling between finite resources is not avoidable. You can’t de-couple the programs to be written from the people who will write it. That just means you’re working from a poorer model of things.

                1. 6

                  You seem to have completely changed arguments. Capitalism and the resulting market pressure may breed complexity, but that’s a rather different conversation.

                  1. 5

                    No. Complexity can control complexity, as mentioned in the post via the law of requisite variety. To simplify software, you still have to add/shift complexity; a shift in perspective is asking the devs to gain better understanding, to absorb (and carry) that complexity in their heads to simplify the program. Someone, somewhere, whether in code or in their mind, needs to establish a more complete and complex model of the world.

                    The only difference between an implementation that was done fast as required to work and an implementation that was cleaned up and made simpler is that as an organization (or as an engineer) is that we have taken the time to gain expertise (transfer knowledge “into the head” to reuse terms from the post) to extract it from the program, clarify it, and re-inject a distilled form. In order to maintain the software effectively, we have to maintain that ability to control complexity, that fancier model which now resides in people. If you don’t do that, the next modifications will also be full of “accidental complexity”.

                    If you write simple software without necessarily handling complex things (i.e. there is no gain in complexity in the entire development chain whether in code or in people), the complexity gets passed on to the users, who now have to adjust to software that doesn’t handle complex case. They learn to “use the software the right way”, to enter or format data in a certain way, to save before doing operations that often crash, and so on. They end up coping with what wasn’t handled anywhere before it made it to them.

                    I maintain that complexity just shifts around.

                    1. 3

                      I think I understand your claim. However, it’s not clear to me, once you expand the term ‘complexity’ to encompass so much, what that buys you. Could you give an example where somebody thinking about reducing complexity would act differently from somebody “embracing it, giving it the place it deserves, focusing on adapting to it”?

                      Edit: Ah, looks like @jonahx asked the same thing.

            2. 2

              I also think that “modern” software has a fetish for features, and, indirectly, for complexity.

              When I was programming in qbasic I could draw a graph in literally four lines of code.

              Now in any “modern” environment this will be really hard. I could probably work it out in C#.NET, but it will take me at least half an hour. Then sure, it will have a little more than the qbasic program (a window around it, which may be resizable, a lot more colors, and maybe double buffering), but none of that was a goal; I just wanted to draw a graph.

        3. 19

          There is a related concept called the Waterbed theory.

          From the Wikipedia article on this topic:

          Waterbed theory is the observation, ascribed to Larry Wall, that some systems, such as human and computer languages, contain a minimum amount of complexity, and that attempting to “push down” the complexity of such a system in one place will invariably cause complexity to “pop up” elsewhere. This behavior is likened to a waterbed mattress which contains a certain amount of water; it is possible to push down the mattress in one place, but the displaced water will always cause the mattress to rise elsewhere, because water does not compress. It is impossible to push down the waterbed everywhere at once, because the volume of the water remains a constant.

          From the origin of the term here at Perl.com: Apocalypse 5:

          The waterbed theory of linguistic complexity says that if you push down one place, it goes up somewhere else. If you arbitrarily limit yourself to too few metacharacters, the complexity comes out somewhere else. So it seems obvious to me that the way out of this mess is to grab a few more metacharacters.

          1. 16

            I strongly disagree. A huge amount of complexity is spent solving problems that don’t need to exist in the first place.

            1. 3

              I think we all can intuitively recognize problems that didn’t need to exist, yet often we don’t agree on the necessity of a given problem. Even if we agree, we still might not be able to do anything about it in practice, short of running away, if an “unnecessary” problem is imposed on us by a powerful third party who doesn’t share our view.

              Rather than comparing things to how they “should” be if I was in charge of the entire situation, I usually find it more helpful to distinguish between problems I have some degree of control over (choice of language or libraries, for example) and those I don’t (laws, business requirements, legacy protocols and APIs…). In my experience, discussions become more productive when these are clearly identified and agreed upon.

              1. 1

                I think we all can intuitively recognize problems that didn’t need to exist, yet often we don’t agree on the necessity of a given problem.

                Very important point. Essential complexity varies based on your goals, background, and role. If you’re a frontend person you may not want a complicated data model because it is hard to work with. The data modeler might make the model more complex for future extensibility.

            2. 8

              True, but the real argument is about unnecessary complexity. I’m reminded of the syscall comparison graph between Apache and IIS https://www.schrankmonster.de/2006/06/12/iis6-and-apache-syscall-graph/

              1. 3

                I’ll stick to my point: Accidental (unnecessary) complexity is just essential complexity that shows its age.

                It would be a mistake to consider the software independently from the people who have to write and maintain it. It doesn’t exist in a vacuum. The challenges in understanding the complexity and in people working around it (leaving behind messy artifacts and bits of code) are not really avoidable except in hindsight, once people tried things, shipped some, found mistakes, and course-corrected.

                At the time these things were thought to be necessary to accomplish a task within the constraints imposed on developers and their experience. It would be misguided to think that this can be avoided or prevented ahead of time. They leave traces and it’s only because of that work and experience gained over time that we can eventually consider the complexity to be unnecessary.

                1. 7

                  are not really avoidable except in hindsight, once people tried things, shipped some, found mistakes, and course-corrected.

                  This is either trivially true, or empirically false. Trivially true: If you replay history with the exact same set of starting conditions. Empirically false: Why can some teams produce software of greater simplicity and higher quality than others? Why can the same team, after training, or other changes, produce software of greater simplicity than they previously had?

                  The argument you’re making feels both defeatist and responsibility-shirking.

                  1. 2

                    It’s actually a call for more responsibility. We need to care for it, train people, and see complexity as something to be managed, adapted to, and not just something we can hope to prevent or push outside of our purview.

                    1. 7

                      You explicitly argue the seeking simplicity is misguided.

                      Another quote:

                      A common trap we have in software design comes from focusing on how “simple” we find it to read and interpret a given piece of code. Focusing on simplicity is fraught with peril because complexity can’t be removed: it can just be shifted around.

                      This simply isn’t true. It happens all the time that someone realizes that a piece of code – their own or someone else’s – can be rewritten in a drastically simpler way. A shift in point of view, an insight – they can change everything. The complexity you thought was there can just vanish.

                      What I take issue with is that you seem to be explicitly arguing against this as a goal.

                      1. 3

                        The shift in point of view often is just people learning things. It’s knowledge moving from the world and into your head. To understand the code that uses that shift in perspective, you have to be able to teach that perspective. You shifted the complexity around. Picasso’s bulls get simpler and simpler, but only retain their meaning as long as you have the ability to mentally connect the minimal outlines with the more complete realization inside your head.

                        It would be a mistake to think that because the code is simple, the complexity vanished. Look at books like “Pearls of Functional Algorithm Design”, or at most papers. The simplicity of some implementations (or even their use) is done by having people who are able to understand, implement, and ultimately handle the complexity inherent to problems and their solutions. People can pick up the book, see the short code, and never get why it works. But it is “simple”.

                        You can’t take the software in isolation from the people working on it. Without them, no software gets written or maintained at this point.

                        1. 8

                          You can’t take the software in isolation from the people working on it. Without them, no software gets written or maintained at this point.

                          You are saying this as if it’s a rebuttal to something I’ve said, when in fact I agree with it. Yet I still find the way you are framing the rest of the argument to be either counterproductive or factually wrong.

                          The shift in point of view often is just people learning things. It’s knowledge moving from the world and into your head.

                          Sure, I guess you can say it like that. I don’t what to make of it until I see what you plan to do with this framing…

                          To understand the code that uses that shift in perspective, you have to be able to teach that perspective.

                          Sometimes. Sometimes not: Often you can refactor code in a way that is simpler and that the original author immediately sees, with no teaching. In fact, they may exclaim something like: “Ah, that’s what I meant! I knew I was making it too complex!” or “Ah, I’d forgotten about that trick!”

                          All of these are common possibilities for what happens when you make such a change:

                          1. The change has no cost. It’s simply free and better.
                          2. The code is simpler if you understand some new concept first. So there is a teaching cost. And perhaps a documentation cost, etc.
                          3. The code gets shorter and superficially simpler, but hasn’t actually improved. You’ve moved stuff around. 6 of one, half a dozen of the other.
                          4. The code gets shorter and superficially simpler, but has actually gotten more complex. What you thought was a win is in fact a loss.

                          It seems to me that your argument assumes we are always in case 2. Or rather: That case 1 isn’t possible.

                          Maybe my real question is: What do you want people to do with that argument?

                          What I want people to do with my argument is to learn more, press harder for simpler solutions, have humility about their own designs, realize that there’s very often a better solution than the one they’ve found, and that this remains true almost no matter how experienced you are. I think if you understand that and take it seriously, and are on a team where everyone else does too, good things will happen.

                          1. 0

                            It seems to me that your argument assumes we are always in case 2. Or rather: That case 1 isn’t possible.

                            I mean, there are surely cases where 1 is possible and the change has no cost, but that would usually happen when there is already enough complexity absorbed in program rules to deal with it (i.e. a linter that changes things for you), or by the people who do the work. I.e. knowing that 1+1+1+1 can be replaced by 4 is almost always a better thing and it’s so trivial even most compilers will do it without fear.

                            Case 2 is what I hint at with the assumption that you need to understand new concepts or new perspectives. I.e. if you go and gather statistics and find out that 95% of cases hit in a certain way, you can afford to re-structure things differently. It may not have changed much to the algorithm, but there’s a need for new awareness.

                            Cases 3 and 4 are more particularly interesting, and far more likely when you start working on systems at large. Changing an ID from an integer to a UUID should be self-contained (and help do things like shard data sets). But if an external services uses integer IDs to sort on them, you’ve potentially broken an unspecified contract (was the ordering ever intended or only used accidentally?)

                            You can make assumptions that “you must be this tall to ride”, in which case all of these ‘complexity’ cases are assumed to be self-evident, need little validation or maintenance. But that’s an assumption you make based on what? Probably personal experience, hiring standards, people self-selecting? That’s a tricky classification to make, and can be very subjective. That’s why I’m thinking of defaulting to embracing the concept of complexity.

                            Sure you can make the case that everyone should be fine with this change, it is equivalent, you can demonstrate it to be the same, get someone to approve it, and move on. Or do it through an automated tool that has accepted these transformations as safe and worthwhile.

                            But an interesting aspect there is to always consider that there could be fallibility in what we change, in what we create, and that communication and sharing of information is a necessary part of managing complexity. If I assume a thing is category 1 as a change, but it turns out that in communicating it I show a new perspective that hits category 2 for a coworker (maybe I don’t know them that well!), aren’t we winning and improving? Knowledge management is part of coping with complexity, and that’s why I make such a point of tying people and what they know, and why I’m so hesitant to clearly delineate essential and accidental knowledge.

                            1. 3

                              Case 2 is what I hint at with the assumption that you need to understand new concepts or new perspectives. I.e. if you go and gather statistics and find out that 95% of cases hit in a certain way, you can afford to re-structure things differently. It may not have changed much to the algorithm, but there’s a need for new awareness.

                              Or you can realize that a problem has a better way of being posed. For example, a common interview problem is validating a binary search tree. You can do this bottom up, propagating the validity of the binary tree, but the book-keeping needed to make this approach is hard and fragile, and you’re looking at probably abotu 50 lines of code to get it right.

                              Or you can think of placing the nodes of a tree on a number line, and the solution becomes 5 lines of code.

                              Neither conceptualization is complex, and the one that solves the problem better is probably simpler to most people, it just takes a bit of thought to find the right isomorphism.

                              I find that similar effects happen in large systems, where entire subsystems become unnecessary because of a good engineering choice at the right time. An example of this may be standardizing on one data format, instead of having different systems connected using protobuf, xml, json, csv, thrift, and. This eliminates a huge amount of glue code, as well as the bugs that come when you need to deal with the representational mismatches between the various formats chosen.

                              I don’t buy your nihilism.

                              1. 0

                                So, once you’ve made that choice that saves a lot of complexity, how do you make sure the other engineers that follow after you work in line with it? How do you deal with the folks for whom JSON no longer works well because they want to transfer bytes and the overhead of base64ing them is too much?

                                You have to communicate and maintain that knowledge, and it needs to be actively kept alive for the benefits to keep going (or to be revisited when they don’t make sense anymore)

                                1. 3

                                  So, once you’ve made that choice that saves a lot of complexity, how do you make sure the other engineers that follow after you work in line with it?

                                  Are you seriously arguing that software can’t be maintained?

                                  Honestly, the way you’re writing makes me worry that you need to talk to someone about burnout.

                                  1. 1

                                    No, it’s rhethorical. Again the whole point is that complexity is fundamental and part of the maintenance and creation of software. The whole thing relates to the concepts behind complex adaptive systems, and again most of the thing is that the complexity doesn’t go away and remains essential to manage, which is more useful than ignoring it.

                                    1. 2

                                      If it’s fundamental and can’t be reduced, then why bother managing it? It’s going to be there no matter what you do.

                  2. 3

                    Accidental (unnecessary) complexity is just essential complexity that shows its age.

                    Your description is true if you assume that the developers intention is 100% aligned with developing the software as good and correct as possible - which might be closer to the case on a project like the development or rebar3.

                    But on a project like developing a boring CRUD system and being paid for it in a 9-5 - this is farther from reality.

                    Developers also are working in an economic framework, where certain skills are more valuable than others and they are trying to include those to their resumes to obtain higher paying, higher status or simply more interesting roles. They might also simply be bored and resent their managers and product managers and not be entirely motivated to look for the simplest solution possible, where many times that solution is on detriment of their own career.

                    In these situations, engineers will bring unnecessary complexity to the system, it is not the same as the unavoidable fact that some code has mistakes, have to be adjusted, have to be course-corrected and fixed, I agree this is essential complexity.

                    But there’s also complexity that goes beyond the software/problem at hand, they are also human, but they’re from the economical and social interactions of the people who develop that system. And I find that calling this essential is not right, because this type of economic relationship, this type of interaction, is not essential to software development!

                2. 6

                  I want to agree with a more limited scope of this, that software has to deal with complexity from the business domain eventually and either you do or you narrow your focus until you’ve ignore swathes of the business domain.

                  Unfortunately, the full claim (at least as articulated here) also seems to hand wave away shitty developers and bad engineering and inexperience as another form of complexity. While I can kinda see that argument–e.g., that you have to account for your resources in writing software and that failure to do so will leak into the finished project as emergent complexity in implementation instead of developer training–it seems to me to both be too easily misunderstood and to go in the face of experience many of us have with software that is just obviously too complicated (to wit, enterprise FizzBuzz) for what it is trying to do.

                  1. 1

                    I think the most contentious part of the post is that I just simply assert that people are an inherent part of software. You often avoid the incidental complexity in code by indirectly shifting it to the people working the software.

                    Their mental models and their understanding of everything is not fungible, but is still real and often what lets us shift the complexity outside of the software.

                    The teachings of disciplines like resilience engineering and models like naturalistic decision making is that this tacit knowledge and expertise can be surfaced, trained, and given the right environment to grow and gain effectiveness. It expresses itself in the active adaptation of organizations.

                    But as long as you look at the software as a system of its own that is independent from the people who use, write, and maintain it, it looks like the complexity just vanishes if it’s not in the code, and can be eliminated without side effects.

                    1. 9

                      I think it’d be easier for people to have got that if you’d built the case for simple software and then explored the people side of “okay, sure, the software is simple, but how’d we get there”.

                      The problem with your current framing is that it seems to boil down to this conversation (:

                      • Me: “Software is overly complicated, we can make it simpler.”
                      • Thee: “No we can’t, only complexity can manage complexity!”
                      • Me: “Preeeeettttty sure we can. A lot of code is clearly overcomplicated for what it does. <example of, say, too much ceremony around adding a seconds offset to a time and then printing it in UTC>”
                      • Thee: “The code is simpler, yes, but at what cost? Can we not say that the training required to prevent that engineer from overcomplicating a time routine is itself a form of complication? Can any among us not indeed be considered but spirited complications upon the timeface of software development?”
                      • Me: “If we claim that all complexity in software can only be removed at the increase of complexity elsewhere, I find this conclusion uniquely unsatisfying, somewhat like the laws of thermodynamics.”
                      • Thee: “Indeed. Life is often unsatisfying, and one might even say, complicated.”
                      • Me: “…”
                      • Me: “I’m not going to reward that joke. Anyways, it’s easy to construct (or, sadly, to recount) cases where software was simple and then, for literally no reason other than it seemed like a giggle, somebody made it less simple without appreciable increase in value or functionality. This strikes me as not as an issue of complexity but one of poor decisions or ignorance.”
                      • Thee: “Dealing with poor decisions and ignorance is core to the human part of software, and thus is a complication core to software itself.”
                      • Me:” Right, and I kinda agree, but if we recast all malice, poor decisions, or ignorance as complexity then we might as well define complexity as anything that makes life harder than we want it. And while I am attracted to that as a way of correlating with Evil, I also think that such a redefinition of complexity conflates it with other forms of things that make life harder than we want it. Ergo, I disagree with your overly-broad usage of complexity.”
                      • Me: “…Erlang in anger is still a great read though, as is Learn You Some Erlang. <3
                      1. 2

                        Point taken on the article being possible to word more clearly.

                        The cybernetics argument for the law is that the inherent complexity you are able to represent and model in your mind is what lets you extract and cancel some of the complexity.

                        If you want to make a long story short, part of my blog post would probably be a neighboring idea to “if you write the code as cleverly as possible, you are, by definition, not smart enough to debug it” – if you had to be very clever to simplify the code and reduce the problem to a simpler expression, you’ll have to have that cleverness around to be able to update it the next time around. I.e. we have to embrace that aspect of complexity as well if we want the overall enterprise to keep moving and evolving sustainably.

                        Let’s pick the time example you introduced, maybe the example is more workable.

                        Imagine the risk of simplifying too much, for example, where instead of adding a seconds offset to a time in UTC, you decide to only handle UTC, and since you only have UTC, you don’t show the timezones anymore. Also since you do time calculation, you can make it a lot simpler by using the unix epoch (ignoring leap seconds) everywhere.

                        This essentially forces the complexity onto the user, at the benefit of the developer. Clearly, aiming for code simplicity isn’t to anyone’s benefit once we increase our scope to include the user.

                        If you shift it around and always work with user-level timestamps (so it always works aligned with what the user wants), then you have to fight a lot harder when you want to calculate time differences of short duration within your codebase’s internals. This will increase the complexity of code in “unnecessary” manners, but encourages alignment with user needs throughout.

                        You might decide that the proper amount of complexity in code results from using both timestamps in a human referent format (year/month/day, hours, minutes, seconds, microseconds) in some cases, and then monotonic timestamps in other cases. This is a trade-off, and now you need to manage your developers’ knowledge to make sure it is applied properly within the rules. Because this higher variety sometimes increases code quality, but can lead to subtle bugs.

                        I think embracing the complexity of the real world is the way to go, but embracing it also means managing it, investing in training, and considering the people to write the software to be first order components. In some cases, it might be worth it to have messy ugly code that we put in one pile because it lets everyone else deal with the rest more clearly. We might decide “all the poop goes in this one hole in the ground, that way we know where it isn’t”, and sometimes we might decide not.

                        Also I’m glad you enjoyed the books :) (incidentally, how much should be “discoverable” in the tool, and how much should be relegated to the docs is a question in managing complexity, IMO)

                        1. 2

                          if you had to be very clever to simplify the code and reduce the problem to a simpler expression, you’ll have to have that cleverness around to be able to update it the next time around.

                          This ignores the fact that many times simplifying code has nothing to do with cleverness and everything to do with just wanting less code. Folding a bunch of array assignments into a simple for loop isn’t clever, for example–and if you argue that it is clever, then I fear that we’ve set such a low bar that we can’t continue to discuss this productively.

                          Clearly, aiming for code simplicity isn’t to anyone’s benefit once we increase our scope to include the user.

                          There’s nothing clear about that. It benefits the implementing engineer, it benefits the debugging engineer, and it benefits the computer that runs it. Your example about making the code as simple as possible until it’s just delivering a Unix timestamp (instead of the UTC string) is a red herring because the original deliverable spec was ignored.

                          1. 3

                            This ignores the fact that many times simplifying code has nothing to do with cleverness and everything to do with just wanting less code. Folding a bunch of array assignments into a simple for loop isn’t clever, for example–and if you argue that it is clever, then I fear that we’ve set such a low bar that we can’t continue to discuss this productively.

                            No I’d probably argue that it isn’t significant. Probably. On its face, it’s such a common refactoring or re-structuring that anyone with the most basic programming knowledge has already internalized enough information to do it without thinking. It fades back into the background as mostly anyone with basic knowledge is able to conceptualize and correct that.

                            I figure it might have been a trickier thing to do with assembler or punch cards (would it have even been relevant?) It might play differently with some macros or post-processing. Or changing to a loop folds the lines and reduces code coverage statistics to the point where a build fails (I’ve seen that happen). Or a loop implies new rules when it comes to preemptive scheduling based on your runtime, or plays with how much optimization takes place, etc.

                        2. 2

                          I love this reply. I’d just add that there is definitely no conservation law for “things that make life harder than we want it”.

                          It might be that we’re often living in optimization troughs that look locally flat, but when that’s true it’s usually because those are equilibria that we’ve spent effort to choose, relative to nearby possibilities that are worse.

                          It’s dangerous to say “everywhere we look, there are no easy wins, so we should just give up on making improvements”. To use an annoyingly topical analogy, it’s like saying “see? The coronavirus isn’t spreading anywhere near it’s initial exponential rate! We didn’t need to socially isolate after all!”

                    2. 6

                      It’s dangerous to assume that the amount of complexity in a given solution is fixed. Much of the complexity stems from the approach.

                      I think a good practical example of this was cvs → svn → git. Svn assumed that the complexity was inherent in SCM systems, and retained most of the complexity from CVS (while patching some of CVS’ flaws). Git redesigned the data model, and removed much of the complexity from the system. Git was able to retain the consistency , while making almost every operation faster, because a better data model reduced overall complexity.

                      1. 1

                        Right, there is no upper bound to complexity, but this is about the lower bound (that there exists one).

                        The prediction is that oversimplifying leads to a system that can’t deal with the real world, or forces the user to invent the rest. I think your SCM example shows that very well: While Git isn’t exactly a good study in userfriendliness (oh boy), its datamodel fits much better with workflows (distributed) and actions (merging) where Cvs and Svn were essentially useless.

                      2. 5

                        I think a lot of these arguments about complexity indirectly hinge on the fact we don’t have words to describe complexity besides… complexity. Like if I wanted to say something’s blue, I can call it blue, turquoise, indigo, sky-blue, ultramarine… I could talk about its tints and shades and RGB values. I can convey different useful nuances and talk about “this kind of blue but not that kind of blue.” With complexity, we have “simple” and “complex” and maybe “complicated”, if people can ever agree on the difference between “complex” and “complicated”. We’re stuck trying to talk about a lot of different ideas when we only have one word.

                        I see this in the article and in all of the comments under the article. Everybody’s talking about different aspects of “complexity”, but nobody’s making explicit that they are different aspects.

                        1. 2

                          Maybe we don’t need more words, but rather quantifiable metrics. How many variables and relations between them? Cyclomatic complexity? Developer hours? Even stupid metrics like SLOC have at least one advantage over nuanced jargon: we can argue about whether they are appropriate to a given situation, but we don’t have to bicker about what they mean.

                          Something I see missing from all sides of this discussion is the difference between specified (or “business”) complexity and implementation complexity relative to a given spec. Easy to miss when we don’t have a spec or model anywhere in sight, just a pile of code which grew organically with implicit and dynamic requirements. But if we do have a spec, even just a test suite, perhaps we can measure it just like we can measure the implementation. Then we have a more objective basis for comparing multiple implementations of the same spec.

                          1. 1

                            In the comment I link to a talk that sees complexity as:

                            • either simple / compounded / complicated
                            • theorical, pratical, practical-at-present time but subject to change in the future
                            • ideal, essential, usefull, useless for describing the problem solved
                          2. 4

                            I think complexity is sometimes (read: a lot) confused with knowledge imbalance.

                            X thing looks complex, because you don’t understand the set of concepts Y within it.

                            Many Xs look complex in software because they touch on many domains of Ys. Software intersects everything.

                            This is why it’s important to use common language, and if you can’t, explain it “like they are five” as people usually say. I think a better phrase is explain it like they know nothing about the domain.

                            You don’t need to simplify explanations; you need to define everything using common language.

                            1. 2

                              This is, I think, the difference between “knowledge in the world” and “knowledge in the head”. The argument you’re making here when it comes to defining things in a common language is essentially shifting the complexity of understanding the software as it evolved into every developer’s personal mind and knowledge. You have to be aware of how you define the language and frame things, the same way you tend to be aware of how you define programs.

                              Doing this well implies that you should at least be aware of this complexity, and prepare your organization for it. You’d want, for example, to provide proper training and on-boarding for people coming in, to make sure they can gain that knowledge if they don’t have it, and possibly do this even with regulars to make sure the knowledge hasn’t eroded. This can more or less be compared to doing things like incident simulations or chaos engineering, but with a focus on the domain knowledge required to maintain the software.

                              Shifting the complexity to people understanding things and then not caring about that complexity anymore isn’t necessarily a lot more responsible than doing the same in code. It’s just less visible.

                              1. 1

                                1st paragraph

                                What are we saying different here? From what I understand you are re-iterating my knowledge imbalance point.

                                2nd paragraph

                                100% agree with that’s why training and documentation are a must.

                                3rd paragraph

                                I didn’t mean to imply shifting anything, in fact I meant the opposite, in fact I mean almost precisely as you’ve written it heh.

                                Overall, I agree.

                                1. 3

                                  I see the agreement better now; I think I was over-focusing on “complexity is confused with knowledge imbalance” whereas I more or less conceptualize knowledge imbalance as an inevitable facet of complexity. In practice, both definitions are close to equivalent.

                            2. 4

                              I attended talks by R. Moura about complexity in software, and the paper “Out The Tar Pit”.

                              From my notes in 2015/2016 talks:

                              He stresses that he decomposes complexity into

                              • Simple : not compound - simplex
                              • Compound : made of simplex parts
                              • Complicated : The talk/paper does not deal with what complex is in the current every-day word usage: that is purely “complicated”

                              He has interesting points of view so as to observe complexity for measuring the REAL problem solved:

                              ideal complexity: [theorical] description of problem in pure abstract term

                              essential complexity: [practical] problem solving + logic but [practical-today] business logic

                              useful complexity: [practical] performance, needed state and mutation but [practical-today] languages and api, abstraction theorem

                              useless complexity: [practical] everything but [practical-today] mindset and tradition, not knowing a language enough, repeated code + diluted functions and classes, tons of object managers (with acronyms), logical tricks that can fool you, adding constraints that can make no sense while using them

                              IIRC, by [practical-today] he does mean “subject to change” also including “almost sure to change in the future”, because there is a possibility to do better in the future

                              Links:

                              He is also introducing Hexagonal Architecture as a possible means to address some of the problem of complexity in software engineering. From my notes he makes some parallels between “Out Of The Tap Pit” and “Hexagonal Architecture” approach.

                              1. 3

                                I think there is a nice simple truth in “Complexity has to live somewhere” that is creates great conversations when designing any system.

                                I’ve been reading through everything I can find on https://en.wikipedia.org/wiki/Problem_frames_approach which is the best system I’ve seen that helps make that complexity tangible to a group. The key aspect of the PF approach the helps show the essential complexity is its process of describing the real work effects and constraints, by limiting your “machine” gather info and triggering effects to a real world entity it feels like I actually can separate the essential complexity from the accidental complexity.

                                I am curious if anyone else has a process / thought tool to help show where the complexity it.

                                1. 3

                                  So, I think that a lot of the contention here is coming from a confusion between tasks that need to be done, and complexity that arises from completing a task in a particular place. If you don’t complete a task in one layer of software, it gets pushed either to a different layer, or to the user. Solving the problem at different levels is not usually equally complex, though. It can be appear to be, sometimes, but let me tell you a story that shows how big of a difference solving a task in the right place can make.

                                  At a previous job, we were working on a special-purpose accelerator with statically allocated, compiler-managed caches. What that means is that the program’s memory access pattern needs to be deduced by the compiler ahead of time, so that the cache can be filled with just the right amount of data. If you over-size, you run out of cache and the compile fails, and if you under-size the program crashes from an unfulfilled memory access. So, it’s important to get right. It’s also really hard. In general it’s impossible (equivalent to the halting problem, etc), and even for our very-restricted set of array processing programs, it was a continual source of problems. It turns out that operations like, say, rotating an image by an arbitrary angle, are pretty useful. It also turns out that translating that rotation into linear array scans to fill the cache requires quite a bit of cleverness. We did get it working in the end, but it was a whole lot of special-purpose algorithm awareness living in the compiler. The compiler definitely could not be described as general-purpose.

                                  Now, this toolchain could have perhaps been designed differently, to expose the cache allocation to the user. That way they would be the ones having to describe their data access pattern to the hardware. The fact remains, though, that so long as the caches were explicitly program-filled, something, be it the user or the compiler, had to have a detailed understanding of the program’s data access pattern in order to lay out a cache-filling and execution sequence that satisfies it.

                                  Contrast this with the usual solution, hardware-managed caches. The cache-filling algorithm of, say, your computer’s L2 cache, doesn’t need a detailed understanding of your program’s execution path. Neither does the CPU, the compiler, or, frequently, the programmer! So where did all that complexity go? Remember, our special-purpose compiler needed all that code and complexity to determine a program’s data access pattern because that’s what the hardware demanded. So in a general-purpose computer, are the caches way more complicated to accommodate the simplicity of programming on top of them? Well, no, not really. An LRU cache replacement algorithm is dead-simple to write. Obviously modern hardware caches are a bit more complicated than that, but they’re still not leaps-and-bounds more complicated than our accelerator’s caches-plus-fill-pattern-generator.

                                  So in our special-purpose accelerator, we were doing the same tasks as a normal CPU would: we picked which data were to reside in the cache and which to evict. The difference in complexity, though, was between a “normal” cache which is basically invisible to the programmer, modulo performance effects; and ours, which was a constant limitation throughout the software stack, including to our end users. That complexity, in a normal system, doesn’t reside further up in the software stack, or in user behavior, or any of that: it simply doesn’t exist. The task is fulfilled more simply.

                                  So why did we make all this extra work for ourselves? Because it made our accelerator faster, of course! You could argue that without that, we would have had to optimize somewhere else, which would have its own attendant complexity. This is true. However, the attendant complexity of, say, a vector math offload accelerator that does not try to gain a deep understanding of the whole program, is still leaps and bounds less complex than our system, while achieving similar goals.

                                  The project was eventually cancelled, largely because our users so hated dealing with our toolchain’s issues that they went back to running on the CPU. Many of those issues did involve cache allocation, in conjunction with some new user code that accessed memory in some slightly different pattern than we’d seen before. I think if you described their lives as simple, thanks to all the complexity we hid away in the compiler, they’d have some choice words for you!

                                  1. 1

                                    Thank you for sharing. I overall, agree with your summary:

                                    “ Complexity has to live somewhere. If you embrace it, give it the place it deserves, design your system and organisation knowing it exists, and focus on adapting, it might just become a strength. “


                                    However, I am not sure I agree with how you define accidental complexity

                                    Wondering if I could test your assertions by applying it in reverse.

                                    WRT: >” Accidental complexity is just essential complexity that shows its age. It cannot be avoided, and it keeps changing. “

                                    If essential complexity is young, does it mean we will not have ‘accidental’ complexity?

                                    I am thinking we still could have accidental complexity (as an example from information change that came late). And if I am right, then the definition above would be wrong..


                                    I also, somewhat disagree, that complexity always needs to be accepted, absorbed in and managed.

                                    WRT: *> “ It’s something I keep seeing debated at all levels: just how much commenting should go on in functions and methods? What’s the ideal amount of abstraction? When does a framework start having “too much magic”? When are there too many languages in an organisation?

                                    We try to get rid of the complexity, control it, and seek simplicity. I think framing things that way is misguided. Complexity has to live somewhere. “*

                                    I would argue that, say, intentionally (or by negligence) creating chaos will result in complexity. But that complexity should not be accepted. And, instead, the reasons for intentional, or by-negligence chaos should be addressed, and chaos should be removed (or reduced).

                                    In other words, why not create defenses against intentional, or by-negligence chaos?


                                    There is a definition of complexity that I have heard (but cannot find a link now to that youtube lecture, it was in cosmology).

                                    Basically:

                                    complexity is a change in entropy between 2 ordered states of the system in time

                                    Then, I would also leverage another view that, especially in human-created systems:

                                    “ We introduce this measure and argue that increasing information is equivalent to increasing complexity, “ [2]

                                    Combining both (and i am doing freely at the moment, without mathematics) would result in a following definition:

                                    ** Increase of information, would result in change (increase) of entropy, and that would result in complexity. **

                                    Then I could re-phrase your blog such that:

                                    If complexity is introduced by absorbing more information into the system, then the complexity must be managed. and the best way to manage it, is to embrace it and to find well defined places were the complexity is managed (rather than sprinkling it all over the system)

                                    The above re-phrasing would avoid, what in my view, are less ‘obvious’ assertions in the post

                                    • that any complexity deserves to be managed (and again, in my view, it is only the one that was introduced by absorbing more information)
                                    • that accidental complexity is an ‘aged essential complexity’

                                    Plus the above rephrasing also is reversible. Meaning if I remove information from the system, I should be able to reduce complexity.

                                    [2] https://www.ncbi.nlm.nih.gov/pmc/articles/PMC4179993/

                                    1. 1

                                      If that was true, then by this day and age a random person that started coughing on one side of the world could within days set cobol machines on fire on the other side.

                                      1. 1

                                        It sounds like you’re arguing that the level of complexity of the system of a problem, a software artefact and its engineers is constant? I don’t think that is true.

                                        Complexity can beget complexity. Engineers with very complex ideas can build very complex software where an engineer with a less complex understanding could build simpler software for the same problem.

                                        We’ve all seen examples of this: engineers that get caught up in an overcomplicated idea when even a less proficient engineer could have come up with something simpler and better.

                                        I agree with these premises:

                                        • there is irreducible complexity to systems
                                        • people are part of these systems and can contain some of the system’s complexity
                                        1. 1

                                          First I agree with a lot of the sentences in the article and maybe it’s just a definition thing about complexity (and simplicity). It has a philosophy tag after all. ;)

                                          if you make the build tool simple, it won’t handle all the weird edge cases that exist out there

                                          That’s basically saying complexity causes other complexity, not complexity is inherently required. I think the build tool is a great example, for where complexity is very avoidable. Edge cases in building could hint at problems in the design/architecture. It might be complex or bad in other ways. It might be out of your hands to change that for a variety of reasons, but then the “has to” part then seems wrong for this example.

                                          if you want to keep the tool simple, you have to force your users to only play within the parameters that fit this simplicity

                                          While that might be a solution a lot of things around the unix command line seem to show other ways by being simple.

                                          Of course if your user wants a complex thing, for example video games, which can be meant to have a certain amount of complexity to be interesting or tools that depict complex law systems or if the requirement is to work with something complex. However even in this case you can aim for the simplest solution, which is a solution just as complex as the problem at hand requires it to be. I would label that a simple tool to solve a complex problem.

                                          Problems might have complexities and “accumulate them their own”. That can be in requirements or because of growth of a system. One could of course call that “accumulate” simply a new problem or a changed requirement. Either way that’s why it’s important to at least strive for a simple solution and to not add additional complexity on top of what the problem requires.

                                          A big part of that is to analyze and understand a problem well. Examples of that could be interviewing your client more, or understanding the layers below or above the one you are working on better. That not being done (for whatever reason, lack of time, finished requirements, clear goals, of knowledge, etc.) seems to be a major cause for complexity added by the implementation of the solution to a problem - be it software, infrastructure, processes, or many other things.

                                          I think this also maps very well to “not coding” as a solution to a problem. Maybe there is a simpler solution to a problem. But in either case having a deep understanding of the actual problem, which might not always be the thing you get told, when you solve the problem as part of a job and sometimes might not be the problem you think you want to solve if you work on your own.

                                          Of course none of these take into account sets of problems, where you want to have something generic, but the rule that you want to understand your problem well applies to sets of problems as well. Of course it can be tempting to make things “accidentally turing complete”, but that means you create additional complexity and thereby problems. So a simple solution would be something that does go into a direction of avoiding that as good as possible.

                                          All of that maps to “only complexity can handle complexity” though. It even explains why one wants to avoid it in first place, if possible. If you create complexity on top of already complex problems, by not keeping things simple you will eventually end up with something not manageable anymore.

                                          I think there is something like inherent complexity, that can sometimes be hard to pinpoint (because there might be a solution thinking very far outside the box around the problem you see). For example a simple file concatenation might have less complexity than a simple c standard library, a simple operating system, a simple data center architecture, etc. All of them might be simple given the problem(s) they try to solve.