
节日刚来,公司把隔壁业务线的一个核心骨干提拔成了前端组长。
新上任,他花了整整一个晚上,在内网的 Confluence 上写了一份👉 《前端团队研发协作与编码规范》。
为了体现专业度,他还特意把文档链接发到了全员群里,并且配置了非常严格的 ESLint 和 Husky 提交拦截。
我随手点开那份规范看了一眼,里面有一段关于网络请求和 TypeScript 类型的规定,写得非常理想主义😢。
我直接把原文档的部分片段,给你们摘录出来👇:
4.1 严禁使用 any 业务代码、公共组件、工具函数、接口定义、状态管理中,均严禁出现
any。
4.2 严禁使用 unknown 规避 anyunknown仅允许在底层工具函数中使用,业务代码中不得使用unknown作为后端数据兜底类型。
4.3 接口出入参必须完整定义 所有后端接口必须定义请求参数和响应参数。
当时开会在投影仪上过这份文档时,大家纷纷鼓掌说好,觉得咱们的代码终于能摆脱草台班子的气质,向大厂开源规范看齐了😁。
结果晚上临近发版,我帮他们组 Review 一段紧急合入的业务代码时,差点笑出声。
理想中的规范是一尘不染的,但现实业务里的代码全是泥巴。我给你们看看,为了躲避这段严苛的规范,一线开发兄弟在提测倒计时的重压下,写出的代码:
typescript
// 后端老哥下午临时改了表单的返回结构,把原本的数组改成了一个逗号分隔的字符串
// 前端兄弟急着提测,但又被 Git Hook 死死卡住不让提交
// 于是他为了绕过严禁使用 any的规范,写出了这种极其糊弄的声明:
type MagicType = unknown; // 高级 any,完美骗过简单的 ESLint 扫描
interface UserFormRes {
userId: number;
// 先 unknown 占位
dynamicExtData: MagicType;
}
const UserProfile = () => {
const [data, setData] = useState<UserFormRes | null>(null);
const renderPrivilege = () => {
// 规范要求:必须用可选链,必须做类型兜底
// 于是业务层出现了一坨安全转换代码:
const extStr = (data?.dynamicExtData as string) || '';
const list = extStr.split(',').filter(Boolean);
// 强行把 boolean 转换回后端有时候乱传的 0 和 1
const isVip = (data?.dynamicExtData as any)?.is_vip == '1' || false;
return <div>{/* 渲染逻辑 */}</div>;
}
}
再去翻翻那一天的 Git 提交记录。 原本规范里白纸黑字写着,Commit Message 必须是 feat(模块名): [JIRA号] 更新详情。 但大家被拦截规则搞烦了,又不想去查具体的命令格式,于是那一晚的记录里,满屏都是用快捷指令强行绕过的产物:
git commit -m "fix" --no-verify
看着新负责人在群里无奈地圈人,问大家为什么不按规范来,我其实非常理解他的挫败感的🤔。
现实问题
在这个行当混了这么多年,带过好几个大大小小的团队,我见过太多这种死在摇篮上的技术规范。很多人总觉得,只要规矩定得细,配上拦截工具,大家就会乖乖就范。
但这忽略了一个最致命的现实:前端是直接扛业务压力的最后一环。
当产品经理站在你工位旁边,问你为什么这个按钮点下去没反应?测试在群里疯狂 @你,倒计时还有半小时就要封板上线。这个时候,你脑子里只有一件事:把逻辑跑通,让报错消失。
这时候谁有空去翻那十几页的文档,查一查这个嵌套了四层、连后端自己都说不清楚的 JSON 结构,到底该怎么老老实实写成 Interface?
当你的规范阻碍了同事准时下班,一线的开发者有一万种方法绕过你的各种 Lint 工具。
那么,既然这种事无巨细的规范行不通,团队代码是不是就活该变成垃圾堆?
其实也并不是。真正有效的前端管理,从来不是写小作文,而是抓大放小。
我现在带项目,从来不限制组员在具体的单个业务文件里怎么折腾。我不关心他是不是多写了几个毫无意义的 div 嵌套,也不关心他是不是为了图省事写了个 as any。那些东西虽然不优雅,但最多就是他自己几个月后接手时看着头疼,影响面可控。
但我会死守工程的底线边界。给大家看一个我平时做 Code Review 绝对会当场打回重写的真实案例:
javascript
// 某个新人写在促销活动页面的代码
// 这种代码我一眼都不会让它合进主分支
useEffect(() => {
// 错误 1:在业务组件里直接裸写 fetch,绕过全局请求实例
fetch('https://api.nurverse.com/v1/get-discount')
.then(res => res.json())
.then(data => {
setDiscount(data.price);
})
.catch(err => {
// 错误 2:私自吞掉报错,不接入统一监控
console.log(err);
});
},[]);
我不强制他定义完美的入参出参,但我绝对不允许他裸调接口和私自吞掉错误🫡。
因为一旦这个接口报了跨域,或者后端抛了 500,全局的 Axios 拦截器抓不到它,公司的统一监控大盘也收不到告警信息。到了第二天客诉找上门,全组人都要陪着他排查到底是哪个节点挂了,这就是在给整个团队挖坑。
我会让他老老实实改成这样👇:
typescript
// 所有的网络层交互,必须走基础封装
import { http } from '@/utils/request'; // 统一的请求封装
import { reportMonitor } from '@/utils/monitor'; // 统一的监控上报
useEffect(() => {
// 必须通过全局 http 实例调用,自动带上 Token、重试机制和全局拦截
http.get('/v1/get-discount')
.then(data => setDiscount(data.price))
.catch(err => {
// 非公共错误,业务侧拦截后必须手动上报大盘
reportMonitor('Discount_Fetch_Fail', err);
});
}, []); // 依赖数组根据实际情况补充
发现区别了吗?
别用规范去管人,用基建去管代码
管理技术团队,和敲键盘写代码是两套完全不同的逻辑。代码只要逻辑对,它就永远按预期执行,但人就不是🤷♂️。
任何一项技术规范的落地,本质上都是一次团队内部的习惯磨合。别再拿着那份十几页的文档试图去改造全组人了。砍掉那些脱离业务的伪需求,删掉那些让人抓狂的强制检查,去关注那些真正拖慢团队协作的链路瓶颈🫵。
当你的团队成员发现,你定的每一条规则,都是在帮他们少踩坑、少背锅、能准时下班的时候。根本不需要任何强制文档,你提倡的最佳实践,自然会成为大家的工作习惯。
这才是一个前端负责人,真正该有的技术管理品味。
你们说是不是呢?