真正的挑战并非交付一个能运行的程序
编写软件是任何人都可以进行的活动。你只需坐下,打开电脑,启动一个 IDE 或编辑器(即使是记事本也可以),然后开始打字。第一个“Hello World”总是神奇的时刻,但这仅仅是开始。
一旦克服了最初障碍,你会发现编写软件是一个持续学习和适应的过程。你学会了基础知识:框架、库、设计模式。你理解了如何构建代码、如何模块化、如何测试。最终,经过几天或几周的工作,你得到了一个能运行的东西——也许不完全如你所想,但它能运行。
但今天我不想谈这些。让我们暂时放下编程的浪漫面,不谈软件是如何构建的。我想讨论的是编程中最复杂、最少被提及的方面之一:移除代码的勇气。
真正的挑战并非交付一个能运行的程序
结果只是构建软件的第一步。任何程序员都会说“我能做到”。真正的挑战是不被自己的软件淹没:不失去对它的控制,不把一个简单的 10 行 REST 服务变成一个吞噬磁盘的怪物。
软件的增长是熵,而资深程序员的工作就是对抗这种熵。
代码通常被比作资产,而实际上它是负债。如果我们认为写的代码是资产,就失去了整个创作过程中最基本的方面:当我们编写代码时,我们是在创造债务。每一行代码都必须被阅读、理解、维护、测试,并且有一天会被更新或移除。
代码越少,漏洞的攻击面就越小,维护成本也越低。
这一点在企业环境中经常被低估,那里的重点是“交付功能”而不是“维护健康且可管理的代码”。
我有幸/不幸地跟进了两种类型的项目。一种只有唯一目标:交付,最终在数吨不协调的代码中内爆;另一种则持续进行重构、移除过时代码并保持干净可维护的代码库。这两个世界的差异是巨大的。
我们都是“囤积症患者”
你是否知道电视节目“囤积者”?那些积累无用物品、填满房屋直至无法居住的人。软件也患有同样的病症。
无论是我们编写,还是更糟的,由 AI 编写,代码量都会开始增加。首先是几个类、几个函数和几个脚本,但随着时间的推移,我们不仅会淹没在自己的代码中,还会淹没在外部的依赖、库、模块、插件中。
VibeCoders 的读者可能还不理解我,但随着时间的推移,他们会意识到 AI 只有一个目标:生成能运行的代码,如果不能,就说服自己它能运行。
AI 的最终目标是优化代码或降低其复杂度:这个方面对它们来说并不重要,并且目前完全委托给我们人类。 如果“人类”不担心这个问题,AI 就更不会为我们担心了。
这正是我们角色发生根本变化的地方。生成式 AI 是强大的“加法工厂”,能够在几秒钟内写出数千行代码。这将人类的角色从“作者”转变为“编辑”或“策展人”。我们最重要的技能将变成验证、简化和拒绝 AI 生成代码的能力,而不仅仅是生成它。
如果你想知道自己是否处于悬崖边缘或自由落体状态,试试这个:检查你某个项目的 node_modules 文件夹,或者由 Maven 或 NuGet 创建的文件夹。在我一个相当简单的项目中,我有 1 MB 自己的代码和 20 MB 的 node_modules。感觉已经失控了,而我的项目才刚刚开始。
你是否曾想过:“为什么我三行的‘Hello World’需要 200 MB 的依赖项?”如果没有,请想一想。你会发现一个新世界,一个经常被低估的、可以将一个简单项目变成维护噩梦的世界。
与其他程序员聊天时,我经常听到这样的话:
我没有从 Vue 2 切换到 Vue 3,因为我有 18 个依赖项,如果不重写一半的项目就无法更新它们。
这听起来像借口,但这是许多开发人员每天面临的现实。
功能蔓延:无声的敌人
我们每天必须问自己的问题是:“这真的必要吗?”
答案通常是“不”。但对这个“不”采取行动成本很高。向客户或产品经理解释需要坚实的技术和口头理由,而很少有人有勇气或力量去坚持。
“功能蔓延”不仅是代码问题,也是产品问题。一个拥有太多按钮、选项和配置的用户界面也遭受着同样的问题。“移除”的勇气也适用于产品经理,为了保持产品的专注和简单而对新功能说“不”。
同时,要警惕那些告诉你的人:
问题在于你的胖客户端,而不是你使用的依赖项。
这只是将注意力从真正问题上转移开的方式:代码和依赖项的失控积累。这并不是说一种架构比另一种有优势——系列积累的问题依然存在。
多余的功能和失控的依赖使一切都复杂化。软件变得脆弱、缓慢、难以维护。技术债务在无形中增长,直到无法忽视。
维护的噩梦
只要我们“专注于项目”并每天处理功能和重构,我们就有额外概率管理混乱。但如果那个项目被搁置数月后重新拾起,或者团队变动很大,不可避免的后果就是混乱和技术债务的增加。
导入的依赖项数量越多,跨领域过时的问题就越大。
但“依赖”不仅仅是我们用来解析 JSON 文件的库 X 或 Y。让我们想想整个生态系统:语言版本、库、框架、工具链、构建系统、执行环境等等。
有时你会陷入技术死胡同:我们需要的更新与另一个基础库冲突,后者尚未更新或其维护者已消失。是否是开源的并不重要——这在软件开发中也是一个被高估的概念。
那时,选择就很少了:重写部分软件、接受安全漏洞,或者保持原样,希望它不会崩溃。
为什么我们不移除?
但我们为什么不移除呢?因为我们害怕。 害怕破坏某些东西(“如果这段代码是为了那个我没测试过的案例 X 而需要的呢?”)。
要有勇气移除,需要两样基本的东西:技术安全和心理安全,但最重要的是去做的意愿。
还需要一种奖励“减法”的企业文化。
在许多公司,生产力是以“交付的功能”或“关闭的工单”来衡量的。但有多少次程序员因移除 1000 行过时代码而受到表扬?一个“-1000 行”的 Pull Request 应该被庆祝。我们需要一种文化,将重构和清理视为开发过程中不可或缺且高尚的部分,而不是“杂务”。
移除的勇气
真正的程序员是那些能够做减法的人。
Unix 和 C 语言的联合创始人 Ken Thompson 曾说:
我最有效率的日子之一是删除了 1000 行代码。
我不知道这个说法有多真实,但概念是强有力的。真正的精通不在于添加,而在于知道如何在不失去功能的情况下移除。
发明快速排序的 Tony Hoare 也有一句名言:
有两种构建软件设计的方法:一种是让它非常简单,以至于明显没有缺陷;另一种是让它非常复杂,以至于没有明显的缺陷。第一种方法要困难得多。
怎么能责怪他呢?毕竟,在计算机科学生涯之前,他的古典文学学位或许让他在理解简洁的优雅方面占据了优势。
但既然没有不带刺的玫瑰,除了这颗宝石之外,Hoare 还留给了我们现代计算的最大悲剧之一:“空指针”的概念。如果它曾毁掉你的一天,现在你知道该感谢谁了。
只有勇者才做减法
回到我们。我们具体如何移除?
第一步是移除无用的代码。这看起来很容易,但正是在这里,结构性和人为的阻力被触发。要赢得这场战斗,需要合适的武器:
- 强大的自动化测试:一套测试套件,能让你确信,如果删除了某个必要的东西,测试会失败,从而保护我们免受意外回归的影响。
- 心理安全:一种工作环境,在这种环境中,在测试或预发布环境中“破坏”某些东西不是犯罪,而是学习和清理过程的一部分。
- 持续的使用验证:有多少功能是根据今天已不再是客户的请求而实现的?有多少流程已无人使用?提出这些问题并采取相应行动至关重要。
- 面对“如果有一天需要怎么办?”的恐惧:这是不删除代码最常见的借口。剧透:那一天几乎永远不会到来。即使到来,需求也会发生巨大变化,以至于从头重写代码仍然是最好的选择,而不是去调整一个陈旧且被遗忘的解决方案。为“未来实现”而编写的代码是对未来的抵押,很少会被赎回。
下一步是简化复杂逻辑并最小化依赖。有一些优秀的软件设计案例旨在实现零依赖或极少依赖,以方便分发和维护。
每个有追求的软件项目都应投入时间来消除和清理,就像一种神圣的仪式。每一行代码都是一个负担,一份我们承担的责任。我们添加的每一行,都是将来必须维护、测试和调试的一行。
删除并不可怕。使用 Git 这样的版本控制系统,我们总是可以恢复已删除的内容。但事实是,十有八九,那段代码只是死重。
真正的精通在于简洁
添加很容易。每个人都擅长添加。但是移除呢?这才是真正的较量。
高级开发人员的精通程度,与其说取决于他们能写多少代码,不如说取决于他们在保留基本功能的前提下能移除多少代码。
阻碍我们的是对“我不会移除它,因为也许明天会需要”的恐惧。但那个“明天”几乎永远不会到来。正如 YAGNI 原则(你不会需要它)所说:如果你现在不需要它,就不要实现它。
真正的价值不在于添加,而在于提炼精华、消除噪音,并构建真正有效的东西。这就是移除的勇气。这正是一个有见地的程序员与一个只看到眼前周末的人之间的区别。