EnriqueMark
设计模式与架构

复杂性与软件设计

2026-07 简体中文

将复杂软件设计类比到生物上,确实极有启发性。尤其是复杂系统的自组织原则——既然对于一个大型的生物系统来说,它可以在不需要掌握全局信息的情况下就运作良好,那么我们为什么要在各种层面上去不断的用各种全局信息进行设计?这的确是一个不错的角度,也是我之前很少想到的。生物系统、社会系统,实际上都是复杂系统的一个切面式的体现。大型的官僚系统其实在部分层面上

但是这里其实忽略了一个点,也是为什么我说是启发而非能完全套用的地方——边界。显然用生物系统来类比的人忽略了边界,那就是它的石山程度不亚于最不忍卒读的代码。生物体本身不对优美、可维护性和效率负责,它只对功能负责。一个大型的复杂的涌现系统的确可以非常的抗干扰、自修复,但自组织只能表明这个系统内部存在一个让其维持稳态的内部反馈机制,不代表他就是高效的。这套内部反馈完全可以建立在超级冗余(不影响功能)的前提之下。比如复杂系统的路径依赖问题,这里面的一些搭配在软件设计上甚至可能是不必要的,生物体几乎无法重构,但软件却可以,就比如说人体内知名的绕了一圈的喉返神经——为什么不重构?对生物来说没必要,不影响生存的中性性状能用就行,对软件来说不见得。真正意义上的大型软件可能也几乎无法重构,成本高到不可接受,这方面反而体现出设计的重要性了。

从这个角度来看,当代的工程实践反而不是「走向了错误的路」,而更像是出于工程可用或者说建筑意义上的考量——我不可能真的给一堆完全无法约束的组块然后让他们自己跑,自己「涌现」出秩序。涌现从一开始就是难以设计甚至是不可设计的,我这里所说的设计,指的其实也只是「系统的倾向」,我们希望它至少往这个方向走,以达到某种稳健性,从而对抗复杂所导致的问题——我们需要借鉴复杂系统的特征,希望他涌现出的至少是秩序而非是混乱。

随着大型软件越来越复杂,一定会逼迫工程师去用系统的角度思考问题。路径依赖在各个软件系统里面几乎普遍存在,微软的上古石山没人敢动就是如此。所以,尽量对局部负责而非全局调控一定会成为优秀软件设计中的要点。因为到了这个规模,必须考虑稳健性。换言之,我需要在这个系统里面添加一些能让其自我调控的反馈机制以保持运作顺畅,至少不崩溃。这时候过多的全局控制会导致稳健性出问题。

而关于后期绑定这部分确实有点绕,但可以立即为反向的约定而非定义,比如

if (messageName.startsWith("addTo")) {
  const field = messageName.slice(5).toLowerCase(); // "addToBalance" -> "balance"
  return (amount: number) => {                       // ← 这里"造"出来一个函数
    (realState as any)[field] += amount;
    return `${field} 现在是 ${(realState as any)[field]}`;
  };
}

obj.addToBalance(50);   // field = "balance"
obj.addToScore(10);     // field = "score"   —— 又造一个,焊的是 "score"
obj.addToHitPoints(3);  // field = "hitpoints"

比如上面的内容,就是只捕获约定好的开头,其他行为统一进「不懂」分支。这就是只在运行时才进行判断的。相较于提前定义好,调用方和被调用方配合进行的行为控制,后期绑定强调被调用方自己的行为控制,而对外部调用方无知。 不管你进来的是什么,我都只按照总计内部的规则走,不知道的就转内部不知道的流程。无论外部给入什么,它在我这里的行为都是我自身来调控,而不是交给外部。 这里的思想本质上跟前两个是一样的,核心关键点就是「外部无关」。这里仔细再想,会发现生物类比还是有一些合理性的,能够帮助理解。因为这种外部无关最彻底的体现就是细胞。

面向对象编程的弊端是什么? - 知乎