先问你个问题。
如果你刚入职一家公司,领导把你拉到一边,神神秘秘地说:"小X啊,咱们有个核心项目,跑了38亿年了,从来没出过宕机事故,现在交给你维护。"
你是不是觉得撞大运了?38亿年不宕机,这系统得多牛逼?
然后领导补了一句:"哦对了,这个项目没有文档,没有测试环境,从来没人重构过,之前负责的程序员都老死了,你看一下代码吧。"
你打开代码仓库一看,
我去。
整个代码库30亿行,其中90%的代码是注释掉的废弃版本,还有一大段是不知道谁复制进来的别的项目的片段。你想找个函数说明,搜出来一堆带// TODO: deprecated的标记,但没人敢删。
更离谱的是,你发现某个核心模块的架构是反的,但是改不了,因为一改整个系统就崩。
你瘫在椅子上,问领导:"这谁写的?"
领导说:"没人写。这项目是自己长出来的。"
这个项目,就是人类基因组。
![]()
说白了,你第一次翻开人类基因组图谱的时候,任何一个有三年以上经验的程序员都会产生一种强烈的既视感:这不就是我上周刚接手的那个祖传项目吗?
你先别急着反驳。
咱们慢慢说。
你第一天上班,想先熟悉一下代码结构。你随机点开一个基因,嗅觉受体基因。
看了半天,你发现一个离谱的事:这堆基因里,差不多一半是坏的。
就是那种,函数名还在,注释也还在,但是里面的逻辑早就因为几个随机的字符替换废掉了,调用一下直接返回错误。
但问题是,这些坏函数就这么留在代码库里,每次编译的时候还要被一起打包,每次细胞分裂的时候还要被完整复制一遍,占空间不说,还浪费CPU。
你数了数,人类基因组里大概有两万个这样的假基因。两万个。
你是不是想说:那删了啊?
别着急。
你刚想删,旁边一个老员工凑过来,拍了拍你的肩膀:"小伙子,别乱删。上次有人删了一个看起来没用的假基因,结果那个基因不知道怎么跟另一个功能搭上了,整个系统直接出问题了。"
这还不是最离谱的。
你继续翻代码,发现整个基因组里,有大概10%的内容是同一段重复序列,叫Alu片段,足足有一百多万份拷贝。
就像你在一个项目里,同一个注释块复制了一百多万遍,散落在代码库的各个角落。没人知道它为什么在那儿,也没人敢动它。
更离谱的是,你还发现有大概8%的代码,明显不是"原生"的,它们是古代病毒感染的时候,把自己的DNA插到人类基因组里的。
![]()
就像你打开一个祖传项目,发现里面有8%的代码是十年前一次黑客攻击留下的后门,没人清理,就这么一直留着,每次编译都带上。
你看到这儿,已经开始怀疑人生了。
这时候你又发现了一个更扯的事:一个正常的基因,转录出来之后,里面有一大段是没用的,要手动剪掉才能用。
这段没用的部分,叫内含子。
![]()
啥意思呢?
就是你写了一个函数,这个函数90%的代码都是注释,真正能干活的外显子只占10%。
每次调用这个函数之前,系统要先花一大堆CPU,把那些注释一段一段剪掉,然后才能运行真正的逻辑。有的基因更夸张,99%都是要剪掉的废话,真正有用的就一丢丢。
你算了一下,如果把这些内含子都删了,转录效率至少能提高十倍。
你又想删。
老员工又凑过来了:"小伙子,内含子不能删。你不知道吗?内含子有个功能叫可变剪接,就是同一段基因,可以通过不同的剪接方式,产出不同的蛋白质。你删了内含子,一个基因就只能干一件事了,那得少多少功能?"
你愣了一下:"那...那为什么不能一开始就写清楚?一个基因对应一个功能,多清爽。"
老员工翻了个白眼:"你以为是写Hello World呢?"
好吧。你忍了。
你继续往下翻,翻到眼睛这个模块的时候,你彻底坐不住了。
你发现视网膜的感光细胞,是朝后的。
什么意思?
就是光线照进眼睛,要先穿过一层血管,再穿过一层神经纤维,然后才能到达感光细胞。感光细胞本身是朝着后脑勺的,背对着光源。
更扯的是,所有的神经纤维要从视网膜前面汇聚到一起,然后穿出眼球连到大脑。这个穿出的地方,没有感光细胞,就是我们的盲点。
![]()
你当场就拍桌子了:"这谁设计的?把感光细胞反过来装?这不是脑子有坑吗?"
老员工慢悠悠地说:"这是五亿年前定的架构。那时候我们还是鱼。"
![]()
"五亿年前...那改过来不就行了?"
"改不了。"老员工摇摇头,"你想想,要把视网膜翻过来,是不是得先让感光细胞在中间过渡一下?就是那种半朝后半朝前的状态?"
"对啊。"
"问题是,半朝后半朝前的鱼,视力是不是比朝后的还差?"
你想了想:"...好像是。"
"那不就结了?五亿年前那条想把视网膜翻过来的鱼,因为中间状态视力太差,被吃掉了。这条路就断了。后面所有的脊椎动物,就只能在这个错误的架构上继续优化。你现在想翻?你翻一个试试?中间状态直接失明,活不到下一代。"
你沉默了。
这时候你想起之前看过一个竞品的代码,章鱼的眼睛。人家的视网膜就是正着装的,感光细胞直接对着光源,没有盲点。
你问老员工:"章鱼跟我们不是一起演化的吗?为什么人家就能设计对?"
![]()
老员工说:"章鱼是单独演化出的眼睛。人家走的是另一条路,一开始就选了正确的方向。我们这条路,五亿年前就锁死了。"
你又想到一个问题:"那我们复制代码的时候,是不是每次都会丢一点?"
老员工看了你一眼:"你怎么知道?"
"猜的。"你说,"这种祖传项目,十有八九都有这问题。"
没错。线性染色体每次复制的时候,末端都会丢一小段。因为DNA聚合酶的工作方式,就是没法复制到最末端。
那怎么办呢?
解决方案就是在染色体的末端,加一堆无意义的重复序列,叫端粒。
就像你写代码的时候,在缓冲区前面多加一堆没用的占位符,每次复制就消耗一点占位符,反正占位符丢了不影响正文。
但是问题来了,占位符是有限的。
端粒会随着细胞分裂越来越短,短到临界点,细胞就衰老死亡了。
![]()
你总结了一下:"所以这就是个内存泄漏?用预分配一大块缓冲区慢慢消耗来规避,而不是真正修复?"
老员工竖起大拇指:"你悟了。"
你这时候已经有点崩溃了。你说:"就没有什么模块是设计得合理的吗?"
老员工想了想:"也不是完全没有。比如...呃...你看这个喉咙的神经。"
你翻到喉返神经那一段。
看了半天,你没看出问题。
老员工给你指了一下:"你看,控制喉咙的神经,从大脑出来之后,不是直接连到喉咙,而是往下绕了一个大弯,从主动脉下面钻过去,再绕回来,才到喉咙。"
![]()
你愣了:"为什么要绕这么大一圈?直接连过去不行吗?"
"直接连过去当然好。"老员工说,"但是改不了。"
"为什么?"
![]()
"因为这条神经,四亿年前我们还是鱼的时候,就是直接连到鳃的,位置就在心脏旁边。后来我们上岸了,脖子变长了,心脏往下移了,这条神经就只能跟着往下绕。你想让它直接连喉咙?那中间过渡的形态,要么神经接不上,要么血管接不上,反正活不了。"
![]()
你算了一下,长颈鹿的喉返神经,足足有四米长。就为了控制喉咙的一小块肌肉,神经从大脑下来,绕心脏一圈,再上去四米。
你说:"这不是局部最优陷阱吗?"
老员工点点头:"对。演化就像一个盲人爬山,他只能往高处走,看不到远处更高的山峰。要到那个更高的山峰,得先下山,但下山就意味着适应度降低,直接被淘汰了。所以他就只能在离出发点最近的那个小山头上待着,永远下不来。"
这时候你又想到一个更严重的问题:"那...两个模块之间如果有冲突怎么办?"
老员工说:"你已经发现了?"
你说:"产道和大脑。"
没错。直立行走要求骨盆窄,因为走路需要。但是大脑演化要求头大,因为聪明需要。两个需求直接打架。
结果就是人类分娩极度痛苦,而且危险。现代医学出现之前,产妇死亡率长期在1%到2%每胎。就是说,一百个产妇里,就有一两个会死。
你说:"那把骨盆改宽一点不行吗?"
![]()
"改宽了走路就不稳了。"老员工说,"骨盆涉及三个功能:行走、支撑内脏、分娩。你优化任何一个,另外两个都受影响。这就是紧耦合。"
"那把脑袋改小一点呢?"
"脑袋小了就不聪明了,直接被淘汰。"
你叹了口气:"所以就只能这样了?女人生孩子跟鬼门关走一遭似的,就因为两个模块打架改不了?"
老员工没说话。
你继续翻代码,翻到免疫系统的时候,又发现一个bug。
免疫系统的设计思路是:随机生成各种各样的抗体,什么敌人都能识别。理论上,生成完之后要把那些攻击自身组织的抗体过滤掉。
但是这个过滤机制不完美。
结果就是,红斑狼疮、类风湿关节炎、多发性硬化这些病,本质上都是免疫系统把自己人当成敌人打了。
你说:"防火墙规则写错了,把自己IP也当敌人了?"
老员工说:"差不多。"
这时候你已经不想说什么了。你翻到阑尾、智齿、尾椎这几个模块。
阑尾,盲肠退化的残留,平时没用,发炎了还要命。
智齿,颌骨缩小之后塞不下的多余牙齿,长的时候疼死个人。
尾椎,尾巴退化剩下的几块骨头,摔一下疼半天。
全都是功能废弃但没清理干净的反模式。
你问老员工:"为什么不删了?"
老员工说:"删了可能也没啥事。但是没人敢动啊。万一阑尾其实还有个免疫功能呢?万一尾椎其实是支撑什么结构的呢?演化都留了这么久,肯定有它的道理。"
你说:"这不是冗余设计吗?"
"对。"老员工说,"冗余就是保险。"
这时候领导过来了,看你一脸生无可恋的样子,说:"怎么了?是不是觉得这系统太烂了?"
你说:"何止太烂,这就是个屎山。要不我们重构一下吧?从头写一个最优版本。"
领导笑了:"你确定?"
"我确定。"你说,"我先做个最小可行版本,把所有非必需的基因都删了,只留核心功能。"
领导说:"行啊。之前有人做过类似的事。"
你说:"谁?"
"Craig Venter。2010年的时候,他的团队做合成生物学,想设计一个最小基因组的细菌,把所有非必需的基因都剥掉,只保留核心功能。"
"结果呢?"
"结果他们发现,有将近三分之一的'必需基因',他们根本不知道是干什么的。但是一删,细菌就死了。"
你愣了:"不知道是干什么的,但是删了就死?"
"对。"领导说,"我们连一个细菌都没法完全理解,何况是人。"
你沉默了半天,说:"那...那些看起来没用的假基因、重复序列、内含子,其实也不是完全没用?"
领导说:"你想想。那些假基因,有时候会被重新激活,变成新的功能。那些重复序列,给了基因重组变异的空间。甚至那些古代病毒留下的DNA,比如胎盘里的syncytin蛋白,就是来自病毒的。没有那个病毒基因,哺乳动物根本就不会有胎盘。"
你说:"所以如果我们按'当下最优'把这些都删了,现在是效率高了,但是环境一变,比如来个新病毒,或者气候剧变,这个过度精简的物种就没有应对的余地了?"
领导点点头:"冗余是演化的保险。你把保险撤了,跑是跑得快,出事的时候就没救了。就像软件工程里的过度优化,把所有东西都内联,去掉所有抽象层,短期跑得快,但后续完全改不动。"
你又想到一个问题:"那...我们改某个基因的时候,为什么总是牵一发动全身?"
领导说:"因为基因不是独立的乐高块。一个基因往往影响几十个性状,一个功能往往依赖多个基因。你想优化A功能,可能意外就把B功能搞坏了。"
"那不能做测试吗?改完先在测试环境跑一跑?"
领导看了你一眼:"没有测试环境。"
"什么?"
"所有演化实验,都直接在生产环境上跑。"领导说,"失败的代价就是死亡。你想做个分支版本,测试通过再合并?不行。任何需要先倒退再前进的重构,都做不了。"
你说:"那项目经理呢?项目经理总该做个长期规划吧?"
领导笑了:"没有项目经理。"
"什么?"
"演化是个贪心算法。"领导说,"它只看当下,谁现在能活下来多生孩子,谁的基因就留下来。它没法说'我先忍几代把视网膜翻过来,子孙后代受益'。任何中间过渡形态如果竞争不过同代对手,这条路就断了。一旦走上某条路,哪怕后来发现是坑,也只能在坑里继续优化。"
你瘫在椅子上,半天说不出话。
你说:"所以...这系统这么丑,其实是所有约束条件的必然结果?"
领导说:"对。不能停机,不能回滚,不能预先设计,只能靠随机变异和残酷淘汰一步步爬出来。任何看起来冗余、绕路、不合理的地方,背后都可能藏着我们现在还看不到的约束。"
你说:"那如果我们真的从头写一个最优的基因组,是不是比这个强多了?"
领导摇摇头:"不一定。"
"为什么?"
"首先,'最优'对谁最优?"领导说,"个体寿命最长?繁殖效率最高?对环境变化最鲁棒?能耗最低?演化潜力最大?这些目标互相冲突。追求长寿就要降低突变率,但这会牺牲演化速度,环境一变就灭绝。追求繁殖快就要简化结构,但简化的代价是鲁棒性下降。没有统一的最优定义,任何设计都是多目标妥协。"
"其次,你怎么预知未来?那些看起来没用的垃圾DNA里,藏着演化的原材料。你今天删了,明天环境变了,就没东西可用了。"
"第三,复杂系统本身就不可预测。我们连一个细菌都没法完全理解,你凭什么觉得自己能设计出一个最优的人类?"
"第四,工程师有个共同盲点,人类设计的系统,容易在设计时没想到的场景下崩溃。飞机、核电站、操作系统,都出过这类事故。演化出来的系统相反,常见场景下可能效率不高,但极度鲁棒,因为所有能活到现在的生物,都被无数种极端情况筛选过。一个从头设计的基因组,没有经过足够长的真实环境测试,大概率会在某些人类没想到的边缘情况下崩溃。"
![]()
你听完,沉默了很久。
你想起自己之前接手的那些祖传项目。每一行看起来丑陋的代码背后,其实都藏着一个当年的生产事故。你觉得它丑,是因为你只看到了代码,没看到它经历过的那几十次宕机、几百次报警、几千次用户投诉。
你想重写一遍?
行啊。重写一遍,通常就是把那些教训重新踩一遍。
人类基因组也是这样。
你说它是屎山代码,从代码洁癖的角度看,没错。
![]()
视网膜装反了,端粒内存泄漏,假基因满地都是,内含子废话连篇,产道和大脑打架,阑尾智齿尾椎全是垃圾,免疫系统偶尔打自己人,还有8%的古代病毒残骸,随便哪一条,放在软件工程里都是要被代码审查骂死的。
但是它跑了38亿年不崩溃。
这本身就是一种我们工程师造不出来的奇迹。
![]()
更准确的说法可能是:人类基因组是一套没有文档、没有版本控制、靠38亿年线上热更新演化出来的遗留系统,屎,但是能跑,而且跑得还挺稳。
你问我要不要重构?
别闹了。你敢动吗?
特别声明:以上内容(如有图片或视频亦包括在内)为自媒体平台“网易号”用户上传并发布,本平台仅提供信息存储服务。
Notice: The content above (including the pictures and videos if any) is uploaded and posted by a user of NetEase Hao, which is a social media platform and only provides information storage services.