软件的腐烂,都是从“多一个开关”开始的...
程序员哄骗自己的鬼话:这个开关只是临时的!
为什么你的软件设置菜单比迷宫还难懂?
该文探讨了软件开发中过度使用配置开关(Flag)导致的复杂性和维护灾难。文章用大量生活化比喻解释了开关如何从“救星”变成“灾星”,并给出了健康管理开关的建议。适合所有被复杂软件配置困扰的开发者和用户阅读。
开关烂掉的地方,就是软件发臭的起点
你有没有遇到过这种软件?打开设置页面,密密麻麻的选项比超市货架上的零食还多。什么“开启实验模式”、“关闭旧版兼容”、“调整内存缓冲块大小”,看得人脑壳疼。程序员们总觉得,给用户越多选择,软件就越牛。
但现实往往是反着来的。给软件装上一大堆开关,就像你养了一屋子宠物。刚开始觉得热闹,过不了多久,光是喂食铲屎就能把你累趴下。每个开关看着不大,凑到一起就是个大麻烦。
很多人觉得,多个开关多条路。万一默认设置不好用,用户自己改呗。这话听着有道理,但软件不是乐高积木,想怎么拼就怎么拼。每个开关背后,都藏着一段说不出口的纠结:“我们也拿不准这么干对不对,要不你们自己看着办?”
说白了,开关就是程序员把本该自己做的决定,偷偷塞给了用户。
开关是怎么从救星变成灾星的
给软件加开关,理由通常都挺正当。比如搞了个新功能,不知道大家喜不喜欢,先藏在一个开关后面,让胆子大的用户先试试水。或者要改个老毛病,但又怕有人离不开旧习惯,只好留个“经典模式”的念想。
这些都是好心。问题在于,好心的开关一旦出现,就很少会消失。它像个赖在你家沙发上不走的亲戚,时间越长,存在感越强。
一开始,你可能只是觉得多写几行 if 代码没什么。但很快你会发现,写帮助文档的时候得提一嘴这个开关。客服回用户问题的时候,第一句就得问:“请问您那个某某开关开了吗?”
更麻烦的是修Bug。以前只要看一种情况,现在开关一开一关就是两个世界,测试人员得把两个世界都跑一遍。这还没完,后面再加新功能,也得问一句:“我跟那个旧开关合得来吗?要不要再做个开关来配合这个开关?”
你看,一个开关就像在平静的水面扔了颗石子,涟漪会一圈一圈扩散到软件的每个角落。
这还不是最要命的。当开关不止一个的时候,真正的噩梦才开始。比如开关A控制超时时间,开关B控制数据缓存方式,开关C控制CPU核心数。
单个看,每个都很乖。但它们组合在一起,就像把音乐、数学、语文课本混在一起复习。用户可能为了追求极限性能,把A调到最快,B开到最大,C搞成最激进。这个组合,程序员可能从来没测过,甚至做梦都没想到过。
这时候用户跑来说“软件崩了”,你问他是怎么设置的,他给你发来一串配置代码。你看着那串代码,就像看一串外星密码。
这就是开关的可怕之处。它创造了一个数学题都算不清有多少种可能的配置空间。在这个空间里,藏着无数种你根本没测试过的“怪胎组合”。哪天用户碰巧撞上了,那就是你的锅。
而且这些奇葩组合往往是“自然演化”出来的。开发这个功能的程序员早跳槽了,写那个功能的大佬也转行了。但开关还在,组合还在,Bug也在。
就像你家楼下那条被无数人走出的小路,没人设计过它,但它就在那儿,而且你还得负责把它修成正经马路。
开关是如何变成化石的
最让人头疼的开关,往往来自好心办坏事的开源项目。有人提了个小众需求:“能不能让我的数据库在每次保存文件前先放个礼花庆祝一下?”
正经的开发肯定说不行。但很多时候,大家为了不得罪人,或者觉得这功能反正没几个人用,就加了个 --fireworks-on-save 开关。
提需求的人开心了,写代码的人也觉得自己做了一件好事。但五年后,那个想要礼花的人早就不见踪影了。可这个开关却像钉子户一样留了下来。
更惨的是,后来有个新人想优化保存文件的代码,突然发现这里有个诡异的开关。他不敢删,因为不知道世界上还有没有哪个鬼才在用它。他也不敢改,因为一改可能就炸了礼花。
最后的结果就是,那段本可以写得很漂亮的代码,为了伺候一个没人用的开关,变得臃肿又丑陋。
这就像你为了放一个不用的旧柜子,把整个客厅的布局都搞乱了。
其实,开关还常常是问题的烟雾弹。如果用户动不动就要关掉某个模块才能让软件跑起来,那可能不是开关的问题,而是那个模块本身就有病。
如果软件跑起来必须要靠调十几个参数,那说明默认设置可能烂到家了,或者是软件的设计本身就像纸糊的房子,一吹就倒。
如果为了兼容旧版本,你需要搞出三层“祖传模式”开关,那说明新旧代码就像油和水,根本没真正融合过,只是被胶水粘在了一起。
开关可以解决现实问题,但它也经常被用来掩盖设计上的懒惰。它像止痛药,能让你暂时不疼,但病根还在。而且吃多了会上瘾,最后变成不吃药就活不下去。
那我们还能不能愉快地加开关了
所以,是不是应该提倡“零开关”主义?当然也不是。一棍子打死所有开关,就像因为怕被鱼刺卡住就不吃鱼了,因噎废食。
开关本身无罪,但用它之前,你得把它当成刷信用卡消费。爽是一时的,债是要还的。
每个新开关诞生的那天,就应该给它想好“葬礼”的日子。你得问自己几个直击灵魂的问题:这开关为什么活在这个世上?到底是谁真的需要它?如果明天我把它强行删了,哪个倒霉蛋的程序会炸?
如果这些问题一个都答不上来,那这个开关就是个还没成型的化石。趁早把它扼杀在代码审查的摇篮里。
更聪明的方式是,试着跟不确定性做朋友,而不是到处甩开关。默认值尽量做到最聪明,让大部分用户根本不需要进设置页面。
如果非要有开关,尽量让它们保持独立,别搞成多米诺骨牌,一碰倒一片。最重要的是,要定期搞大扫除。
在每个新版本发布前,像个考古学家一样去挖一挖,看看哪些开关已经几年没人碰过了。确认它没用了,就果断删掉。哪怕会引起少数几个“化石级”用户的不满,也比你整个软件变成一座无法维护的化石博物馆要强。
毕竟,维护软件的是活生生的人。让程序员为了一个可能根本没人在用的开关而掉头发,这事儿本身就不太人道。
软件应该像一把锋利的刀,而不是一把什么都能干但什么都干不好的瑞士军刀。开关多了,刀就钝了。刀钝了,切什么都费劲,最后只能放在角落里生锈、发臭。
所以,下次你想往代码里加开关的时候,先停一停。问问自己:这到底是给用户的自由,还是给未来的炸弹?
Reddit网友观点
这些观点从不同角度对原文进行了反驳、补充或调侃,可以看作是来自真实开发者世界的“回音”。
核心反驳:理想很美,但现实很骨感
大部分高赞评论都认为原文观点过于理想化,只适用于极其简单的软件场景,忽略了现实世界的复杂性。
1. 硬件和环境的多样性(最有力的反驳)
* 网友指出,在处理硬件(如GPU、路由器、嵌入式设备)或不同平台(如Android、车载系统)时,配置开关是必需品,而非“不确定性”。
* 同一个二进制文件需要运行在成百上千种不同的硬件组合上(不同型号的芯片、传感器、内存大小),不可能为每一种都单独编译一个版本。配置开关(或配置文件)是告诉软件“现在是什么硬件、该启用哪些功能”的唯一方式。
* 例子:一个视频游戏需要知道玩家用的是哪款NVIDIA显卡;汽车固件需要知道这辆车卖到了哪个国家(法规不同),以及车上装了哪些具体的硬件。
2. 法律、合规与客户合同
* 大型企业软件或政府项目,往往有强制性的合规要求,必须通过开关来开启/关闭特定功能。
* 有时候,客户付了钱,合同里白纸黑字写明了需要某个特殊功能,开发者就必须提供一个开关来单独启用它。这不是“不确定性”,这是“履约”。
3. 原文混淆了不同类型的“开关”
* 很多网友认为,原文把“功能开关”(Feature Flag,用于灰度发布、A/B测试)、“编译时选项”(如GCC的-O2优化开关)、“运行时配置”(如超时时间、线程数)和“兼容性开关”(Backward Compatibility)全搅在一起讨论了。
* 这些东西的性质完全不同。例如,编译器的-march=native开关是为了让你的代码在你的CPU上跑得更快,它永远不可能成为默认值,也永远不需要被移除。
4. “不配置”不等于“更简单”
* 认为软件自己就能“猜”对用户想要什么,是天方夜谭。软件怎么知道用户是想把文件存在本地还是云端?怎么知道用户喜欢什么字体?
* 强行移除配置开关,只会把配置的复杂性从“界面选项”变成“修改源代码”,这对于普通用户来说是一场灾难。
除了严肃反驳,还有很多网友用幽默的方式表达了类似观点。
* DNA 比喻:有网友借用原文的“化石”比喻,调侃说人类DNA里大部分都是“垃圾”和不活跃的片段,如果按照原文作者的理论,为了“干净”就该全删了。但现实中,这些看似没用的片段一旦删除,可能会导致整个生物体崩溃。
* “Works on my machine”:有网友总结,原文的终极观点就是“在我电脑上能跑就行”(Works on my machine),完全无视了软件要在千千万万台不同电脑上运行的现实。
* 下次博客标题预测:有人开玩笑说,作者下一篇文章的标题会是《产品团队想毒死我》。
也有一部分网友认为原文有道理,但需要加上重要的前提。
* 开关是债务,需要管理:赞同“开关应该像债务一样对待”,引入要有计划,移除更要有计划。但问题在于,现实中,移除开关的优先级永远是最低的。
* 区分“用户开关”和“开发者开关”:有网友分享了一个好做法:所有新开关默认都放在“隐藏页”。只有当收到真实的客户请求后,才把它公开。并且定期统计,从来没人改过的开关,就降级回硬编码或直接删除。这既保持了灵活性,又控制了膨胀。
* 开关暴露了架构问题:有条件地同意原文的观点:如果你发现自己需要一大堆开关才能让软件跑起来,那很可能不是开关的问题,而是软件底层设计本身就烂了。开关只是在给这个烂设计“打补丁”,而不是解决问题。
总之
Reddit网友的主流观点是:原文作者指出了“滥用开关会导致维护噩梦”这个真实存在的问题,但他提出的“解决方案”(让所有开关都有过期时间,甚至尽量少用开关)过于天真,在大多数现实世界的软件开发(尤其是底层、硬件相关、企业级软件)中根本不切实际。
大家普遍认为,开关本身不是坏东西。真正的坏东西是:
1. 没有计划、无限期存在的临时开关。
2. 互相依赖、错综复杂的开关组合。
3. 用开关来掩盖糟糕的设计和偷懒。
所以,开发者们不是在反对“少用开关”,而是在反对“因为有人滥用锤子,就说所有锤子都是坏工具”的极端观点。