不是说因为我们写得多牛,而是我们从第一天就立了 5 个基本工程约束,这些规则不是高深技术,而是工程底线。坚持这几个点,不一定能写出完美代码,但至少能避免那种“越写越乱、越维护越痛苦”的局面。
1. 所有逻辑必须可测试 —— 哪怕只是基本的 mock
“写业务不用写单测吧?”
我们早期的确也没有测试,但我们有个共识: “可测性”是模块合格的基础能力。
例如一个 API 请求函数:
// 直接在组件里调用
const fetchUser = async () => {
const res = await axios.get('/api/user')
setState(res.data)
}
我们会主动抽出:
// utils/api/user.ts
export async function fetchUser() {
const res = await request.get('/api/user')
return res.data
}
组件中调用:
const load = async () => {
const user = await fetchUser()
setState(user)
}
哪怕这个模块你暂时没写测试,也可以未来 mock。不是写不写测试的问题,是结构上必须可测。
2. 所有配置和工具链都必须文档化 & 脚手架化
配置复杂是技术债的第一大来源。
我们内部所有 Vite、ESLint、TSConfig、Stylelint、commitlint、husky 的配置,全部在一个 template/config 仓库里,并通过 create-project CLI 自动生成。
新建项目流程:
npx @our-team/create-project my-app
就能生成带全套约束的工程结构。
每次有新成员,只需要一句话:
“请从 CLI 拉起工程模板,手动搭项目我们不维护。”
我们甚至把 .editorconfig、.prettierignore、.vscode/settings.json 也包进去。
3. 拒绝“一次性”的逻辑代码
我们总结过一个现象:
项目中 90% 的“技术债”,其实不是写得差,而是“写了个一次性的东西,结果被复用起来了”。
例如写一个上传组件,临时加了个 hook:
useEffect(() => {
document.body.style.overflow = 'hidden'
return () => {
document.body.style.overflow = ''
}
}, [])
这个代码在某个场景 OK,后来别人复制过去也用了它,但页面结构不同,结果出现滚动异常。
我们现在的做法是:一切不是“组件通用”的逻辑,必须写注释说明场景适用条件。
甚至工具函数开头会写:
/** * 仅适用于弹窗场景中,用于锁死背景滚动 * 不适用于嵌套 iframe 或 shadow DOM 中 */
防止“拿来就用”的行为。
4. 不允许多人维护的页面使用匿名函数 / 闭包乱穿
我们有条硬性规定:
所有 team-shared 页面(如 layout、dashboard、全局 hook)不允许存在闭包调用状态更新函数,必须封装成独立方法。
这是我们以前踩过的一个坑:
const [count, setCount] = useState(0)
useEffect(() => {
const timer = setInterval(() => {
setCount(count + 1) // 这个 count 其实永远是 0
}, 1000)
}, [])
重构后:
useEffect(() => {
const timer = setInterval(increment, 1000)
return () => clearInterval(timer)
}, [])
function increment() {
setCount(prev => prev + 1)
}
不仅闭包安全,而且容易测试、易于代码搜索。
这也是我们不允许“匿名箭头函数当核心业务函数”的原因之一:可维护性远比可写性重要。
5. 所有可复用组件都必须写 usage demo
我们早期封装了一堆组件,但文档没人写,最后大家都懒得用,干脆自己手撸。形成了大量重复组件。
后来的约束是:
- 每个 components/ 下的组件目录,都必须包含 __demo__/index.tsx
- 内部项目自建 Storybook,开发时自动引入展示
- 提交 CI 时,若组件没有 __demo__,CI 报错
这样强制你思考组件边界、props 设计、可控状态,并且顺带解决了一部分测试问题。
例如:
/components
/UserCard
index.tsx
style.scss
__demo__
index.tsx ← 示例文件,必须有
最后的话
我们不是靠“约定”或“大家自觉”来避免技术债,而是靠结构性约束 + 工具自动化来抵御它。
只要你让“混乱”这件事发生一次,下次它就会被复制。
我们总结的经验是: “能自动化的就不要靠人记住,能写进 CLI 的就别写进 README。”
这样,你就不会等到 3 个月后才说:
“唉,这一坨代码也太难维护了吧。”
你们也有这样的感受吗?