1. 从「AI味」说起前端代码为什么一眼就能被认出来做前端这些年我审过的代码没有一万份也有八千份了。最近一两年有个特别明显的变化越来越多的代码我扫一眼就知道是AI写的。不是因为它写得差恰恰相反很多时候它写得太标准了——标准到失去了人味。什么叫「AI味」我总结下来大概是这么几个特征变量命名永远是data、result、temp、item这种万能词组件拆分永远按一个功能一个文件的教科书逻辑来样式写法永远是display: flex; justify-content: center; align-items: center;三件套错误处理永远是try-catch包一层然后console.log(error)注释永远在解释这行代码做了什么而不是为什么要这么做。这种代码能跑能过测试甚至能过Code Review——因为Review的人也在用AI辅助大家的审美被拉到了同一个水平线上。但问题在于当整个团队的代码都长一个样的时候维护成本会指数级上升。你改一个按钮的样式发现三个地方有类似的实现但你不知道哪个是正主你查一个bug发现五个文件里都有类似的逻辑但每个都差那么一点点。这就是「AI味」的本质它追求的是局部最优而不是全局一致。AI每次生成代码都是独立的它不知道你项目里已经有一个formatDate工具函数了所以它会再写一个它不知道你们团队的按钮组件已经封装了loading状态所以它会再写一遍。久而久之代码库就变成了一个看起来整洁但实际混乱的缝合怪。那怎么办最近圈子里在传一个叫taste-skill的东西配合Agent Skills和SKILL.md这套机制据说能让AI生成的代码去味。我花了两周时间在自己的项目里试了一遍下面把完整的思路、配置和踩坑记录整理出来。注意本文讨论的Agent Skills是一套通用的AI代理技能描述规范不涉及任何特定平台或工具。SKILL.md是技能描述文件的通用格式你可以把它理解成给AI看的项目规范说明书。2. Agent Skills与SKILL.md给AI立规矩的底层逻辑2.1 为什么需要一套技能描述机制先说清楚一个前提AI写代码之所以有「AI味」根本原因不是模型能力不够而是上下文缺失。你在对话框里输入帮我写一个登录表单AI只能根据它训练数据里的平均登录表单来生成。它不知道你的项目用的是Vue3还是React不知道你们的设计系统主色是#1890ff还是#1677ff不知道你们的表单校验用的是async-validator还是zod。传统的解决办法是写很长的Prompt把项目规范一股脑塞进去。但这样做有几个问题第一Prompt太长会稀释注意力AI会漏掉后面的要求第二每次对话都要重复粘贴效率极低第三规范更新了之前的历史对话不会自动同步。Agent Skills这套机制的核心思路是把项目规范从一次性Prompt变成持久化技能文件。你写一个SKILL.md里面描述清楚这个项目的技术栈、代码风格、目录结构、命名约定、常用工具函数然后让AI在生成代码之前先读这个文件。这样每次生成的代码都会自动对齐项目规范而不是每次从零开始猜。2.2 SKILL.md的文件结构设计我试过好几种SKILL.md的写法最后沉淀下来一个比较稳定的结构。核心原则是AI能读懂人也能维护。不要写成散文也不要写成配置文件而是介于两者之间——用Markdown的层级结构来组织关键信息用列表和代码块突出。一个典型的SKILL.md大概长这样# 项目技能描述 ## 技术栈 - 框架Vue 3.4 TypeScript 5.3 - 构建Vite 5.0 - 状态管理Pinia 2.1 - 路由Vue Router 4.2 - UI库自研组件库 company/ui - 样式SCSS BEM命名 ## 目录结构约定 - src/components/ 通用组件每个组件一个文件夹 - src/views/ 页面级组件按路由路径组织 - src/composables/ 组合式函数use开头 - src/utils/ 纯函数工具按功能分文件 - src/api/ 接口定义按模块分文件 ## 命名约定 - 组件文件PascalCase如 UserProfile.vue - 组合式函数camelCaseuse开头如 useUserInfo.ts - 工具函数camelCase动词开头如 formatDate.ts - 常量UPPER_SNAKE_CASE如 MAX_RETRY_COUNT - 类型PascalCaseI开头可选如 UserInfo 或 IUserInfo ## 代码风格 - 优先使用组合式API禁止Options API - 优先使用script setup语法糖 - 禁止使用any必要时用unknown 类型守卫 - 异步操作统一用async/await禁止.then链式调用 - 错误处理统一用try-catchcatch中必须处理或上报 ## 常用工具函数禁止重复实现 - formatDate(date, format) - 日期格式化 - debounce(fn, delay) - 防抖 - throttle(fn, delay) - 节流 - deepClone(obj) - 深拷贝 - storage.get/set/remove - 本地存储封装 ## 组件开发规范 - 所有组件必须定义props类型和默认值 - 所有组件必须处理loading和error状态 - 所有组件必须支持v-model如适用 - 样式必须使用scoped禁止全局污染 - 禁止在组件内直接调用API必须通过composable这个文件大概200行左右写一次能用很久。关键是它把隐性知识变成了显性规则。以前这些规范都在老员工的脑子里新人来了要口口相传现在写进SKILL.mdAI和人都能读。2.3 技能加载的时机与优先级这里有个实操细节SKILL.md不是越长越好。我试过写一个800行的版本结果AI反而抓不住重点。后来改成分层加载的策略基础层技术栈、目录结构、命名约定这些是每次生成代码都必须遵守的放在文件最前面。场景层组件开发规范、API调用规范、样式规范这些是特定场景才需要的放在中间。参考层常用工具函数列表、代码示例这些是备查的放在最后。然后在Prompt里明确告诉AI优先遵守基础层场景层根据当前任务选择性遵守参考层仅用于避免重复实现。这样AI的注意力分配会更合理。实操心得SKILL.md最好放在项目根目录文件名全大写这样在文件列表里一眼就能看到。另外建议加一个CHANGELOG段落记录每次修改的原因方便团队追溯。3. 去「AI味」的核心技术点拆解3.1 命名去味从万能词到领域词「AI味」最重的地方就是命名。AI特别喜欢用data、result、temp、item、list这种词因为它们安全——不会错但也没信息量。人写的代码不一样人会根据业务语境起名比如pendingOrders、activeUsers、expiredCoupons。我在SKILL.md里加了一条硬规则禁止使用万能词作为变量名必须体现业务语义。具体做法是给AI一个命名映射表禁止命名推荐命名说明datauserProfile / orderList根据实际内容命名resultfetchResult / submitResponse体现操作来源tempdraftContent / cachedValue体现临时性质itemproduct / comment / message体现元素类型listproducts / comments / messages用复数形式flagisLoading / hasPermission用布尔语义objconfig / options / params体现对象用途arrtags / ids / names体现数组内容这张表看起来简单但效果立竿见影。我对比过同一段逻辑用AI生成两次加了命名规则之后变量名从data1、data2、result变成了userInfo、orderDetail、submitResponse可读性完全不是一个级别。3.2 结构去味从功能拆分到职责拆分AI拆组件有个固定套路一个功能一个文件。比如做一个用户列表页它会拆成UserList.vue、UserItem.vue、UserSearch.vue、UserPagination.vue。这没错但太机械了。人拆组件会考虑复用性和职责边界比如搜索框可能和别的页面共用那就抽到components/common/SearchInput.vue分页器可能整个项目都用同一个那就用UI库的。我在SKILL.md里加了一条组件拆分必须说明复用场景禁止为拆分而拆分。具体做法是要求AI在生成组件之前先输出一个组件职责表## 组件职责表 - UserListPage.vue页面容器负责数据获取和状态管理 - UserTable.vue表格展示接收users数组发出edit/delete事件 - UserSearchBar.vue搜索栏复用common/SearchInput发出search事件 - UserPagination.vue分页器复用company/ui的Pagination组件这样AI在生成代码之前会先想清楚这个组件为什么存在而不是无脑拆分。实测下来组件数量减少了30%左右但复用率提升了一倍。3.3 样式去味从三件套到设计系统AI写样式有个经典三件套display: flex; justify-content: center; align-items: center;。不管什么场景先来一套居中。还有margin: 0 auto;、padding: 20px;、border-radius: 4px;这些万能值。去味的关键是让AI用设计系统的变量而不是硬编码值。我在SKILL.md里定义了一套设计令牌// 间距 $spacing-xs: 4px; $spacing-sm: 8px; $spacing-md: 16px; $spacing-lg: 24px; $spacing-xl: 32px; // 圆角 $radius-sm: 2px; $radius-md: 4px; $radius-lg: 8px; // 颜色 $color-primary: #1890ff; $color-success: #52c41a; $color-warning: #faad14; $color-error: #f5222d; $color-text-primary: rgba(0, 0, 0, 0.85); $color-text-secondary: rgba(0, 0, 0, 0.65);然后规定所有样式必须使用设计令牌禁止硬编码数值。AI一开始会不习惯但只要你把令牌列表给它它就会乖乖用$spacing-md代替16px。这样做的好处是以后设计改版只需要改令牌文件所有组件自动更新。3.4 逻辑去味从能跑就行到边界清晰AI写的逻辑有个特点主流程很顺边界情况很糙。比如写一个表单提交它会写async function submit() { try { const res await api.submit(form) if (res.code 200) { message.success(提交成功) } else { message.error(res.message) } } catch (error) { console.log(error) } }这段代码能跑但问题很多没有loading状态、没有防重复提交、没有表单校验、错误处理太粗糙。人写的代码会考虑这些边界因为人知道线上环境有多复杂。我在SKILL.md里加了一个逻辑检查清单要求AI在生成任何异步逻辑之前先过一遍[ ] 是否有loading状态[ ] 是否有防重复提交[ ] 是否有表单校验[ ] 是否有错误提示[ ] 是否有成功反馈[ ] 是否有超时处理[ ] 是否有取消机制[ ] 是否有数据缓存这个清单逼着AI把边界情况想全。实测下来加了清单之后AI生成的代码在Code Review中被挑出的问题减少了60%以上。4. 完整实操从零搭建一套去味工作流4.1 环境准备与文件组织先说清楚这套工作流不依赖任何特定工具你用什么编辑器、什么AI助手都行。核心是三个文件project-root/ ├── SKILL.md # 技能描述主文件 ├── .ai/ │ ├── naming.md # 命名规范细则 │ ├── patterns.md # 代码模式库 │ └── checklist.md # 逻辑检查清单 └── src/ └── ...SKILL.md是入口.ai/目录下是细则。这样组织的好处是主文件保持精简细则按需加载。AI在生成代码时先读SKILL.md如果涉及命名就去读naming.md涉及复杂逻辑就去读checklist.md。4.2 命名规范细则的编写naming.md的核心是场景-命名映射。我按业务场景分类每个场景给出推荐命名和禁止命名# 命名规范细则 ## 数据获取场景 - 推荐fetchUserList / getUserDetail / queryOrders - 禁止getData / fetchInfo / queryList - 变量userList / orderDetail / productInfo ## 状态管理场景 - 推荐isLoading / hasError / canSubmit - 禁止loading / error / flag - 变量submitStatus / fetchState / formValid ## 事件处理场景 - 推荐handleSubmit / onUserSelect / emitSearch - 禁止onClick / handleEvent / doSomething - 变量selectedUser / searchKeyword / activeTab ## 工具函数场景 - 推荐formatDate / parseQuery / debounce - 禁止util1 / helper / tool - 变量formattedDate / queryParams / debouncedFn这个文件大概100行覆盖了80%的命名场景。关键是它给出了替代方案AI知道不用data之后该用什么。4.3 代码模式库的沉淀patterns.md是我觉得最有价值的部分。它把项目里反复出现的代码模式抽象成模板AI直接套用就行。比如# 代码模式库 ## 异步数据获取模式 javascript const loading ref(false) const error ref(null) const data ref(null) async function fetchData() { loading.value true error.value null try { data.value await api.getData() } catch (e) { error.value e message.error(获取数据失败) } finally { loading.value false } }表单提交模式const submitting ref(false) async function handleSubmit() { if (submitting.value) return const valid await formRef.value.validate() if (!valid) return submitting.value true try { await api.submit(formData) message.success(提交成功) emit(success) } catch (e) { message.error(e.message || 提交失败) } finally { submitting.value false } }列表分页模式const pagination reactive({ page: 1, pageSize: 20, total: 0 }) async function fetchList() { const { list, total } await api.getList({ page: pagination.page, pageSize: pagination.pageSize }) data.value list pagination.total total }这些模式不是凭空写的是从项目里实际代码抽象出来的。AI套用这些模式之后生成的代码风格和项目现有代码高度一致Review的时候几乎看不出是AI写的。 ### 4.4 逻辑检查清单的使用 checklist.md是最后一道防线。我把它设计成生成前检查和生成后检查两部分 markdown # 逻辑检查清单 ## 生成前检查AI自问 - 这个功能的核心职责是什么 - 有没有现成的工具函数可以复用 - 有没有类似的组件可以参考 - 边界情况有哪些 ## 生成后检查AI自查 - [ ] 所有变量命名是否体现业务语义 - [ ] 是否使用了设计令牌而非硬编码 - [ ] 异步操作是否有loading和error处理 - [ ] 是否有防重复提交 - [ ] 是否有表单校验 - [ ] 是否处理了空数据和异常数据 - [ ] 是否有必要的注释解释为什么 - [ ] 是否遵循了项目的目录结构这个清单看起来啰嗦但效果很好。AI在生成代码之后会自己过一遍发现问题会自动修正。我统计过加了清单之后AI生成的代码一次通过率从40%提升到了75%。4.5 实际生成效果对比说再多不如看效果。我拿同一个需求做一个用户反馈表单分别用裸AI和去味工作流生成对比一下维度裸AI生成去味工作流生成变量命名data, result, tempfeedbackForm, submitResult, formErrors组件拆分1个文件搞定拆成FeedbackForm FeedbackTypeSelect样式写法硬编码16px, #1890ff使用$spacing-md, $color-primary异步处理try-catch console.logloading error 防重复提交表单校验无完整校验规则 错误提示代码行数120行180行Review问题数8个2个代码行数多了但质量高了。多出来的60行全是边界处理和规范对齐这些恰恰是「AI味」最重的地方。5. 常见问题与排查技巧实录5.1 AI不遵守SKILL.md怎么办这是最常见的问题。你写了SKILL.md但AI生成代码的时候还是我行我素。原因通常有三个第一文件太长AI没读完。解决办法是把核心规则放在文件前50行用## 必须遵守这样的标题突出。AI的注意力是有限的前面的内容权重更高。第二规则太抽象AI理解不了。比如你写代码要优雅AI不知道什么叫优雅。改成禁止使用any类型禁止使用console.log禁止使用varAI就知道怎么做了。规则要具体、可执行、可验证。第三没有在Prompt里显式引用。你光有SKILL.md不够还要在每次对话时告诉AI请先阅读SKILL.md然后按照其中的规范生成代码。最好把这句话做成模板每次复制粘贴。实操心得我试过在SKILL.md开头加一句如果你没有读完这个文件请不要生成任何代码效果出奇地好。AI会先确认自己读完了再开始生成。5.2 规则冲突怎么处理项目大了规则难免冲突。比如naming.md说变量用camelCase但patterns.md里的示例用了snake_case。AI遇到这种情况会随机选一个导致风格不一致。解决办法是建立优先级。在SKILL.md里明确写## 规则优先级 1. SKILL.md 中的规则优先级最高 2. .ai/naming.md 次之 3. .ai/patterns.md 中的示例仅供参考如与命名规则冲突以命名规则为准 4. .ai/checklist.md 用于自查不强制有了优先级AI就知道该听谁的。另外建议定期审查规则文件把冲突的地方改掉。我一般每个月过一遍把过时的规则删掉把新沉淀的模式加进去。5.3 老项目怎么接入老项目接入去味工作流最大的问题是历史代码和规范不一致。你写了SKILL.md说禁止使用Options API但项目里一半的组件都是Options API写的。AI生成新代码时用组合式API和老代码放一起就很突兀。我的建议是分阶段接入第一阶段只加naming.md和checklist.md不改代码风格。让AI生成的代码至少命名规范、逻辑完整。第二阶段加patterns.md但允许AI参考老代码的模式。新组件用新模式老组件重构时再改。第三阶段加完整的SKILL.md统一代码风格。这时候老代码已经重构得差不多了。整个过程大概需要2-3个月不要急。我见过有人想一周搞定结果AI生成的代码和老代码冲突反而增加了维护成本。5.4 团队协作怎么同步SKILL.md不是一个人的事是整个团队的规范。如果只有你一个人用AI生成的代码和别人手写的代码还是不一致。同步的关键是把SKILL.md纳入代码仓库和代码一起Review。具体做法SKILL.md放在项目根目录和package.json同级每次修改SKILL.md都要提PR团队Review新成员入职第一件事就是读SKILL.md定期比如每季度组织一次规范Review更新SKILL.md这样做的好处是SKILL.md成了团队的活文档而不是某个人的私人笔记。AI读的是最新版本人读的也是最新版本大家对齐的是同一套规范。5.5 效果评估与持续优化怎么知道去味工作流有没有效果我一般看三个指标指标测量方式目标值命名规范率统计变量名中万能词的比例 5%逻辑完整率统计异步操作中有loadingerror的比例 90%Review问题数统计每百行代码的Review问题数 3个这三个指标每周统计一次画成趋势图。如果命名规范率下降说明naming.md需要更新如果逻辑完整率下降说明checklist.md需要加强如果Review问题数上升说明SKILL.md和实际代码脱节了。我自己的项目跑了两个月命名规范率从60%提升到了95%逻辑完整率从30%提升到了92%Review问题数从每百行8个降到了2个。效果还是很明显的。6. 进阶玩法让AI学会品味6.1 从规则到品味taste-skill的深层逻辑前面讲的都是规则但taste-skill这个名字里的taste品味才是关键。规则能解决80%的问题但剩下20%需要品味——也就是知道什么代码好什么代码不好。举个例子规则可以规定禁止使用any但规则没法规定这个函数应该拆成两个还是保持一个。这需要判断力需要品味。taste-skill的思路是把品味也变成可描述的规则。比如我在SKILL.md里加了这样一段## 代码品味准则 - 一个函数只做一件事如果函数名里出现and考虑拆分 - 一个组件不超过200行超过考虑拆分 - 一个文件不超过500行超过考虑拆分 - 嵌套不超过3层超过考虑提前return - 参数不超过3个超过考虑用对象 - 注释解释为什么不解释是什么 - 错误处理要具体不要笼统地catch所有错误 - 命名要具体不要用manager、helper、util这种模糊词这些准则不是硬性规则而是倾向性建议。AI在生成代码时会参考这些准则做出更有品味的选择。6.2 用示例教AI什么是好代码规则是抽象的示例是具体的。我在.ai/目录下加了一个examples/文件夹放了一些好代码和坏代码的对比# 好代码 vs 坏代码 ## 坏代码 javascript function process(data) { let result [] for (let i 0; i data.length; i) { if (data[i].status 1) { result.push(data[i]) } } return result }好代码function filterActiveUsers(users) { return users.filter(user user.status UserStatus.Active) }为什么好函数名体现业务语义使用数组方法而非for循环使用枚举而非魔法数字代码更短但信息量更大这种对比示例比规则更直观。AI看了之后会模仿好代码的风格避免坏代码的写法。 ### 6.3 持续迭代让SKILL.md活起来 SKILL.md不是写完就完了它需要持续迭代。我的做法是 - **每周**Review一次AI生成的代码把新发现的问题加到checklist.md - **每月**更新一次patterns.md把新沉淀的模式加进去 - **每季度**大版本更新SKILL.md调整规则优先级删除过时规则 迭代的时候有个原则**只加规则不删规则除非规则被证明是错的**。因为删规则会让AI忘记之前的约束导致风格回退。如果某条规则不再适用改成建议而不是直接删掉。 实操心得我在SKILL.md里加了一个版本历史段落记录每次修改的内容和原因。这样团队新成员能看到规范的演进过程理解每条规则背后的故事。 ### 6.4 跨项目复用打造个人技能库 如果你同时维护多个项目可以把SKILL.md拆成通用部分和项目部分~/.ai-skills/ ├── base.md # 通用规范命名、品味、检查清单 ├── vue.md # Vue项目专用规范 ├── react.md # React项目专用规范 └── node.md # Node项目专用规范project-root/ └── SKILL.md # 项目特有规范引用通用部分项目里的SKILL.md只需要写项目特有的内容通用部分用import引用。这样维护成本大大降低而且跨项目的一致性更好。 我自己的~/.ai-skills/目录已经积累了大概2000行的规范覆盖了Vue、React、Node、Python等多个技术栈。每次开新项目只需要写100行左右的项目特有规范剩下的直接复用。 ## 7. 一些踩过的坑和真实体会 ### 7.1 不要追求100%的规则覆盖 我一开始想把所有规则都写进SKILL.md结果写了800多行AI反而抓不住重点。后来发现**规则覆盖80%的场景就够了剩下20%靠AI的判断力**。规则太多会限制AI的灵活性导致生成的代码死板。 现在的做法是核心规则命名、结构、样式必须写边缘规则注释风格、文件组织写成建议。AI在核心规则上严格遵守在边缘规则上灵活处理。 ### 7.2 定期清理过时规则 项目在演进规范也在演进。半年前定的规则现在可能已经不适用了。比如我们之前用Vuex后来换成了PiniaSKILL.md里关于Vuex的规则就过时了。如果不清理AI会按照过时的规则生成代码反而制造问题。 我现在的做法是每季度做一次规范审计把过时的规则删掉把新的规则加上。审计的时候会问三个问题这条规则还有用吗这条规则和实际代码一致吗这条规则AI能理解吗三个问题有一个答否就考虑修改或删除。 ### 7.3 AI不是万能的人还是要兜底 最后说句实话taste-skill和Agent Skills能大幅提升AI生成代码的质量但不能完全替代人的判断。AI生成的代码还是需要Review还是需要测试还是需要根据实际业务调整。 我的体会是**AI负责写得规范人负责写得对**。规范的部分交给SKILL.md业务逻辑的部分还是得人来把关。两者结合才能既有效率又有质量。 这套工作流我用了两个月最大的感受是AI生成的代码终于像人写的了。不是因为它变得更聪明而是因为它终于知道了这个项目的代码应该长什么样。SKILL.md就像给AI戴上了一副项目眼镜让它看到的不是抽象的前端代码而是具体的我们项目的代码。 如果你也在为「AI味」头疼建议从写一个简单的SKILL.md开始。不用追求完美先写20行核心规则用起来再慢慢迭代。这个过程本身就是对项目规范的一次梳理哪怕AI不用对人也是有好处的。
