🟡 进阶

怎么给一个模块定边界

不讲"高内聚低耦合"这类词(说的是"每块只干一件事、块和块别绑在一起";这类词你大概听过,但听完还是不知道明天该动哪一行)。这一篇只干一件事:教你在一个已经乱掉的项目上,划出"这一块归谁管、别人只能从哪几个口子进它"——然后验证你划对了没有。重点是从乱代码出发,不是从零设计。

一个词先说清楚:"模块"就是"一块能单独拿出来说的东西"(可能是一个文件、一个页面上的东西、一个动作,或一个后台服务,§02 会展开)。整篇只需要这一个词加一个"口子",别的术语一个都不用记。

更新于 2026-09-16
1 句
判断法,背下来就够用
4 步
从乱代码到划完边界
3 处
这个站里同一张卡片被几处依赖
1972
年就有人把这件事写清楚了

01速览、起手动作、以及这篇假设你有什么

读完这一篇,你能做到一件事:拿你手上任意一个已经乱掉的项目,挑出一块该收口的东西,把它变成"负责一件事、别人只从一个口子进"的样子——而且知道怎么验证你没收错、没收过头。

🎯 先记住这三句

① 边界不是一个词,是两个问题的答案:这一块负责什么?别人从哪几个口子用它?两句话都答得出来,它就有边界;答不出来,就没有。

② 判断法只有一句:"这东西,别的地方从哪几个口子用它?"——答不上来 = 它没有边界。注意是数"口子的种类",不是数"被引用的次数"(这两者的区别是全文最容易搞错的一处,见 §04)。

③ 从乱代码出发,顺序是反的。不是"先想清楚再动手",而是:先钉住它现在的样子 → 再把口子收窄 → 里面慢慢变干净。而且一次只动一块。

"口子"到底指什么:别人找它的时候,必须穿过的那几样东西——它叫什么名字、你递给它什么、它还你什么、用它的先后顺序、出错时它会怎样。§02 会用一家饭馆把这件事讲透。这篇里你只要记住这一个词就够,不需要任何别的术语。

如果你只有 5 分钟

只看两处:§04 那个判断法(怎么数口子),和 §05 的动作一、动作二(怎么选一块、怎么在动它之前先钉住它)。其余部分是你真动手那天回来查的。

起手动作:读完 60 秒内你要做的第一件事

不要读代码(你不读代码),也不要先想"我的项目该怎么整理结构"。你就打开一个项目,把下面这段话原样发给你的 AI(这段要发给 AI 的话,下文都叫"提示词"——就是你要打给 AI 的那段字):

# 起手提示词(原样复制,把"这个项目"换成你的项目文件夹)
看一遍这个项目。列出被最多地方用到的 5 样东西。
对每一样,写清楚三件事:
1) 它是什么(一句话,用日常话,不要用专业词)
2) 哪些文件用到它
3) 用到它的每一处,各自从它身上"拿了什么"
   (是整块拿去用?只拿走里面某一项内容?只调用了它的某个动作?还是依赖它的文件名/格式?)
最后告诉我:这 5 样里,哪一样的"用法种类"最多、最杂。

你只需要看最后那一句的答案。那一样东西,就是你今天要划边界的那一块。(为什么是"用法种类最多"而不是"被用得最多",§04 会讲。)

⚠️ 做这个动作之前,你手上必须有什么(这一步比上一步重要)

必须有①:一个已经存在的项目文件夹。不需要能跑起来,不需要你懂里面任何一行。你手上那十几个半途停下的项目,随便挑一个都行。但必须是已经写了一些东西的——空文件夹不行,这一篇讲的是"从已有的乱代码里划",没有代码就没得划。

必须有②:一个能读整个文件夹的 AI。Claude Code、Codex 这类能打开整个项目目录的工具。只会在聊天框里贴代码的那种不行——它能看见一行,看不见"谁用了谁"。

最好有③:一张"哪几块 / 谁用谁"的粗略清单。这篇假设你已经看过 01《怎么给一个已经成型的项目画结构图》,手上有一张图。为什么它重要:看不见现状就划边界,等于闭着眼睛改结构——你会把"其实被三处依赖的东西"当成"没人用的东西"删掉。

没有 ③ 也能读这篇,但要先补 10 分钟。补法在 §04 末尾的"没有图也能开始",那里有一条提示词,能把最小版的那张图直接问出来。先跑那条,再回来。

这篇不适合谁

放在这里说,不占开头的位置:如果你手上还没有任何"写了一半、还想继续改"的项目,这一篇你读完也用不上——它是给"手上有一堆烂摊子、还得继续在上面加东西"的人写的。另外,如果你现在正打算把某个项目推倒重写,也不用划边界:推倒重来是另一条路(而且多半更贵,见 §08)。

02边界长什么样:两句话,不是一个词

先破一个误会。边界不是一个你能"设计"出来的东西,也不是"把东西分分层""让两块互不依赖""设计一套接口"这类说法(接口就是"口子"的正式叫法;这些说法各自都在讲同一件事,只是换了词——§10 有一张对照表)。它是一个能被你肉眼验证的状态——验证方式就是那两句话。

先说"一块"是什么

这篇里说的"一块",不一定是别人嘴里那个"模块"(模块:一块能单独拿出来说的东西)。它可能是:

判断标准只有一条:你能把它当成一个东西来说吗?"这块管的是文章卡片"——能这么说,它就能是一块。你不需要先学会技术上的分类,只要认出"这一块"就行。

用一家店来理解"边界"

你见过的小饭馆,几乎都有边界,而且你从来没觉得那是个"设计":

有边界的一张桌子

顾客只知道四件事:从哪个门进、怎么点、怎么给钱、怎么拿走。这四件事就是这家店的口子。

后厨里谁切菜、用几口锅、先炒哪个——顾客不知道,也没人问。所以后厨换了主厨,顾客第二天照样来。

没有边界的一张桌子

顾客要自己进后厨拿酱油、自己找碗、自己开火。每个人进后厨拿的东西还不一样:有人拿酱油,有人拿锅,有人把火关了。

结果:换一口锅,三个老顾客都吃不上饭了。而且没人觉得是自己的错——他们只是"照以前的习惯拿了东西"。

把这张图搬到代码上,"口子"具体是什么?就是别人找它的时候,必须穿过的那几样东西。包括:

这四样加起来,就是这个口子的全部。其中任何一样变了,用到它的地方就可能坏。换句话说——"口子"就是你改它之前必须通知的那些人。这句定义比什么术语都好用,后面全靠它。

所以"有边界"就是三个状态

状态长什么样你怎么一眼看出来
说得清它负责什么 一句话:这块管文章卡片 如果你要用"它管卡片、顺便也管排序、也管日期格式、也管那个报错提示"才能说完整,它就没有边界——它是一团
所有人都从同样的一两个口子进 三个用它的人,都走同一个入口 如果三个人进的方式各不相同(一个拿整块、一个只改里面一项内容、一个依赖它的文件名),那它没有边界
改里面,外面不用知道 你换了里面的做法,外面三处一行都不用改 这是最实用的验法:你改完,需要通知几个人?答案是 0,就有边界
📌 这个类比到这里就不准了

饭馆的门是物理固定的,你开业那天门就在那儿了,改不了。代码里的口子是你自己划的,划错了可以重划——这既是好事(你能救),也是坏事(所以"没人拦着你乱划")。

还有一处不一样:饭馆通常只有一个门。代码里的一"块"常常需要两三个口子,因为"只看不拿"和"要改东西"经常得分开——让一个只想读的人也能改,是很多麻烦的开头。

03没有边界,你已经在付什么钱

你可能觉得"边界"是个讲究人的事。不是。没有边界,你此刻正在为它付四种账,而且每一笔你都以为是自己的问题。

账单 ① 改一处,别处坏

你让 AI 改个文案、调个顺序,它改完了,别的地方却坏了。这不是 AI 笨。是这一处的"里面"被好几个地方同时伸着手。

账单 ② 同一个毛病修三遍

上个星期修过的那个问题,这星期在别的地方又冒出来。因为同一条规则被三个地方各自写了一遍——你只修了其中一处。

账单 ③ 加个小功能要动五个地方

想在卡片上加个"置顶"标记,结果发现要改五个文件。这就是那句老话的日常版:修一个小问题要动三处以上。

账单 ④ 你不敢让 AI 动它

这一条对你最贵。你付了 AI 的钱,却只敢让它做"加个按钮"这种不痛不痒的事——因为你不知道它会碰到什么,也不知道碰坏了你能不能发现。

这四笔账有一个共同的来源

同一份东西,被好几处同时改。这正好是你如果做过 01(画结构图)时会画出的第三张表——"谁在改同一份数据"。那张表上连到同一个点最多的那些线,就是这篇文章要处理的那些块。

⚠️ 有一条容易搞反:这跟"代码丑不丑"没关系

你有过这种项目吧:代码写得很整齐,缩进漂亮,文件名也规规矩矩,但就是一改就出事。反过来也有:代码乱得像垃圾堆,但你从来只改那一个地方,改了两百次都没事。

所以不要拿"看起来乱不乱"当判断依据。边界只跟一个数字有关:改一件小事,要动几处。

这件事不是新问题:1972 年就有人给出了答案

这正是开头那个"1972"的来历。那一年,一位叫 David Parnas 的人写了一篇论文,专门回答"该怎么把一个系统拆成几块"(出处见文末)。

他的结论跟直觉相反:不要按"先做什么、后做什么"来拆,也不要按"哪几块看起来像一类"来拆。要按"哪些决定以后最可能被改"来拆——把最可能变的那部分收进一块里,只留一个口子露在外面。(他的原话大意是:每个模块负责把一个"以后最可能变的设计决定"藏起来,不让别的模块知道;而它露出来的那个"接口",要尽量少透露里面怎么做的。本文说的"口子",就是他说的"接口"。)

五十多年过去,这条结论没变过——你手上那些项目犯的毛病,也还是同一个。

04判断法:这东西别的地方从哪几个口子用它?

这是全文唯一一个你需要背下来的东西。把它写在一张纸上贴显示器上,比记住任何术语都有用。

先看你会得到哪种答案

你的答案说明什么接下来做什么
"就一个口子,别的地方都从这一处用它" 有边界。哪怕它代码很乱、很长、很丑,它也是有边界的 先别动它。它是你项目里的好资产,去处理别的块
"有好几个口子,但它们长得差不多" 半有边界。口子该合并成一个 这是最好做的一种:把几个相似的口子并成一个,外面改动很小
"别的地方是直接伸手进去拿里面的东西" 没有边界。最常见的那种乱,也是账单①的来源 这是 §05 要处理的主要对象
"答不上来,我得去搜一下才知道谁在用它" 也没有边界——而且更严重:连你自己都不知道谁在用它 先别改。先跑下面的提示词,把答案问出来

最后一行值得停一下。很多人以为"没边界"的意思是"结构不好"。不是。没边界的意思是"没人知道它和谁连着"——包括你,包括 AI。在这种状态下改它,不是勇敢,是赌。

最容易搞错的一处:数"种类",不是数"次数"

这是全文最值钱的一句区分,请慢一点读。

被用 40 次,但边界很好

某个"算价格"的东西,项目里 40 个地方用到它。但它们全都是同一种用法:递进去一个购物车,拿回来一个数字。

口子只有一个。你完全可以在里面大改(换算法、换数据结构),40 处一行都不用动——因为它们只知道"递进去、拿回来"。

只被用 3 次,但边界已经碎了

同样一个东西,只有 3 个地方用它。但是:第一处直接改它里面的某一项内容;第二处依赖它内部各项的排列顺序;第三处依赖它的文件名。

口子有三个,而且三个都不一样。你动其中任何一处,都要单独通知一个人——这三个人还都不知道彼此存在。

所以"被引用得多"根本不是问题。被引用得多、但用法只有一种,是好事——那叫好用,所有人都在用同一个口子。真正的问题是"用法有几种"。一种 = 一个口子 = 有边界。三种 = 三个口子 = 你得改三处、通知三个人。

怎么数:一条可以原样复制的提示词

# 数口子(把 X 换成你上一步圈出来的那一样东西)
我要给 X 定边界,先要知道它现在有几个"口子"。
请在项目里找出所有"用到 X"的地方(除了定义 X 本身的那一处)。

对每一处,写清楚三件事:
1) 是哪个文件、大概哪一段
2) 它从 X 身上拿了什么:是整块拿去用?只读了其中某一项?改了其中某一项?
   只调用了它某个动作?还是依赖了它的名字/顺序/文件位置?
3) 如果 X 改一个名字、或者把内部结构换个顺序,这一处会不会坏?

最后按第 2 问的答案分组(不是罗列),告诉我一共分成几组、每组有几个地方。
# 关键:它分出来的"组数",就是口子的数量。组数 > 2 就要收口。

怎么读这个结果:不要看它列了多少个地方,直接跳到最后一行的"分成几组"。几组就是几个口子。如果它只给你一个总数没分组,就回一句:"请按第 2 问的答案分组,不要给我总数。"

没有图也能开始(补 01 那条路)

如果你还没做过 01,手上没有那张图,用下面这条提示词,10 分钟能拿到这次需要的最小版:

# 最小版结构图(没做过 01 就跑这条)
看一遍这个项目,回答我三个问题,每个都尽量短:
1) 这个项目大致分成哪几块?每块用一句日常话说它在管什么。
2) 哪几块会用到别块?谁用谁?
3) 有没有哪一样东西,是好几块都在读、或者都在改的?列出来。

不要给我代码,不要给我建议,只要这三条的答案。

拿到第 3 问的答案之后,把里面"被好几块同时读或改"的那几样排个序——选其中你最常碰到的那一样,进 §05。

🎯 这一步真正的产出

不是一张好看的图,也不是一个数字。是你终于能说出"改 X 会波及哪几处"。在第 3 问的答案出现之前,你改任何东西都只能靠猜;出现之后,你是知道。

05从一堆乱代码出发:四步

先把话说死:这一篇讲的不是从零设计。你手上的项目都是已经存在的、一半还跑着、你也不想推倒重来的。所以下面四步的顺序,跟教科书里的顺序是反的:

教科书:先想清楚这块该负责什么 → 设计好口子 → 再写。
你:先把口子收窄(外面改动最小)→ 再让里面慢慢变干净 → 最后才写"它负责什么"那句话。

为什么反着来才对:因为你的目标不是"得到一份好设计",是下一次改它的时候别出事。而下一周就要改,等不到你先想清楚。

⚠️ 前置:做这四步之前,你要有这两样

一样是 §04 数出来的口子清单(哪几处、各自拿走了什么)。另一样是一个能跑起来的检查——如果不方便跑,至少要有"改完怎么知道没坏"的办法。第二样在动作二里造。

动作一:选出血点,不要选整个项目

你想划的那个东西,几乎一定是"整个项目太乱了"。那就一定做不完。所以第一步是砍范围。

1
在 §04 的结果里,挑"口子种类最多"的那一样

不是挑最重要的,不是挑最乱的,是挑口子最多的那个。理由很实在:口子最多 = 你每改一次它就要通知最多的人 = 它正在花你最多的钱。

2
把它缩到"一次能改完"的大小

如果这样东西太大(比如"整个用户系统"),就往里再选一层——选它里面口子最多的那个小东西(比如"用户头像")。标准是:这一块的所有用法,你今天下午能一个个看完。

3
写下来,写在你看得见的地方

一行字:"这次要收口的是 ______,它现在有 __ 个口子。"这句话是你后面所有判断的锚——AI 提出"顺手也改改别的"时,你就看这一行。

动作二:先钉住它现在是什么样,再动它

这是最容易跳过、也最贵的一步。跳过它,前面省下的两小时,会在两周后以"不知道哪儿坏了"的形式还给你。

为什么必须先钉住

改结构的时候,最常发生的失败不是"改坏了",是"改坏了你不知道"。它可能三周后才在某个页面上露出来,那时候你已经改了十几个别的地方,根本不知道是谁干的。

所以要做的是一件很朴素的事:在动它之前,先把"它现在是什么样"记下来。记下来之后,你改完再验一遍,两次结论一样 = 你没改坏行为,只改了结构。

🎯 这个东西有个名字:表征测试

表征测试(characterization test)就是"只描述现状的检查"。拆开看:"表征"就是"描述它现在长什么样","测试"就是一段能被自动跑起来、跑完告诉你"一样 / 不一样"的小程序。

它最反直觉的一点:它不管对错。哪怕它记下来的行为是个毛病,也照样原样记下来——因为你现在的任务不是修毛病,是不让改动出意外。修毛病是另一件事,别混在一起(混在一起你就分不清是哪边出的问题)。

发明这个说法的人(Michael Feathers)自己写得更直白:"它的目的是记录你的系统实际的行为,而不是检查你希望它有的行为。"(出处见文末)

# 动作二:钉住现状(把 X 换成动作一里写下的那个名字)
请给 X 写一组"只描述现状"的检查。
要求:
1) 不要修正任何看起来像毛病的地方,现在是什么样就记什么样。
2) 至少覆盖 3 个正常情况 + 2 个边界情况(空的时候、特别多的时候、缺东西的时候)。
3) 每一条都写清楚:输入什么 → 现在的输出/结果是什么。
4) 写完后跑一遍,把结果给我看。全通过再继续。
5) 这一步不要改 X 里面的任何逻辑。

# 验收:它给你的每条,你都能读懂"输入什么、现在会怎样"。
# 读不懂就让它用日常话重写一遍——读不懂的检查,等于没有检查。

动作三:把口子收窄——加一道门,让旧的走法都改走这道门

现在才是真正的"划边界"。核心做法一句话:

不要先改里面。先在它外面加一个统一入口,规定"外面只能从这个入口拿东西",然后把旧的用法一个一个搬过来。

🔑 这一步的关键是"一个一个搬",不是"一次改完"

搬一个 → 跑一次动作二那组检查,结果还是老样子 → 再搬下一个。

只要守住这个节奏,你在任何一刻停下来,项目都还是能用的。这跟"让 AI 大改一通然后祈祷"是两件事。中间状态下新旧两条路同时存在,是正常的,不是失败。

# 动作三:开口子(先只要方案,不要它动手)
我要给 X 加一个统一的入口,让现在所有的用法都改走这个入口。
现在先不要改任何代码,只给我一份方案:

1) 你打算开的这个入口长什么样:外面能递什么进去、能拿什么出来。
   要求:口子越少越好、越简单越好。
2) 现在每一处用法,改走这个入口之后分别写成什么样。一处一处列。
3) 哪些用法你搬不过去——因为它拿的是 X 内部的东西,不是入口能给的。这些单独列出来,
   并说明它具体拿走了什么。
4) 你打算分几步搬?每步搬完之后,怎么验证行为没变。

# 关键看第 3 问:搬不过去的那些,就是你现在真正的口子清单。
# 它们没有消失,只是以前藏在你没看见的地方。

看完方案,先别急着让它动手。先自己看一眼第 3 问那几条——那几条决定了你这个入口开得对不对。如果第 3 问是空的("全都搬得过去"),那说明口子开得不错。如果第 3 问列了一大堆,往往说明这一块负责的事太多,该考虑拆成两块(这属于"今天先不做"的范畴,记下来就行)。

确认方案没问题之后,再说这句:

# 开始搬(一次一步)
按你上面的方案,只做第 1 步。
做完之后:① 跑一遍动作二那组检查,把结果给我 ② 告诉我这一步动了哪几处、其余没动的地方为什么不受影响。
不要顺手做第 2 步。

动作四:写一句话,贴上去

在你动过的那一块最上面写一行说明(写给人看的、机器不会执行的那种,技术上叫"注释"):

这一块负责:______。
外面只能通过这几个入口用它:______(列出那几个)。

这三行看着像走形式,其实它是全文最硬的验收标准:

还有一个好处:这三行是你以后跟 AI 说话的锚。下次你说"改一下这一块",AI 读到这三行,就知道哪些地方可以动、哪些口子是约定好的不能乱碰。

每一步做完,怎么知道有没有白做

检查怎么做(一分钟内)什么算过
口子少了吗 重跑一次 §04 那条数口子的提示词 组数比之前少。没少 = 你只是在旁边加了一层,没真的收口
行为变了吗 重跑动作二那组检查 结论和改之前逐条一样。有一条不一样就得说清楚为什么
那句话写得出吗 写动作四那两行 两行都写得出来,而且你信。写不出来就别往下走
真的少动了吗 拿一个真实的小改动试一次,数它动了几个地方 比上次同类改动少。这是唯一能证明"你赚到了"的检查

06一个具体例子:这个站自己的主页卡片

抽象讲完了,来看一个真的、很小的、你能自己去看的例子——就是你现在正在读的这个站(学习笔记)。它的主页上有一排文章卡片,每张卡片上有:标题、一句简介、难度标记、日期。

这一块,有三个地方在用它

谁它拿走了什么为什么它必须拿到
写卡片的说明书(/learn 这个 skill 的文档) 卡片必须长什么样:用哪些标记名(就是网页里用来标记"这里是什么"的那些记号)、简介最多多少字、日期从哪儿读 它是生产方——新文章要上主页时,是它告诉 AI 卡片该怎么写
主页的行为脚本(theme.js:一段能被浏览器自动跑起来的小程序) 去每张卡片里读标题、简介、日期这三样 主页上的搜索、按难度筛选、按日期排序,全靠读这三样才能工作
检查脚本(check.sh) 核对:卡片上的日期和文章里写的日期是否一致、顺序是不是倒序、简介有没有超过 140 字 它是验收方——每次改完主页,它负责告诉你"这次没搞坏"

三个消费者,一个生产方,一个验收方。它们之间那个"必须达成一致"的东西,就是这张卡片的口子。数一数,一共三类:

没有边界的时候,这里会怎么坏

假设这三方谁也不管谁,各写各的。那么下面这件事一定会发生,而且它是这个例子里最值得看的一点:

🚨 它坏起来是安静的

生产方把简介那一项的标记名写成了另一个名字。于是读的那一方读不到简介了——它不会报错,它只会读到"空",然后搜索和筛选就静默地少了一部分能力。你翻主页的时候一切正常,只有在你搜某个词搜不到的时候才会觉得"咦"。

验收方那边可能也不响:如果它只核日期和顺序,简介叫什么名字它根本不看。

结果是:三处对不上,三处都不报错。这就是"没有边界"最典型的样子——不是崩掉,是悄悄不对。

而如果口子定死了(就是上面那三类),情况完全不同:

这个例子的真实来源

这不是我编的。这个站自己的待写清单里记着一件事:2026-09-16 那天,改主页加一张卡片,最后需要回滚重来(回滚:把改动退回到没改之前的样子——这是"改坏了"最贵的一种代价,因为你前面花的时间全没了)。

而且你可以自己验证"口子确实存在"——就在这个站的文件里:

# 你可以自己跑这两条,看看口子的两端分别长什么样
grep -n "card-title\|card-desc\|card-date" learn/theme.js
grep -n "140" .agents/skills/learn/check.sh

第一条会让你看到读的那一方正指着那几个名字;第二条会让你看到140 这个上限是被谁拦的。这就是"口子"的具体样子——不是概念,是两个文件里对得上的那几行字。

🎯 从这个例子带走一句

这个卡片没有"被拆成很多文件",也没有"分层"。它就长在主页那个文件里。但它是有边界的——因为那两句话答得出来:它负责"展示一篇文章的摘要";别人只通过"三类口子"用它。

边界跟文件数量、跟目录结构、跟漂不漂亮,都没有关系。

07划完之后,什么变了

你会有一种"好像什么也没变"的错觉——因为文件还是那些文件,行数可能还多了几行。但变的是这四件事:

N → 1改一处要照顾的地方,从 N 个变成一个口子
会响改坏了不再是"三周后才知道",而是当场告诉你
能交出去你和 AI 的对话从"解释半小时背景"变成"改这一块,口子别动"
答得出"这不影响别的吧"——以前只能猜,现在能答

四条变化,逐条说

① "改一处要动几处"这个数字变小了,而且你能提前说出来

以前你改东西是靠试:改完跑一遍,看看哪儿坏了。现在你能在动手之前说出"这一改会动到哪三处"。这个差别,就是"猜"和"知道"的差别。

② 坏了会响

这是动作二(钉住现状)带来的,也是最容易低估的一条。口子上有了检查,"悄悄不对"就死了。你以前最怕的不是出错,是出错了没人知道——现在有人在门口站着。

③ 你终于能让别人(包括 AI)动它

划边界之前,你交给 AI 一块东西,得先花半小时解释背景:这是干什么的、别碰哪儿、改完要注意什么。划完之后,那三行注释就是背景。你不是变强了,你是把"必须在你脑子里"的东西搬到了纸上。这对"站在审批位上的你"是最实际的一条——你终于能验收了,因为口子给了你一个能对照的东西。

④ 你能回答"这不影响别的吧"

这句话你肯定问过无数次,得到的回答是"应该不会"——那是猜。有了口子,答案变成一个具体的范围:影响到那几处,就这些,其他都不碰。

⚠️ 两个反直觉的地方,先给你打预防针

第一,划完边界,代码总量通常不会变少,有时还会多几行(你加了一个入口)。别用"行数变少了没有"来判断这件事有没有做成。变的不是代码的量,是"改它的时候你要同时照顾的地方"。

第二,边界不是一次划完的。它不是一场大扫除,是"这次改到这儿,顺手把它收一下口"。所以做完一块就停,下个月改到下一块时再收下一个。你每次都只赚一点,但赚到的不会再还回去。

08什么时候不该划

这篇如果不写这一节,就会变成"到处划边界"的怂恿。划边界是有成本的,下面这几种情况划了就是白干。

① 只被一处用到的东西

一个口子只有一个使用者,那这个口子就是纯绕路——多了一层,什么也没挡住。留到有第二个使用者出现的那天再开。

这条有个很好记的说法:一种用法 = 想象出来的口子;两种用法 = 真的口子。别为"以后可能会有人用"先开一个。

② 你马上要扔掉的项目

边界是给"还要继续改"的东西用的。你手上十几个项目,不是每个都值得——有的你已经不想要了,那就别在它身上花这个钱。

判断法:如果这个项目你已经三个月没打开、而且也没有接下来的计划,跳过它。

③ 需求还在剧烈变的时候

如果这一块下周还要大改(要加一堆功能、要换做法),先别划。你现在划的边界,下周会跟着需求一起作废。

等它稳住一个星期,再回来划。边界是给"已经定型的行为"画的。

④ 想一次划超过一块

一次一块。做完一块、验证完、停下来。同时划三块 = 出了事你不知道是哪块引起的——而且你几乎一定会出事。

这条不是保守:一块做完你就有了一次真实经验,第二块的判断会准得多。

还有一个"别当":别把"多写一层"当成划边界

这是"做过头"最常见的样子。你(或者 AI)在原来的东西外面包了一层,看起来很规范,但外面要递进去的东西没变少、要拿出来的也没变少——只是绕了一下路。

这是绕路,不是收口

外面原来要递进去 6 样东西。包了一层之后,还是要递 6 样——只不过中间多转了一手。

口子没有变少。你付出了一层复杂度,什么也没换到。

这是收口

外面原来要递 6 样、还要自己按顺序调 3 个动作。收口之后:递进去 1 样,里面的顺序由它自己管。

口子从"6 样 + 3 步顺序"变成"1 样"。这才叫收窄。

一句话的判断法:做完了之后,外面要知道的东西变少了吗?没变少,你就是绕了一圈。

还有一条容易忽略的:口子别定得太死

反过来说,口子也不能收得太窄。如果你把入口定成"只允许这一种用法",那么等第二种用法出现时,别人(或 AI)就只能绕过这个口子——你辛苦划的边界当场作废。

所以留一点余量:口子按"这一类事"来定,不要按"现在这一件事"来定。比如入口叫"读一篇文章的摘要",就比叫"读主页第三张卡片上的简介"耐用得多——后者一改版就废了。

这一条有出处,而且是原话。Ousterhout 在书里列了两种正好相反的错:一种是把太多东西当成重要的(就是口子开得太大、什么都往外露);另一种是没认出某样东西其实很重要——后果是"重要的信息被藏起来了,或者重要的功能拿不到,于是每个开发者都得自己重新造一遍"。口子开得太松和收得太紧,是两种不同的错,不是"越紧越好"。

09五个坑与排错

下面这五个,是这件事最常翻车的地方。每一个都有"症状",你可以拿它当体检表。

坑一:把"拆成更多文件"当成划边界

症状:你把一大块拆成了 5 个文件,感觉自己整理了结构。但改起来还是"一改就坏"。

为什么没用:拆文件只改变了"东西放在哪里",没改变"谁从哪几个口子拿它"。5 个新文件之间还在互相伸手,口子从 1 个变成了 5 个——更糟了。

怎么验:拆完之后跑一次 §04 的数口子提示词。组数如果变多了,说明你拆反了。

坑二:口子开在"里面怎么做的"上

症状:你开了个入口,但外面递进来的东西里,有"内部的顺序""内部的编号""内部的状态"这种只有里面才懂的东西。

为什么是坑:那不是口子,那是把里面伸出来了——只是换了个形式。外面还是得懂里面的规矩,你一点也没减负。

怎么验:把入口的说明书拿给一个完全不懂这块的人看。他看不懂"这个参数该填什么",就是坑二(参数:你要递给它的那样东西)。

坑三:划到一半,新旧两条路都在,然后忘了旧的

症状:半年后你发现,有的地方走新入口,有的地方还在走旧路——而且你已经不记得旧路是干什么的了。

先说清楚:中间状态本身是正常的,甚至是必须的(动作三就是一步一步搬)。问题不在"有两条路",问题在没有截止日期。

怎么办:搬的时候就在动作四那三行注释里多加一行:"旧的走法在 ____ 之后一律不许再用。"有这一行,它就不会烂在那里。

坑四:让 AI 一次性大重构

先说清"重构"这个词:重构 = 不改行为,只改里面的结构。改完之后,东西的长相和能力都该和以前一模一样,只是做法换了。它跟"顺手把毛病也修了"是两件事,混在一起你就分不清是哪边出的问题(这也是动作二存在的理由)。

症状:你跟 AI 说"帮我把这块整理一下,收个口",它给你改了 20 个文件。你跑起来一看,崩了。然后你面对着 20 个文件的改动,不知道从哪儿退回。

为什么是坑:不是 AI 能力问题,是这个任务不适合一次做完。改 20 个文件之后,任何一处出错都淹没在改动里。

怎么办:回到动作三的节奏——一次搬一处,每搬完一处跑一次检查。这在提示词里已经写死了("不要顺手做第 2 步"),照抄就行。

坑五:验收只看"跑起来了没有"

症状:改完,页面能打开,你点了几下没发现问题,就认为成功了。

为什么是坑:这一篇里最危险的坏法叫"悄悄不对"(§06 那个例子)。它能打开、能看、不报错,但某个功能已经少了一半。"跑起来了"完全查不出这种坏。

怎么办:验收时不要只看能不能打开,要拿一个具体的输入去试,并且知道正确结果该是什么。这就是动作二那组检查存在的全部理由。

排错表:症状 → 先做什么

你看到的最可能是先做这一个动作
改完没报错,但某个功能好像不灵了 悄悄不对——某个口子对不上了,而且没人检查它 回去看动作二那组检查覆盖了没有;没覆盖就补一条
AI 说"改好了",但你数不出它动了哪几处 改动范围失控,多半是动作三没按"一次一步"来 让它列出"改了哪几个文件、每处为什么必须改",列不出就退回重来
口子收完之后,别的地方要改的反而更多了 你开的是坑二那种"把里面伸出来"的口子 把入口说明书重写一遍,只留"外面必须知道的事"
你写不出"这一块负责什么"那句话 这一块太大,不是一块,是一团 回到动作一,往小里再选一层
数口子数出来一堆,多到不想看了 你选的范围太大了 只保留"最常被改到"的那一样,其余先记下来放一边

10听见这些词别慌:一张对照表

这一节是为了一个很具体的问题:你做这件事的时候,AI 会用一堆词跟你说话。你不需要学会它们,但你要能认出"它其实在说哪句日常话"。认出之后,你就能判断它说的对不对。

你会听到的词它其实在说什么为什么它重要 · 别当什么用
模块 / module 一块能单独拿出来说的东西。可能是一个文件、一个页面上的东西、一个动作、一个后台服务 别当"技术上的某种单位"——它的大小由你决定。你说得出口的那一块,就是一块
边界 / boundary 那两句话的答案:这块负责什么、别人从哪几个口子进 别当"画在文档上的框"。它是可以验证的:改里面,外面要不要跟着改
接口 / interface "口子"的正式说法 别当"只是那个动作的写法"——它还包括顺序、什么时候不能用、出错会怎样。这是最容易把它想窄的一个词
实现 / implementation 里的部分:外面的人不需要知道的那部分 别把"实现"当成"不重要的部分"。正好相反——外面越不用知道,里面越可以随便改
字段 一块内容里的"某一项"。比如一张卡片有标题、简介、日期——这三样就是三个字段 听到它要警觉:如果外面只拿某一个字段,那往往就是一个独立的口子。§04 数口子时,"只拿了其中某一项"和"整块拿去用"要算成两组
耦合 两个东西"必须一起改" 别当"两个东西有关系"。有关系不是问题;必须一起改才是问题。判断法就是那句:改一个,另一个要不要跟着改
单一职责 / SRP 一个模块只该有一个"需要改它的理由"——注意不是"只干一件事" 这是最常被误读的一个词,作者本人澄清过:"这个原则讲的是人"——同一批人关心的东西放一起,不同人关心的分开。别当"一个模块只能干一件事",那正是 §08 警告的"做过头"
重构 / refactor 不改行为,只改结构。长相和能力都一样,只是里面换了做法 别当"顺手把毛病也修了"——那是两件事。混在一起,你就分不清是哪边出的问题。这也是动作二存在的理由
表征测试 / characterization test 只描述现状的检查。不管对错,只把"现在是这样"钉下来 用在改动之前,用来防止"改坏了你不知道"。别当"测试是为了证明代码对"——它只证明"跟以前一样"
接缝 / seam 另一个说"口子开在哪"的词。原话是:"一个能让你改变程序行为、而不用改这个地方本身的位置"(原文:A seam is a place where you can alter behavior in your program without editing in that place.) 它比"口子"多强调了一件事:位置是选出来的。同一个东西,口子开在前门还是后门,是两回事
深模块 / 浅模块 一种说"口子小、里面多"的说法 听到它先问一句"你说的是哪种算法"——见下面的分歧说明。这个圈子里的两个人对它定义不一样

一个真实的分歧:两位作者对"深模块"的定义不一样

下面这段是"你只有真读过两边的原文才写得出来"的东西,也是这张表里最值得看的一格。

"深模块"到底深在哪儿?

一种说法(John Ousterhout,《A Philosophy of Software Design》,第二版 2021 年):深 = 口子比里面简单得多。衡量它的办法是"这点口子的复杂度,换回来多少功能"——口子带来的一点点麻烦,要能换回一大片能力,才叫深;口子几乎和里面一样复杂,就是浅。

另一种说法(codebase-design 这份技能文档,本机装着):它反对的是把深度读成"里面的行数 ÷ 口子的行数"——那样只要往里面多塞行,深度就自动增加了,等于奖励灌水。它改成用杠杆来衡量:一个口子后面能办多少事、能被多少处复用。

有意思的地方来了:被反对的那个"按行数算比例"的读法,Ousterhout 自己也否认——他在书里写过"大小其实没那么重要"(两百行到两千行的类都可以),真正重要的是"这个抽象有多大威力"。所以这更像是一个被简化过的版本,对上它的原意,而不是两个人在吵同一件事。

还有人更彻底:Eric Normand 写过一篇直接叫"模块深度是站不住的"的文章,说"深度"这个比喻经不起细看——同样一段代码,你把框画大一点,它看起来就"深"了;他主张改成问"这个口子提供的控制力,和它带来的复杂度相称吗"。

所以怎么办:三方的结论方向一致(口子要小、复杂性要藏在里面),分歧在"能不能用一个比例把它算出来"。你听到"深模块"时问一句对方用的是哪种说法,就能对上话;而你自己做判断时,用本文那句"改一处要动几处"就够了——它是数出来的,不依赖上面任何一种定义。

📌 顺带说一个命名上的坑

"边界"这个词在别的圈子里还有另一个意思(指"一个业务领域的范围",跟这里的"口子"不是一回事)。所以有些 AI 工具或者文档会刻意避开"边界"这个词,改用"接缝"或"接口"。你不需要跟着改口,但听到的时候别以为它们在说两件事。

99一句话总结

🎯 核心要点

边界不是一个设计出来的词,是两个问题的答案:「这一块负责什么」和「别人从哪几个口子用它」——判断法永远只有那一句:"这东西别的地方从哪几个口子用它?"答不上来,它就没有边界(数口子的种类,不是数被引用的次数)。从一堆乱代码出发,顺序是反的:先选一块(口子最多的那个,不是最重要的那个)→ 先钉住它现在的样子(表征测试)→ 再把口子一道一道收窄(一次搬一处,每搬完跑一次检查)→ 最后写下"它负责什么"那句话。一次只动一块,做完就停——因为划完边界你不会得到更少的代码,你得到的是"改它的时候,要同时照顾的地方从 N 个变成一个"。

留给你的最后一件事

这一篇里所有判断,你都能在纸上验证——只有一条不能:你手上那个项目,到底有几块是真的没有边界。那只能你自己去数。

📌 现在就能做(按顺序,第一个不用装任何东西)

① 跑一次起手提示词。挑你最常打开的那个项目(不是最重要的那个,是你最常打开的那个),把 §01 那条提示词原样发给 AI。只看最后一句的答案——哪一样东西的"用法种类"最多。

② 数一次口子。拿那一样东西,跑 §04 的数口子提示词。先只做这一件事,不要动手改。你要的产出是一个数字:一共分成几组。

③ 对照你的直觉。那个数字和你原本的感觉对得上吗?如果它比你以为的多得多——恭喜,你刚找到了那笔你一直在付、但从来没见过账单的钱。

做完这三步就停。真正的动手(钉住现状、搬口子)留到你下次真的要改那一块的时候——那时候你才有理由花这个时间。

⌂