做 Java 后端的同学大概都有这个经历:在 IDEA 里敲sout加个 Tab,System.out.println()就整整齐齐地出来了,敲psvm一回车,main 方法骨架瞬间成型,手指几乎不用离开主键区。换到 vscode 之后,这套肌肉记忆突然就失效了,尤其在写 demo、刷算法题、给前端项目补一段 Java 示例的时候,效率断崖式下滑。好消息是,vscode 的自定义代码片段(User Snippets)机制完全能把这套手感还原回来,而且比 IDEA 的 Live Template 更轻量——不用装插件、不用改 IDE 配置文件,一个 JSON 文件就能搞定,还能跟着 Settings Sync 走到哪带到哪。这篇就把 sout、psvm 这类 Java 模板的还原过程讲透,顺带把 html 场景的自定义片段一并说清楚,最后给一套把 IDEA 现成模板批量搬过来的思路。不管你是刚装好 vscode 的新手,还是已经用了一阵但一直懒得配片段的"老白嫖党",照着走一遍都能直接抄作业。
1. 为什么我要在 vscode 里重建 IDEA 的快捷键手感
1.1 从 IDEA 迁到 vscode 的真实痛点
先说清楚一个事实:vscode 的默认补全能力其实不弱。它内建了log、for、if、func这类通用片段,装了 Java 扩展包(Extension Pack for Java)之后,System.out.println也会出现在智能提示里。问题在于触发链路太长——你得敲System,等提示列表弹出来,用方向键选到那一项,再回车。对比 IDEA 里sout+ Tab 的两键操作,多出来的这几步在一天写几十行打印语句的场景下,累积起来非常烦人。
更麻烦的是psvm。main 方法本身不复杂,但它有固定的缩进、固定的大小写、固定的String[] args参数名。手敲的时候很容易写成String args[]或者少个空格,虽然编译能过,但团队代码规范工具一扫就是一堆警告。这种"结构固定、内容死板、出现频率高"的代码,本来就是代码片段最该发挥作用的地方。
还有一个隐藏痛点:不同人的键位习惯不一样。有人习惯sout,有人习惯syso(Eclipse 风格),有人习惯soutv打印变量名加值。IDEA 的 Live Template 允许你自己加缩写,vscode 的自定义片段同样允许,而且配置成本更低——不需要进设置面板点来点去,直接编辑一个 JSON 文件。
1.2 vscode 内置补全、Emmet 和自定义片段的分工
很多人配完片段发现"怎么不生效",根本原因是没搞清楚这三套机制谁在管什么。
| 机制 | 负责范围 | 触发方式 | 典型例子 |
|---|---|---|---|
| IntelliSense 补全 | 语言服务提供的符号、方法、类名 | 输入字符后自动弹出 | 输入Str提示String |
| Emmet | HTML/CSS 的缩写展开 | Tab 或 Enter | ul>li*3、! |
| 自定义片段 | 你自己定义的固定文本模板 | 输入 prefix 后 Tab | sout、psvm、html5 |
三者会同时往提示列表里塞内容,所以优先级和冲突是必须考虑的事。举个最常见的例子:在 html 文件里,Emmet 默认把!绑定成了 HTML5 骨架,如果你自定义了一个 prefix 叫!的片段,两者就会打架,最终谁赢取决于提示列表里的排序,很不稳定。稳妥做法是给自己的片段取一个绝对不会撞车的 prefix,比如html5、h5doc、page这种。
另外要提醒一点:自定义片段是纯文本替换,它不理解语法。你写$1它就是个光标停靠点,你写public它也不会去做语法校验。这个特性是优点也是缺点——优点是你可以拿它生成任何东西,包括一段注释、一段 SQL、一段配置;缺点是如果你把模板写错了,它照样原封不动地贴进代码里,不会提醒你。
2. 自定义代码片段的存放位置与文件组织
2.1 三种创建入口:全局、语言、项目级
vscode 给了三个层级的片段文件,选错层级是新手最容易踩的坑。
第一种是全局片段文件,文件名形如xxx.code-snippets,放在用户配置目录下。它的特点是文件里可以自由指定scope字段,一个文件里塞 Java、Python、Shell 的片段都行。适合放"跨项目通用"的东西,比如版权头注释、常用的TODO模板。
第二种是语言专属片段文件,通过"首选项:配置用户代码片段"菜单选具体语言创建,文件名固定为java.json、html.json、javascript.json等。这种文件不需要写scope,因为它天然只对对应语言生效。我个人强烈推荐这种方式,理由很简单:写 Java 片段不可能误触发到 Python 文件里,天然隔离,排查问题的时候也能快速缩小范围。
第三种是项目级片段,放在项目根目录的.vscode/xxx.code-snippets。它的价值在于可以提交到 Git,团队拉下来就有统一的模板。比如你们团队规定每个 Vue 组件必须带<script setup>和固定的注释头,那把这个片段放进项目里,比写进 Wiki 让人自己抄靠谱得多。
2.2 各平台文件路径速查与命名规则
菜单点进去最省事,但知道真实路径有好处——比如你想用 Git 管理这些片段,或者想直接复制别人整理好的文件。
- Windows:
%APPDATA%\Code\User\snippets\ - macOS:
~/Library/Application Support/Code/User/snippets/ - Linux:
~/.config/Code/User/snippets/
如果你用的是便携版或者指定了--user-data-dir启动参数,路径会跟着变,这时候走菜单最保险。
命名规则上有一条硬性要求:语言专属片段文件的名字必须和 vscode 内部的语言 ID 一致。比如 Java 是java.json,不是Java.json也不是jav.json;HTML 是html.json;Markdown 是markdown.json。写错了会怎样?文件照样存在,但它永远不会被加载,你会在"为什么我的片段不生效"里绕很久。语言 ID 可以在编辑器右下角的状态栏看到,点一下就能看到当前文件的 Language Mode,那行英文就是 ID。
2.3 片段文件的最小结构
一个能用的片段,最少需要prefix和body两个字段。完整结构长这样:
{ "片段名称(显示在提示列表里的名字)": { "scope": "javascript,typescript", "prefix": "触发的关键词", "body": [ "第一行内容", "第二行内容 $1", "$0" ], "description": "提示列表右侧显示的说明文字" } }body用数组写,每个元素是一行,这样不用在字符串里塞\n,可读性好很多。你也可以写成单个字符串用\n分隔,但改起来痛苦,不推荐。
scope只在全局.code-snippets文件里需要,逗号分隔多个语言 ID。不写scope意味着"所有语言都能触发",这通常会带来误触发的麻烦。
3. Java 场景实战:把 sout、psvm 一行不漏地搬过来
3.1 sout / soutv / soutp 的写法与制表位设计
打开java.json,先把最核心的几个打印片段写进去:
{ "System.out.println": { "prefix": "sout", "body": [ "System.out.println($1);$0" ], "description": "打印一行内容" }, "System.out.println 带变量名": { "prefix": "soutv", "body": [ "System.out.println(\"$1 = \" + $1);$0" ], "description": "打印变量名和值,IDEA 同款 soutv" }, "System.out.print": { "prefix": "soutp", "body": [ "System.out.print($1);$0" ], "description": "不换行打印" } }这里的关键是制表位的设计。$1是第一个光标停靠点,$0是最后光标停靠的位置。sout这行我写的是System.out.println($1);$0,插入之后光标会先落在括号里,你输入完变量,再按一次 Tab 跳到$0,也就是分号后面。这个顺序和 IDEA 的sout完全一致,肌肉记忆可以直接复用。
soutv更有意思。IDEA 的soutv会打印变量名 + 值,比如System.out.println("count = " + count);。vscode 的片段机制没法自动读取你选中的变量名(除非用$TM_SELECTED_TEXT,下面会讲),所以最省事的写法就是把$1用两次——一次放在字符串里,一次放在加号后面。插入片段后你输一遍变量名,Tab 一下两处同步更新,实际体验和 IDEA 差不了多少。
注意:
body里如果要用双引号,必须写成\"转义,这是 JSON 的硬性规则,不是 vscode 的要求。漏了转义符,整个片段文件会直接报错失效,而且报错信息只在编辑器顶部的波浪线上提示,很容易被忽略。
3.2 psvm、fori、iter 这些高频模板怎么配
main 方法是必配项:
{ "public static void main": { "prefix": "psvm", "body": [ "public static void main(String[] args) {", " $0", "}" ], "description": "main 方法骨架" }, "main 方法(简写参数名)": { "prefix": "main", "body": [ "public static void main(String[] args) {", " $0", "}" ], "description": "同上,备用触发词" } }缩进我用了四个空格。如果你团队用 Tab 缩进,就把那四个空格换成一个\t。这里有个细节值得说:vscode 默认开启了editor.formatOnType或者保存时格式化,你插入的骨架可能会被格式化器重新排版。如果格式化器配置的缩进宽度是 2,而你片段里写的是 4 个空格,插入瞬间就会被改成 2 格,看起来很诡异。解决办法是让片段里的缩进和项目的.editorconfig保持一致,或者干脆只写$0位置不写多余缩进,交给格式化器处理。
循环相关的fori和iter也很常用:
{ "for 循环(带索引)": { "prefix": "fori", "body": [ "for (int ${1:i} = 0; ${1:i} < ${2:limit}; ${1:i}++) {", " $0", "}" ], "description": "经典 fori 循环" }, "增强 for 循环": { "prefix": "iter", "body": [ "for (${1:String} ${2:item} : ${3:list}) {", " $0", "}" ], "description": "遍历集合" } }fori这段用了${1:i}这种带默认值的占位符。插入后i是预设好的,你不用输,直接 Tab 跳到limit位置填边界,再 Tab 一下,三处i会同步改名——这就是镜像制表位的效果:同一个编号的占位符在多个位置出现时,编辑其中一个,其余全部跟着变。这个特性在写fori时特别有用,因为索引变量名通常要改(比如换成j、idx),有了镜像就不用改三遍了。
3.3 占位符、默认值、下拉选项的高级用法
除了基础的$1、$2,vscode 的片段语法还有几个值得掌握的进阶写法。
带默认值的占位符:${1:默认文本}。插入后默认文本处于选中状态,你直接输入就会替换掉它,不输入就直接用默认值。
下拉选择:${1|public,private,protected|}。插入后会出现一个小下拉框,用方向键选,回车确认。这个在写 Java 修饰符、写 HTML 的method属性(get/post)时特别顺手。
变量:$TM_FILENAME(当前文件名)、$TM_FILENAME_BASE(不含扩展名的文件名)、$TM_DIRECTORY、$CLIPBOARD(剪贴板内容)、$CURRENT_YEAR、$CURRENT_MONTH、$CURRENT_DATE,还有$RANDOM、$UUID。
举一个把变量用起来的实际例子——生成带类名的日志声明:
{ "Logger 声明": { "prefix": "logd", "body": [ "private static final Logger logger = LoggerFactory.getLogger(${TM_FILENAME_BASE}.class);$0" ], "description": "按当前文件名生成 Logger" } }$TM_FILENAME_BASE会自动替换成当前文件的类名,省掉手敲一遍的功夫。这类片段在批量新建类的场景下非常省事。
3.4 用变量和正则转换器做"智能片段"
再往上走一层,片段里还能做正则替换。语法是${变量/正则/替换/选项},末尾的选项可以是/upcase、/downcase、/capitalize、/camelcase、/pascalcase、/snakecase、/kebabcase这些(需要较新版本的 vscode 支持)。
举个实际会用到的例子:把文件名转成帕斯卡命名,用来生成类名或组件名。
{ "按文件名生成 Pascal 命名": { "prefix": "clsname", "body": [ "${TM_FILENAME_BASE/(.*)/${1:/pascalcase}/}$0" ], "description": "文件名转大驼峰" } }如果文件叫user_service.java,插入后就变成UserService。同理把/pascalcase换成/camelcase就得到userService。
还有一个用到剪贴板和选中文本的组合技巧:$TM_SELECTED_TEXT会取你当前选中的内容。如果你选中一段文本再插入片段,选中内容会被塞进片段里。配合soutv的写法,就能做到"选中变量名,一键生成打印语句":
{ "打印选中内容": { "prefix": "printsel", "body": [ "System.out.println(\"$TM_SELECTED_TEXT = \" + $TM_SELECTED_TEXT);$0" ], "description": "把选中的变量名展开成打印语句" } }实测下来这套组合在调试阶段特别高效:选中变量,敲printsel,Tab,一行日志就有了。比 IDEA 的soutv还少一步。
4. HTML 场景实战:从 doctype 骨架到一键回到顶部
4.1 为什么内置!还不够,自定义 html.json 的价值
在 html 文件里敲!然后 Tab,Emmet 会给你一个 HTML5 骨架。但它生成的是英文默认版本,lang="en",没有 viewport 元信息,也没有 favicon 引用。对于中文项目来说,每次都要手动把en改成zh-CN、手动补 viewport,这本身就是个重复劳动。
更关键的是,Emmet 生成的是通用骨架,而真实项目往往需要一堆额外的头部信息:字符集声明、视口配置、SEO 用的 description 和 keywords、移动端适配的format-detection、禁用电话识别的 meta、分享卡片用的 Open Graph 标签。这些加起来十几行,每次新建页面都敲一遍不现实。所以自定义一个html5片段,把这些一次性写全,是性价比很高的一件事。
4.2 完整 HTML5 骨架片段(含 viewport 与 lang)
下面这份可以直接抄进html.json:
{ "HTML5 中文页面骨架": { "prefix": "html5", "body": [ "<!DOCTYPE html>", "<html lang=\"zh-CN\">", "<head>", " <meta charset=\"UTF-8\">", " <meta name=\"viewport\" content=\"width=device-width, initial-scale=1.0\">", " <meta name=\"description\" content=\"${1:页面描述}\">", " <meta name=\"keywords\" content=\"${2:关键词}\">", " <title>${3:页面标题}</title>", " <link rel=\"stylesheet\" href=\"${4:./style.css}\">", "</head>", "<body>", " $0", " <script src=\"${5:./main.js}\"></script>", "</body>", "</html>" ], "description": "带 meta 与资源引用的中文 HTML5 骨架" } }写得长一点是有意为之。$1到$5的顺序是按真实建页面的思考路径排的:先填描述和关键词,再写标题,然后引样式,最后引脚本。$0放在 body 里,因为那是你下一步真正要写内容的地方。这一套下来,新建一个静态页面从"敲十几行 + 手动改 lang"变成"敲 5 个词 + Tab 跳 6 下"。
提示:
<!DOCTYPE html>里的感叹号在 JSON 字符串里不需要转义,直接写就行。但如果你的片段里出现反斜杠(比如要在输出里出现\n这个字面量),需要写成\\n,否则会被解析成换行。
4.3 常用结构与返回顶部算法片段
除了骨架,日常写页面还会反复出现几种结构。表格骨架:
{ "表格骨架": { "prefix": "table5", "body": [ "<table class=\"${1:data-table}\">", " <thead>", " <tr>", " <th>${2:列一}</th>", " <th>${3:列二}</th>", " </tr>", " </thead>", " <tbody>", " <tr>", " <td>$4</td>", " <td>$5</td>", " </tr>", " </tbody>", "</table>", "$0" ], "description": "带表头的两列表格" } }再就是"一键返回顶部"这个高频需求。它的核心逻辑其实就三件事:一个固定定位的按钮、监听滚动距离决定显示隐藏、点击后平滑滚回顶部。把这些打包成一个片段,比每次去搜索引擎翻代码复制粘贴快得多:
{ "返回顶部按钮": { "prefix": "backtop", "body": [ "<button id=\"backTop\" class=\"back-top\" aria-label=\"返回顶部\">↑</button>", "<style>", ".back-top {", " position: fixed;", " right: 24px;", " bottom: 24px;", " width: 44px;", " height: 44px;", " border: none;", " border-radius: 50%;", " cursor: pointer;", " opacity: 0;", " visibility: hidden;", " transition: opacity .25s ease;", "}", ".back-top.is-visible {", " opacity: 1;", " visibility: visible;", "}", "</style>", "<script>", "(function () {", " var btn = document.getElementById('backTop');", " var threshold = ${1:300};", " function toggle() {", " if (window.scrollY > threshold) {", " btn.classList.add('is-visible');", " } else {", " btn.classList.remove('is-visible');", " }", " }", " window.addEventListener('scroll', toggle, { passive: true });", " btn.addEventListener('click', function () {", " window.scrollTo({ top: 0, behavior: 'smooth' });", " });", " toggle();", "})();", "</script>", "$0" ], "description": "自带样式与滚动监听的返回顶部组件" } }这段代码有两个细节值得展开讲。第一,滚动监听加了{ passive: true },告诉浏览器这个回调不会调用preventDefault,浏览器就能把滚动和回调放到不同线程处理,移动端滑动明显更跟手。第二,用classList控制显隐而不是直接改style.display,这样 CSS 过渡动画才有机会生效,直接切 display 是不会有淡入淡出效果的。
那个${1:300}的阈值也是有意留下的可调参数。页面很长的时候 300 太小,按钮一进页面就冒出来,观感不好;页面很短的时候 300 又太大,永远不显示。这个小数值最好按实际情况调。
4.4 转义、缩进与格式化冲突的处理
HTML 片段比 Java 片段更容易遇到转义问题,因为 HTML 属性全是双引号。记住三条:
- JSON 字符串里的
"一律写成\" - 要输出字面的反斜杠,写成
\\ - 要输出字面的美元符号
$,写成\\$(vscode 里$是占位符语法的一部分)
缩进方面,HTML 嵌套层数多,片段里的空格数量很容易和格式化器打架。比较稳妥的做法是:片段里只保留结构,缩进写 4 个空格保证可读,然后依赖格式化器统一。如果你不希望格式化器动你的片段输出,可以在项目里加一段配置:
{ "[html]": { "editor.formatOnSave": false } }但我不太建议直接关掉格式化,更推荐给项目根目录加一个.prettierrc或者.editorconfig,明确写死indent_size = 4之类的规则,让片段和格式化器用同一套缩进标准,从根上消除冲突。
5. IDEA Live Template 批量迁移:半自动化脚本思路
5.1 迁移前先分类:哪些值得搬,哪些该丢
IDEA 的 Live Template 管理面板里能导出一份模板配置,格式是 XML 或带分隔符的表格。但不要试图全量搬过来,几百个模板塞进 vscode 只会让提示列表变成垃圾场,输入两个字母弹出一屏候选,反而更慢。
我的分类标准是这样的:
| 类型 | 是否迁移 | 理由 |
|---|---|---|
| 打印、循环、main 方法 | 必迁 | 高频,短,收益立竿见影 |
| 测试方法、断言模板 | 建议迁 | JUnit 场景高频,结构固定 |
| try-catch、日志埋点 | 建议迁 | 结构固定但长度中等 |
| 项目特定的业务脚手架 | 谨慎 | 更适合放项目级片段而非全局 |
| 冷门工具类模板 | 不迁 | 一年用不上两次,占坑不值 |
按这个标准筛一遍,一般从几百个模板里能砍到二三十个,这二三十个才是真正提升手感的。
5.2 一段转换脚本把常用模板变成 JSON
手写 JSON 太慢,可以写个小脚本把导出的模板表格转成 vscode 的格式。假设你导出的文件是 CSV,每行是缩写,模板内容:
import csv import json def to_snippet(abbr, text, desc=""): lines = text.replace("\r\n", "\n").split("\n") # 把 IDEA 的 $VAR$ 变量语法转成 vscode 的 $1 制表位 converted = [] index = 1 for line in lines: while "$" in line and line.count("$") >= 2: start = line.index("$") end = line.index("$", start + 1) line = line[:start] + "${%d}" % index + line[end + 1:] index += 1 converted.append(line) converted.append("$0") return abbr, { "prefix": abbr, "body": converted, "description": desc or abbr } result = {} # utf-8-sig 用来吃掉 BOM,避免解析出空行 with open("templates.csv", encoding="utf-8-sig", newline="") as f: for row in csv.reader(f): if len(row) < 2: continue abbr, text = row[0].strip(), row[1] if not abbr: continue _, snippet = to_snippet(abbr, text) result[abbr] = snippet with open("java.json", "w", encoding="utf-8") as f: json.dump(result, f, ensure_ascii=False, indent=2)这个脚本只做了最基础的转换,两个地方需要注意。一是utf-8-sig这个编码,Windows 上导出的 CSV 十有八九带 BOM,用普通utf-8读第一行的键名会多出一个不可见字符,导致 JSON 里出现一个诡异的 key。二是变量的转换逻辑只做了简单配对,IDEA 的模板变量语法比这复杂得多,带默认值的、带表达式的(比如$VAR$自动推导类型)都处理不了,这些只能手工补。
5.3 迁移后必须手工核对的几类模板
脚本跑完不等于能用,下面几类必须逐个开文件检查。
第一类是含正则或表达式的模板。IDEA 的 Live Template 支持groovyScript之类的表达式,能根据上下文动态推类型、推包名。vscode 的片段做不到这个程度,最多用$TM_FILENAME_BASE加正则转换凑合。这类模板要么降级成静态文本,要么干脆放弃。
第二类是依赖 IDE 动作的模板。有些模板会顺带触发"自动导包"或者"重命名重构",vscode 片段只是纯文本插入,不会触发任何动作。这类模板搬过来之后,你得自己手动补 import。
第三类是缩进敏感的模板。IDEA 的模板引擎会自动按当前位置的缩进层级对齐,vscode 的片段是硬编码缩进,插在深层嵌套里就会歪。解决办法是尽量把片段的内容写成"相对缩进",或者接受它插入后手动Shift+Alt+F格式化一下。
6. 常见问题与排查清单
6.1 片段写了不生效的几个高频原因
片段不生效是最常见的求助,按照下面顺序排查基本一次就能定位。
第一,文件放错位置或名字写错。语言专属片段必须叫java.json、html.json,写在snippets目录下,大小写敏感。放在别处的文件不会被加载。
第二,JSON 语法错误。少个逗号、多个逗号、双引号没转义,整个文件都会失效,而且 vscode 不会弹窗提示你,只会在编辑器里画一条红波浪线。写完后扫一眼有没有红线是基本功。
第三,scope 不匹配。全局.code-snippets文件里的scope字段如果写了javascrip(少个 t),那它永远不生效。不确定的话直接删掉scope字段测一下,能触发就是 scope 写错了。
第四,当前文件的语言模式不对。看着是 Java 文件,其实可能是"纯文本"模式。看右下角状态栏确认。
第五,editor.tabCompletion被关掉了。这个设置控制 Tab 键是否用于展开片段。如果是off,输入 prefix 后按 Tab 只会插入一个制表符。改成on,或者用Ctrl+Space手动唤出补全列表再选。
第六,输入法拦截了按键。中文输入法在拼音候选状态下,Tab 和回车经常被输入法吃掉,用来选字而不是选片段。切到英文状态再试。
第七,prefix 和其他片段冲突。两个片段都叫sout的时候,vscode 只会展示其中一个。给它们取不同的名字。
6.2 问题速查表
| 现象 | 可能原因 | 处理办法 |
|---|---|---|
| 输入 prefix 什么都不弹 | 文件位置/命名错误 | 检查snippets目录与文件名 |
| 弹出了但选中后没展开 | editor.tabCompletion为 off | 改为on或用Ctrl+Space选择 |
| 整个文件片段全失效 | JSON 语法错误 | 检查红波浪线与逗号、转义 |
| 只在部分文件生效 | scope 写错或语言模式不对 | 删除 scope 复测,确认右下角语言 |
| 展开后缩进全乱 | 缩进风格与格式化器不一致 | 统一.editorconfig的 indent_size |
| 输出里多了奇怪字符 | 反斜杠或$未转义 | \\与\\$ |
| 中文输入时 Tab 没反应 | 输入法处于候选状态 | 切英文输入法 |
6.3 Tab 展开、补全冲突与输入法干扰
关于 Tab 展开,有一个容易被忽略的细节:vscode 优先响应 Let 智能提示列表里的第一项,而不是你的片段。如果列表第一项是某个同名的变量或方法,你按 Tab 就会插入那个,而不是片段。这时候有两个办法:一是用Ctrl+Space打开列表后手动选中你的片段项;二是把 prefix 改得更独特一点,减少撞名概率。
还有一个值得调的设置是editor.suggest.snippetsPreventQuickSuggestions。这个选项控制"当光标紧跟在片段前缀后面时,是否阻止其他智能提示弹出"。默认值在某些版本里是true,意思是只要你输入的文字能匹配到片段,其他补全就不再弹出来捣乱。如果你觉得它太激进,影响正常的代码补全,可以改成false,代价是片段和普通补全需要你自己在列表里选。
输入法这块没什么技术解法,只能靠习惯:写代码时保持英文输入状态,需要写中文注释时再切回去。用微软拼音的话,可以在设置里把"中英文切换"设成单独的 Shift 键,切换成本比 Ctrl+Space 低一些。
6.4 团队共享与片段同步方案
片段的同步有三条路,各有适用场景。
Settings Sync是最省事的。vscode 自带的同步功能可以把用户片段一起同步到云端,换电脑登录一下账号就回来了。缺点是它同步的是"你的"配置,同一个团队的人如果各自维护,容易越走越偏。
Git 仓库更适合团队统一。把snippets目录单独抽出来放一个私有的 Git 仓库,再用软链接(Windows 上可以用目录联接)指到 vscode 的配置路径。这样修改片段就是一次 commit,团队其他人 pull 一下就同步了。缺点是软链接在 Windows 上偶尔会被某些安全策略拦截,需要额外配置。
项目级片段是最克制的方案。把.vscode/xxx.code-snippets提交到项目仓库里,只放这个项目专属的模板,比如你们项目特有的 API 请求封装样板、特有的日志格式。它不影响开发者的全局配置,又能保证同一个仓库里的人模板一致,我个人最推荐这个做法。
我自己的组合是:用户级java.json放sout、psvm、fori这类通用模板,跟着 Settings Sync 走;项目级放业务脚手架,跟着 Git 走。两者互不干扰,换项目也不会串味。
最后再分享一个小技巧:片段写完不用重启 vscode,保存文件即时生效,这一点比很多插件的体验好。所以我的习惯是遇到重复敲了三遍以上的代码块,立刻停下写个片段,一分钟的事,后面省的是几十次重复劳动。真正值得注意的其实是克制——片段文件会随着时间越来越臃肿,我大概每隔两三个月会翻一遍,把半年没用过的删掉。判断标准很粗暴:如果某个 prefix 我自己都要愣一下才想起来它是干嘛的,那它就该删了。我目前这套 java.json 稳定在二十个片段左右,html.json 十五个上下,提示列表干净,触发几乎零误选,这个规模用下来是最舒服的。