写代码到第三年,我开始对“重复”这件事格外敏感。一个组件文件的开头七八行 import、一个 useEffect 的固定壳子、一段每次都要现查的日志打印格式,敲一遍是三十秒,敲一百遍就是五十分钟。真正让我下决心整理的是某次改需求,同一个模板文件我一天里手写了六遍,第五遍的时候漏了一个清理函数,排查了一个多小时。那次之后我把 VS Code 的代码片段(snippet)系统翻了个底朝天,从最基础的 prefix 匹配,到变量注入、正则变换、工作区共享,中间踩的坑比想象中多。这篇就是我整理出来的完整经验:VS Code 的 snippet 到底怎么匹配、怎么写才不别扭、全局和各语言片段该摆在哪、为什么明明写对了却不弹出来,以及哪些东西压根不该用片段解决。不管你是刚装好编辑器的新手,还是已经攒了一堆零散片段但没系统整理过的老手,下面这些内容都能直接拿去改自己的配置。
1. 别急着打开用户片段文件,先搞清楚 VS Code 拿什么匹配你的前缀
很多人第一次接触代码片段,是照着某篇帖子在javascript.json里贴了一段 JSON,保存,然后发现敲前缀没反应,于是开始怀疑编辑器坏了。问题几乎从来不在编辑器,而在于对“片段是什么时候被检索的”这件事没有概念。这一节把底层机制摊开讲清楚,后面所有操作才有依据。
1.1 片段的三层存放位置与各自的生效范围
VS Code 的代码片段分三层,不是一个配置文件打天下。搞清楚哪层管什么,能省掉大量“为什么我在 A 项目里写的片段到 B 项目就没了”的困惑。
第一层是全局用户片段。打开命令面板(Ctrl+Shift+P或Cmd+Shift+P),输入Configure User Snippets,选择New Global Snippets file...,起个名字,比如common,它会生成一个common.code-snippets文件。这种文件只要不写scope字段,在任何语言的文件里都生效。适合放那种跨语言通用的东西,比如时间戳注释、TODO 标记、分割线。
第二层是语言专属片段。同一个命令里下面列着一堆语言名,点javascript就生成javascript.json。这个文件里定义的片段,只会在 languageId 为javascript的文件里被检索。注意这里说的是 languageId,不是文件后缀——比如.vue文件里<script>区块的 languageId 通常还是javascript,能不能触发取决于语法注入的具体实现,实测下来并不稳定,这点后面排错章节会细说。
第三层是工作区片段。在项目根目录建.vscode文件夹,里面放一个任意名字的xxx.code-snippets文件。这层片段的特殊性在于它跟着项目走,进 Git 仓库,团队其他人拉下来就自动有了。业务相关的、框架约定死的模板,全都应该放这一层,而不是塞进个人全局配置里。
三层文件在磁盘上的实际位置,按操作系统分布如下:
| 层级 | Windows | macOS | Linux |
|---|---|---|---|
| 全局 / 语言级 | %APPDATA%\Code\User\snippets\ | ~/Library/Application Support/Code/User/snippets/ | ~/.config/Code/User/snippets/ |
| 工作区级 | 项目根.vscode\ | 项目根.vscode/ | 项目根.vscode/ |
知道路径的意义在于:这玩意儿可以直接复制粘贴迁移,换机器的时候把 snippets 目录整个拷过去就行,不用一个个重新录。
1.2 JSON 五个字段里,最容易被写废的是 body
一个片段对象的结构其实很简单,但每个字段都有它的脾气。标准形态是这样:
{ "React 函数组件": { "prefix": "rfc", "body": [ "import React from 'react';", "", "export default function ${1:${TM_FILENAME_BASE}}() {", " return (", " <div>$2</div>", " );", "}", "$0" ], "description": "生成一个 React 函数组件骨架", "scope": "javascript,javascriptreact,typescript,typescriptreact" } }最外层的 key(React 函数组件)是片段名,它就是补全列表里左边那行文字。prefix是触发词,写字符串或字符串数组都行,写数组意味着多个前缀都能唤出同一个片段,我当时把rfc和component都挂上去了,用着确实顺手。description是补全列表右侧的灰色说明,写不写都能用,但写清楚能救命——半年后你自己都不记得rfc和rfcx有什么区别。
scope字段决定这个片段在哪些 languageId 下生效,逗号分隔,中间绝对不能有空格。这里踩过坑:写成"javascript, typescript",第二个就失效了,而且编辑器不会有任何提示,你只会觉得“我的 TS 片段怎么没了”。
真正容易写废的是body。它支持字符串和字符串数组两种形式。字符串形式要自己写\n,可读性极差,而且编辑器不会帮你处理缩进。数组形式一行一个元素,好处是 VS Code 会做一件很聪明的事:先算出所有行里最小的公共前导空白,把它整体剥掉,然后按片段展开时光标所在行的缩进重新补齐。这意味着你在 JSON 里写的那些用于对齐的空格,不会跑到代码里去。所以写 body 的时候不要为了好看手动加一堆缩进,反而可能算出错误的公共缩进,导致展开后整体错位。
还有一个隐藏规则:body数组里的空行必须写成""或者直接留空元素,不能省略。少了空行,展开出来的代码就会挤成一坨。
2. 把一段十行的样板代码压成三秒输入:一次完整的片段编写过程
知道结构以后,剩下的问题就是“写什么”。大部分人的片段库最后变成垃圾场,原因是一开始没有筛选标准,见到重复就写一个,写了又不用。我自己的筛选流程走了两年才稳定下来,这里完整复盘一遍。
2.1 从“我一天要敲几遍”开始筛选候选
我的判断标准非常粗暴:一天之内手敲超过三次,且长度超过五行,才值得做成片段。低于这个门槛的,做了也用不起来,因为脑子里根本没形成“我有这个片段”的记忆。
按这个标准筛出来值得做的,一般是这么几类:
- 文件骨架类:组件文件、测试文件、接口定义文件。这类东西开头固定、结构固定、结尾固定,是片段的最佳战场。
- 固定壳子类:
try/catch带日志、useEffect带清理、异步请求带 loading 和错误处理。这类代码一次要写十几行,但真正变化的只有中间几个变量。 - 高频小语法类:打印日志、类型断言、解构赋值。短归短,但频率高到离谱。
- 配置片段类:
package.json里的 scripts、tsconfig里的常用选项、CI 配置的常用块。 - 注释模板类:函数说明、变更记录、待办标记。
反过来,有几类我明确不做片段:一是只在某个特定业务里出现一两次的代码,做成片段纯属浪费;二是超过三十行的大块代码,片段展开后光标跳转会乱成一团,而且这种规模的代码往往需要按情况改动,片段反而拖慢速度;三是任何需要条件判断才能生成的代码,因为片段系统压根不支持逻辑,硬做出来的一定是残次品。
筛选完之后我建议排个优先级,先做前三类里最烦的那几个。别一上来搞二十个片段,用不上就是负担。
2.2 手写 body 的顺序:先写满,再切占位符
新手最常见的错误,是一边写片段一边想占位符,结果$1、$2编号乱跳,Tab 键按下去光标到处乱飞。我的做法是分两步走,先不管占位符。
第一步,把一段真实的、能跑的代码原样贴进body数组,每一行一个元素,该有的空行留出来。这时候它是死的,展开就是一模一样的文本,先保证它能正确展开、缩进正确。
第二步,再回头把需要改的地方替换成占位符。占位符的规则是$1到$9(超过 9 得写${10}),按下 Tab 会按数字顺序跳转,$0表示最终停留位置。
举个例子,我最早做的一个测试骨架片段,第一版长这样:
{ "单测骨架": { "prefix": "tcase", "body": [ "describe('${1:模块名}', () => {", " it('${2:应该做什么}', () => {", " const result = ${3:fn}(${4:input});", " expect(result).toBe(${5:expected});", " });", "});", "$0" ], "description": "生成一个 describe/it 测试骨架" } }注意$0单独放了一行数组元素。这个位置很关键,它决定了你连续按 Tab 之后光标落在哪里。放在最后一个元素上,意味着填完期望值光标会跳到块外面另起一行,接着写下一个测试,节奏刚好。如果忘了写$0,光标会停在最后一个占位符上,你得手动挪,三次以后你就懒得用这个片段了。
还有一个细节:占位符默认值里如果包含}、,、|这类字符,可能被解析截断,最好避免。真需要的话,整个默认值要处理转义。
2.3 用默认值和 choice 把最常见的分支预先定好
占位符除了${1}这种裸写法,还有两种带默认值的形态,用好了能省掉大量二次输入。
第一种是${1:默认值}。展开片段时这个默认值会被选中,你直接按 Tab 就接受,需要改就直接输入覆盖。我所有占位符都给默认值,哪怕是foo、bar这种占位的,也比空着强——空的时候光标停在那儿,你得先确认一下自己在哪。
第二种是${1|选项A,选项B,选项C|}。展开会弹一个下拉让你选,选完继续往下跳。这个对枚举场景特别有用。比如我写一个请求方法的片段:
{ "请求封装": { "prefix": "req", "body": [ "const ${1:data} = await request({", " url: '${2:/api/path}',", " method: '${3|GET,POST,PUT,DELETE|}',", " data: ${4:params},", "});", "$0" ], "description": "生成一个请求封装结构,方法可下拉选择" } }${3|GET,POST,PUT,DELETE|}这个下拉在敲代码的时候体验很顺,比手打还快,最重要的是不会拼错。
3. 让片段自己知道文件名、日期和当前选中内容的那套变量
如果说占位符是片段的常规武器,那变量就是它的隐藏技能。很多人觉得片段只是“文本替换”,其实它能读取当前文件信息、当前选中的文本、日期时间,甚至对这些值做正则变换。这一节内容用熟了,片段的价值会翻倍。
3.1 内建变量的清单与实战取值
VS Code 内建了一批以TM_开头以及日期相关的变量,在body里直接用${变量名}就能取值。常用的我整理成表:
| 变量 | 取值示例 | 典型用途 |
|---|---|---|
TM_FILENAME | UserCard.vue | 需要带后缀的场景 |
TM_FILENAME_BASE | UserCard | 组件名、类名、测试标题 |
TM_DIRECTORY | /src/components/user | 自动生成相对路径引用 |
TM_FILEPATH | 完整绝对路径 | 调试信息、注释头 |
RELATIVE_FILEPATH | src/components/user/UserCard.vue | 比绝对路径更值得写进注释 |
TM_SELECTED_TEXT | 当前选中的文字 | 包裹式片段的核心 |
TM_CURRENT_LINE | 光标所在行全文 | 快速复制当前行 |
TM_LINE_NUMBER | 42 | 日志里带上行号 |
CURRENT_YEAR/CURRENT_MONTH/CURRENT_DATE | 2025/03/17 | 文件头注释 |
CURRENT_HOUR/CURRENT_MINUTE | 14/08 | 变更记录 |
CLIPBOARD | 剪贴板内容 | 把刚复制的东西塞进模板 |
WORKSPACE_NAME | 工作区目录名 | 多项目区分 |
举个我天天用的例子,文件头注释片段:
{ "文件头注释": { "prefix": "fhead", "body": [ "/**", " * ${1:模块说明}", " * @file ${RELATIVE_FILEPATH}", " * @created ${CURRENT_YEAR}-${CURRENT_MONTH}-${CURRENT_DATE} ${CURRENT_HOUR}:${CURRENT_MINUTE}", " */", "$0" ], "description": "插入带路径和时间的文件头注释" } }这个片段展开以后路径和时间自动就有了,唯一要手填的只有一句模块说明。以前手动写这个头,光路径就要翻目录复制。
TM_SELECTED_TEXT是另一个被严重低估的变量。它的用法是:先在编辑器里选中一段文本,再触发片段,片段里的TM_SELECTED_TEXT会被替换成选中的内容。于是你可以做“包裹式片段”。比如我常写一个log片段:
{ "包裹为日志": { "prefix": "logsel", "body": "console.log('${1:debug}:', ${TM_SELECTED_TEXT});$0", "description": "把选中内容包进 console.log" } }选中userInfo,触发,直接变成console.log('debug:', userInfo);。这个操作比手打快太多,而且不会出现“复制粘贴时把变量名剪掉”的低级错误。
3.2 正则变换:把文件名变成组件名、类名、常量名
变量本身取到的通常是原始形态,比如user-card.vue取到TM_FILENAME_BASE就是user-card,但你的组件名需要UserCard(大驼峰),常量名需要USER_CARD(大写下划线)。这时候就得用变量转换语法。
完整语法是${变量名/正则/替换格式/选项}。比如把文件名转大驼峰,较新版本的编辑器提供了简写:
${TM_FILENAME_BASE/(.*)/${1:/pascalcase}/}/pascalcase是内建的格式修饰符,同系列的还有/camelcase、/snakecase、/kebabcase、/upcase、/downcase、/capitalize。老版本编辑器不支持这些修饰符,退化方案是用正则加\u之类的处理,具体写法比较绕,实际用起来我更建议先升级版本。
一个真实用起来的例子:新建一个组件文件后,敲cname,直接从文件名生成组件名和样式类名:
{ "根据文件名生成组件名": { "prefix": "cname", "body": [ "const ${1:${TM_FILENAME_BASE/(.*)/${1:/pascalcase}/}} = () => {", " return <div className=\"${2:${TM_FILENAME_BASE/(.*)/${1:/kebabcase}/}}\">$3</div>;", "};", "$0" ], "scope": "javascript,javascriptreact,typescript,typescriptreact" } }新建文件user-card.tsx,敲cname,展开就是const UserCard = () => {,第二个占位符已经是user-card。整个组件的骨架自动跟着文件名对齐,命名不一致的问题从源头消掉了。
正则替换里有两个坑必须提。一是 JSON 里的反斜杠要双写,\d得写成\\d,否则 JSON 解析就报错。二是替换格式里的$1指的是正则捕获组,不是占位符编号,两套$语法混在一个字符串里的时候特别容易看花眼,建议写的时候先拆开验证。
3.3 变量没生效的几种典型情形
变量不生效,绝大多数是三类原因。第一类是变量名拼错,尤其是TM_FILENAME_BASE和TM_FILENAME记混,拼错的时候 VS Code 不会有任何提示,会原样输出${TM_FILENAME_BASEE}这一串,等于当场暴露。第二类是在不支持的上下文里用,比如用CLIPBOARD变量,某些平台或者某些安全设置下取不到值,会渲染成空。第三类是转换语法版本不匹配,/pascalcase这类修饰符在旧版本里不认,会直接把整串代码当文本输出。
我的做法是:新写一个带变量的片段,先在一个临时文件里展开一次,确认变量都正确替换了,再放进正式配置。这一步三十秒,能省掉后面半小时的自我怀疑。
4. 全局、语言级、工作区级:片段该放哪一层才不会互相打架
片段攒到二十个左右的时候,放置问题就浮出水面了。全塞全局配置里,换个项目发现一堆用不上的业务片段排在前面,很烦;全塞项目里,换个项目又得重来一遍。这一节讲我最后稳定下来的组织方式。
4.1 用 scope 字段替代语言专属文件名的理由
早期的做法是按语言拆文件,javascript.json、python.json、css.json各放一堆。这么做能用,但有三个明显问题:一是同一个片段要同时给 JS 和 TS 用,得复制两份,改的时候两边都要改,早晚不同步;二是文件名和语言绑定死,.vue、.svelte这种多语言文件里的表现不可控;三是文件一多,找东西费劲。
我后来统一改成一个自定义名字的.code-snippets文件,靠scope字段控制生效范围。比如frontend.code-snippets这个文件里,把 React 相关片段的scope写成javascript,javascriptreact,typescript,typescriptreact,一份定义四处生效。CSS 相关的片段单独写scope为css,scss,less。这样文件数量砍掉一半,维护成本直线下降。
scope的写法有两个硬性要求:逗号分隔,逗号后面不能有空格;支持的取值是 languageId 而不是文件后缀。想知道某个文件到底是什么 languageId,看编辑器右下角状态栏的语言名,或者打开命令面板搜Change Language Mode,当前语言会显示出来。
4.2 工作区片段的版本管理与团队约定
工作区片段(.vscode/xxx.code-snippets)的价值在于“团队共享的代码约定”。这类片段应该满足一个条件:它是这个项目的规范,而不是个人的输入习惯。
举个典型场景:项目规定所有接口请求必须走统一的封装函数,参数顺序固定。那这个请求结构就应该做成工作区片段,让所有人敲req出来的都是一样的结构,减少评审时的格式来回。反过来,你个人喜欢用cl触发日志、用ff触发for循环,这种纯粹是输入习惯,放全局配置里,别往仓库里塞。
这里有个容易忽略的协作细节:.vscode目录经常被加进.gitignore,尤其是老项目。要做工作区片段,先确认它没被忽略,否则你辛辛苦苦写好,同事拉下来根本没有。另外,片段文件在仓库里也会被代码评审,命名要克制,前缀最好带项目特有的短前缀,比如统一以xx-开头,避免和同事个人全局片段的同名前缀撞车——同名的时候补全列表会出现两个一模一样的条目,选错就是灾难。
4.3 命名前缀和清理机制
片段库失控的根本原因是只增不减。我现在的规则是三条。
第一条,前缀统一小写、两到四个字符、按领域分组。rfc(React 函数组件)、rfs(React 状态)、tcase(测试骨架)、fhead(文件头)。同一组的第二个字符尽量一致,形成肌肉记忆,敲两个字符就能从补全列表里定位。
第二条,每季度清一次。判断标准很实在:过去三个月里有没有用到过。没有就删。我第一年攒了六十多个片段,清理完剩下二十三个,使用频率反而上去了——因为列表短了,找起来快。
第三条,片段名写清楚用途,不写缩写。React 函数组件(带 Props 类型)比RComp强太多,半年后你还认识前者。这个字段不影响触发,只影响可读性,别在这上面省字。
5. 片段不弹出来、弹出来不对:一条从现象到根因的排查顺序
这是全文最实用的部分。我遇到过至少七八种“片段写了但没用”的情况,症状相似,根因完全不同。下面按排查成本从低到高的顺序列出来,照着走基本能定位。
5.1 JSON 写坏了但没有任何报错
这是最隐蔽的一种。.code-snippets文件和settings.json不同,它对语法错误的容忍度极低,但不会弹窗提示。少一个逗号、多一个尾逗号、字符串里有个没转义的引号,整个文件的片段就会全部失效,表现是“所有片段都不弹了”,而不是“某一个不弹”。
排查方法:把文件内容复制到任意一个能校验 JSON 的地方看一眼。更快的办法是打开视图 → 输出,切到Log (Extension Host)或者开发者工具的控制台,语法错误有时会在那儿打出解析失败的日志。
最常见的三个语法雷区,我列一下:
- 尾随逗号。JSON 标准不允许最后一个元素后面有逗号,但很多人写惯了 JS 对象,顺手就加了。
- 字符串里的双引号没转义。
"className="box""这种,正确写法是"className=\"box\"",注意在 JSON 里需要写成\\\"才最终输出\"。这个层级很容易绕晕。 - 正则里的反斜杠。
\d、\w、\.都要写成\\d、\\w、\\.,漏了就直接解析失败。
还有一个更阴的坑:想输出一个字面量的$。片段系统把$当特殊字符,要输出它必须写\\$。如果不处理,展开后你会发现$和它后面的内容一起消失了,或者变成了奇怪的空值。
5.2 补全列表里有它,按 Tab 却不展开
能出现在补全列表里,说明片段文件本身是好的,问题出在触发环节。按可能性排序:
第一个可能:补全列表根本没弹出来。片段依赖编辑器的建议列表,如果editor.quickSuggestions被关掉了,或者当前处于某些特殊上下文(比如字符串内部、注释内部),列表不会出现。检查设置里editor.quickSuggestions是不是on或者至少对other、comments、strings开着。
第二个可能:列表弹出来了,但你按 Tab 只是接受了普通补全。这跟editor.tabCompletion设置有关。三个取值:off表示 Tab 不做补全;onlySnippets表示 Tab 只用来展开片段;on表示 Tab 接受任何补全。默认是off。很多人写片段的时候顺手把editor.snippetSuggestions调成了top,但忘了动tabCompletion,结果就是列表里有、按 Tab 没反应。我的建议是设成onlySnippets,这样 Tab 只对片段生效,不会影响其他补全习惯。
第三个可能:语言服务抢先了。同一个前缀被其他扩展注册了(比如某些框架的组件生成扩展),补全列表里两条同名条目,你按回车选中的是扩展那条。这种情况要么换前缀,要么在列表里用方向键明确选中片段那条(片段条目的图标通常是个小方块)。
第四个可能:当前文件语言不对。片段有scope限制,但文件没被识别成预期的语言。典型场景是新建了一个空文件还没保存成.ts后缀,languageId 是plaintext,所有语言级片段都不生效。先保存文件,再看状态栏语言是不是对的。
5.3 展开后缩进错乱、多出制表符
这个问题的根源是缩进字符不一致:你的项目用两个空格,片段 body 里写的是四个空格或者 Tab,展开后自然歪。
片段展开时,VS Code 会剥离所有行的公共最小缩进,再按当前行的缩进重新补齐。所以正确的写法是:body数组里每一行都从第 0 列开始写,靠数组顺序表达换行,不要手动加空格去视觉对齐。一旦加了,公共缩进的计算就会出错,剥不干净,展开后就多出一截。
另外,如果你在片段里写了显式的制表符,而项目用空格缩进,展开后编辑器的格式化会把这个 Tab 当真实字符处理,看着就乱。保险起见,body 里只用空格,并且从第 0 列开始。
还有一种情况:片段展开后整体多了一层缩进。检查你是不是在缩进已经很深的代码块里触发的片段——这是设计行为,会跟随当前缩进层级。如果你希望片段永远顶格,那就得手动调整,片段系统没有“忽略当前缩进”的开关。
提示:排查片段问题有一个通用手法——先在一个全新的、内容为空的、后缀正确的文件里测试。能把九成环境干扰排除掉,剩下的一成才是片段本身的问题。
6. 片段解决不了的事:边界在哪,什么时候该换别的工具
写了几年片段之后我有个比较明确的判断:片段系统的能力上限其实不高,但它解决的恰好是最痛的那一类重复。知道它的边界,能避免你在错误的方向上浪费时间。
6.1 片段的能力上限
片段本质上就是文本替换 + 光标跳转 + 变量插值 + 正则变换,仅此而已。它没有条件判断,没有循环,不能读取项目配置,不能调用其他命令,不能根据上下文分支。你没法写“如果当前文件在pages目录下就生成 A 模板,否则生成 B 模板”这样的逻辑。
意识到这一点之后,我停止了不少徒劳的尝试。比如我曾经很想做一个片段,根据当前目录名字自动决定导入路径的写法(别名@/还是相对路径../),试了半天发现做不到,最后改用相对路径一律手改,或者干脆在项目配置里统一别名规则,让代码本身不需要分支。
所以判断一个需求该不该做成片段,只需要问三个问题:内容是固定的吗?变化的部分能用占位符和变量覆盖吗?长度在三十行以内吗?三个都答是,就做片段;有一个答否,就该考虑别的方案。
6.2 与代码模板、脚手架的分工
片段之外,还有两个层级的方案,各有各的适用场景。
文件模板适合整文件生成的场景。当你要做的是一个完整的新文件,包含十几行 import、导出、类型定义,那用片段展开之后还得手动调整结构,不如直接维护一个模板文件,需要的时候复制改名。编辑器里也可以给“新建文件”绑定模板,具体实现方式取决于你用的扩展生态,但思路是一样的:片段管“块”,模板管“文件”。
脚手架适合带参数的批量生成场景。一次要生成四五个文件、目录结构固定、文件之间还有相互引用关系的,片段完全无能为力。这种需求应该脱离编辑器,用脚本或者项目自带的生成命令解决。我现在的项目里,新增一个业务模块用的是一条生成命令,一次产出组件、样式、测试、接口定义四个文件,比任何片段都快。
三层的关系可以这么理解:片段是最轻的,随手就能加,改起来也没负担;模板次之,要维护一个文件;脚手架最重,得写脚本或者引入工具。能上片段就别上模板,能上模板就别上脚手架,按重量递增的顺序选,别一上来就搞大炮打蚊子。
顺带说一个我实际用出来的小技巧:把最常用的几个片段绑定到快捷键上。打开keybindings.json,加一条:
{ "key": "ctrl+alt+l", "command": "editor.action.insertSnippet", "args": { "name": "包裹为日志" }, "when": "editorTextFocus && !editorReadonly" }name填的是片段名(就是 JSON 最外层那个 key),不是 prefix。绑定之后不用敲前缀、不用看补全列表,选中变量按一下快捷键就是日志包裹。这种“无感知”的操作对高频片段特别值——它省掉的不只是敲字符的时间,还有从写代码思路里跳出来找补全列表的那一下分心。
片段这个东西,做的时候花十分钟,后面能省下几百次重复。但它的价值只有在你真的记住自己有哪些片段之后才成立。所以我现在的做法很土:每加一个新片段,头三天刻意用,用不到三次的直接删掉。留下来的那批,才是我真正需要的。