EnriqueMark
Design Patterns & Architecture

Complexity and software design

2026-07 English

Analogizing complex software design to biology really is illuminating. Especially the self-organization principle of complex systems — since a large biological system can run fine without holding global information, why do we keep designing with global information of every sort at every level? It’s a good angle, and one I’d rarely thought of before. Biological systems and social systems are both cross-sectional expressions of complex systems. Large bureaucratic systems, on some level, actually

But this misses one thing, which is also why I say it’s illuminating rather than something you can apply wholesale: boundaries. The people making the biological analogy clearly overlook boundaries, namely that a living system is every bit as much a mountain of crud as the most unreadable code you’ve ever seen. An organism isn’t accountable for elegance, maintainability or efficiency, only for function. A large complex emergent system really can be very resistant to disturbance and self-repairing, but self-organization only shows there’s an internal feedback mechanism keeping it in a steady state, not that it’s efficient. That internal feedback can perfectly well sit on top of massive redundancy (which doesn’t affect function). Take path dependence in complex systems: some of the arrangements in there might not even be necessary in software design. An organism can hardly be refactored, but software can. Think of the famously long detour of the recurrent laryngeal nerve in the human body. Why not refactor it? For an organism there’s no need, a neutral trait that doesn’t affect survival is fine as long as it works. For software that isn’t necessarily so. Genuinely large software probably can hardly be refactored either, the cost is unacceptably high, and that’s exactly where design shows its importance.

Seen this way, contemporary engineering practice isn’t “heading down the wrong road” at all, it looks more like a consideration of engineering usability, or of building in the architectural sense. I can’t actually hand over a heap of completely unconstrainable blocks and let them run on their own and “emerge” into order. Emergence has been hard to design, even undesignable, from the very start. What I mean by design here is only “the system’s tendency”: we want it to at least move in this direction, so it reaches some level of stability and can push back on the problems complexity causes. We need to borrow the traits of complex systems, and hope what emerges is at least order rather than chaos.

As large software gets more and more complex it’s bound to force engineers into thinking about problems from a systems angle. Path dependence exists in pretty much every software system, and Microsoft’s ancient mountain of crud that nobody dares touch is exactly that. So being accountable to the local part as much as possible rather than regulating globally is bound to become a key point in good software design. Because at that scale you have to think about whether it holds together. Put another way, I need to add feedback mechanisms inside the system that let it regulate itself, so it keeps running smoothly, or at least doesn’t fall over. At that point too much global control makes stability a problem.

The part about late binding really is a bit twisty, but you can take it as an agreement in reverse rather than a definition, for example

if (messageName.startsWith("addTo")) {
  const field = messageName.slice(5).toLowerCase(); // "addToBalance" -> "balance"
  return (amount: number) => {                       // ← a function gets "made" right here
    (realState as any)[field] += amount;
    return `${field} 现在是 ${(realState as any)[field]}`;
  };
}

obj.addToBalance(50);   // field = "balance"
obj.addToScore(10);     // field = "score"   —— makes another one, welded to "score"
obj.addToHitPoints(3);  // field = "hitpoints"

What’s above only catches the agreed-on prefix, and every other behavior funnels into the “don’t understand” branch. That’s judging only at runtime. Compared with defining things up front and having caller and callee cooperate on behavior control, late binding stresses the callee’s control over its own behavior, and stays ignorant of the external caller. Whatever comes in, I go only by the internal rules on my side, and anything I don’t know turns into the internal don’t-know path. No matter what the outside hands in, its behavior on my side is regulated by me, not handed over to the outside. The idea here is essentially the same as the previous two, and the core point is “external independence”. Think about it a bit more and you find the biological analogy does have some validity, and helps with understanding. Because the most thorough expression of this external independence is the cell.

物件導向程式設計的弊端是什麼? - 知乎


Translation note. I wrote this in Chinese. This English version is an LLM translation, so the wording is not mine even though the thinking is. Original: 复杂性与软件设计.