技术探索与实践的一些思考:从产品思维到工程落地的综合权衡
入职新公司快两个月了,今天难得在周五下午五点半准时关掉 IDE,泡了杯手冲(没错,我就是那个每天 8 点就坐在工位上敲代码的早起型选手),突然想写点东西。不是为了卷,而是这两个月踩的坑太多,不吐不快。
以前做产品经理时,总觉得技术同学“矫情”——为什么一个接口不能今天提需求明天上线?现在自己转码了才明白,原来每个“不行”背后,都藏着一堆技术债、兼容性问题和凌晨三点的告警短信。身份转换后最大的感悟是:技术选型从来不是单纯的技术问题,而是一场关于时间、人力、业务目标和未来可维护性的综合博弈。
起因:一个“简单”的需求,差点让我连夜跑路
事情得从上周说起。我们团队负责一个内部运营平台,原本用的是 Vue 2 + Element UI 的老架构。产品同事(对,就是曾经的我自己会干的事)提了个“小需求”:要支持动态表单配置,字段类型包括富文本、级联选择、上传组件等,而且要求能拖拽排序、实时预览。
听起来很常规对吧?但当我打开代码库那一刻,心凉了半截——项目里还混着 jQuery 和原生 JS 写的模块,Webpack 配置文件比我的简历还长,CI/CD 流程跑一次要 12 分钟。更致命的是,没人敢动核心模块,因为去年双11前改了个按钮样式,结果导致订单导出功能挂了,运维半夜打电话骂街。
领导看我一脸苦相,拍拍肩膀说:“你不是做过 PM 吗?这次你来定技术方案,既要快又要稳。”
我:???这不就是传说中的“既要马儿跑,又要马儿不吃草”?
选型困境:框架之争,本质是风险与效率的拉扯
面对这种“历史包袱重 + 迭期紧”的场景,技术选型就成了生死抉择。我翻遍了掘金、知乎、GitHub Trending,甚至把床头那本《软件架构模式》翻到卷边(别笑,转码人真的会靠书籍续命),最后锁定了三个方向:
- 继续用 Vue 2 + 自研动态表单组件
- 升级到 Vue 3 + 使用 Formily / FormKit 等成熟方案
- 彻底重构,上 React + Ant Design Pro
乍一看第三个选项最“先进”,但结合现实,立马被我否了。为什么?产品迭代节奏不允许。我们下个月就要支撑一场大型营销活动,没时间搞大换血。而且团队里 React 经验几乎为零,贸然切换等于自爆。
于是重点对比前两个方案。我列了个表格,把关键维度都拉出来晒一晒:
| 维度 | Vue 2 自研 | Vue 3 + Formily |
|---|---|---|
| 学习成本 | 低(团队熟悉) | 中(需学新生态) |
| 开发速度 | 慢(重复造轮子) | 快(开箱即用) |
| 可维护性 | 差(无标准规范) | 好(社区方案) |
| 兼容风险 | 高(老旧依赖冲突) | 中(需处理 Vue 2/3 混用) |
| 长期收益 | 几乎无 | 可平滑迁移 |
看到“可维护性”这一栏,我果断倒向了 Vue 3 + Formily。原因很简单:我太懂“临时方案变永久债务”有多痛了。当年做 PM 时,为了赶上线让开发写了个“临时 hack”,结果三年都没人敢碰,成了系统里的幽灵代码。
但现实哪有这么理想?Vue 3 在我们项目里根本跑不起来——因为底层依赖的某个 UI 库只支持 Vue 2。这就尴尬了。
曲线救国:微前端 + 渐进式迁移
被逼到墙角的时候,人总能想出骚操作。我灵机一动:为什么不把新功能做成一个独立的微应用?
具体思路是:
- 主应用保持 Vue 2 不动
- 新的动态表单模块用 Vue 3 + Vite 单独构建
- 通过 Webpack Module Federation 实现模块共享
- 通信通过 CustomEvent + localStorage(别喷,过渡期够用就行)
说干就干。周一下午我就拉着前端组长开了个“技术可行性评审会”。他一开始满脸怀疑:“Module Federation?我们连 Webpack 5 都没升,你确定能跑通?”
我说:“跑不通就改配置呗,反正 CI 流水线慢成狗,多试几次也不差这点时间。”
结果周三晚上真的跑通了!虽然过程堪称灾难:
- 因为
@vue/compiler-sfc版本冲突,Vite 构建直接报错Cannot find module 'vue' - 微应用加载时主应用的 CSS 样式污染了新组件
- 本地开发热更新失效,每次改代码都要手动刷新
但好在,核心逻辑跑起来了。我把关键配置贴出来,给后来人避坑:
// vue3-form-app/vite.config.js
export default defineConfig({
build: {
// 关键:暴露模块供主应用消费
rollupOptions: {
external: ['vue'],
output: {
globals: {
vue: 'Vue'
}
}
}
},
plugins: [
vue(),
federation({
name: 'vue3FormApp',
filename: 'remoteEntry.js',
exposes: {
'./DynamicForm': './src/components/DynamicForm.vue'
},
shared: {
vue: {
singleton: true,
requiredVersion: '^3.2.0'
}
}
})
]
})
主应用那边也要加一段加载逻辑:
// main-app/src/utils/loadMicroApp.js
export async function loadDynamicForm() {
const container = document.getElementById('form-container');
if (!container) return;
// 动态加载 remoteEntry
await import('http://localhost:5173/assets/remoteEntry.js');
const factory = await __webpack_require__.e('DynamicForm');
const Module = await factory();
// 挂载到指定 DOM
createApp(Module).mount(container);
}
虽然丑了点,但它 work 了!周五 demo 时,产品同事眼睛都亮了:“哇,这个拖拽体验好流畅!” 我表面微笑,内心狂喜:终于不用背锅了!
可读性与可维护性:我的强迫症救了团队
作为前 PM,我现在写代码有个执念:任何接手的人都能在 10 分钟内看懂我在干嘛。
所以我在新模块里强制做了几件事:
- 组件命名语义化:
DynamicFormRenderer.vue而不是Form.vue - 关键逻辑加注释:不是“这里是个循环”,而是“此处处理级联字段的联动逻辑,参考 PRD 第 3.2 节”
- 配置抽离:把字段类型映射关系单独放到
fieldTypeMap.js - 错误边界兜底:每个异步操作都有 try-catch + 用户友好提示
举个例子,富文本上传图片的逻辑,我这么写的:
// src/utils/uploadHandler.js
/**
* 处理富文本编辑器图片上传
* @param {File} file - 用户选择的图片文件
* @param {Function} onProgress - 上传进度回调(用于显示 loading)
* @returns {Promise<string>} - 返回 CDN 图片 URL
* 注意:此接口需走内部鉴权网关,不可直连 OSS
*/
export async function uploadRichTextImage(file, onProgress) {
// 1. 校验文件类型 & 大小
if (!['image/jpeg', 'image/png'].includes(file.type)) {
throw new Error('仅支持 JPG/PNG 格式');
}
if (file.size > 5 * 1024 * 1024) {
throw new Error('图片大小不能超过 5MB');
}
// 2. 调用内部上传服务(带 token)
try {
const formData = new FormData();
formData.append('file', file);
const res = await axios.post('/api/internal/upload', formData, {
headers: { 'Authorization': getAuthToken() },
onUploadProgress: (progressEvent) => {
const percent = Math.round((progressEvent.loaded * 100) / progressEvent.total);
onProgress?.(percent);
}
});
return res.data.url; // CDN 地址
} catch (error) {
// 3. 错误分类处理
if (error.response?.status === 401) {
throw new Error('登录已过期,请刷新页面');
}
throw new Error(`上传失败:${error.message}`);
}
}
可能有人觉得啰嗦,但上周测试同学发现一个上传 403 的 bug,直接根据注释定位到是 token 过期,省了半小时排查时间。可读性不是写给机器看的,是写给人看的。
教训与反思:技术决策不能只看“酷不酷”
回过头看,这次技术探索给我几个血泪教训:
- 不要迷信新技术:Vue 3 很香,但在遗留系统里硬上就是找死。微前端看似复杂,却是平衡之选。
- 产品思维是优势,不是负担:正因为我知道这个需求背后是运营 KPI,才敢砍掉“完美方案”,选择“够用就好”。
- 文档即代码:我花了一整天写 README,包括如何本地联调、如何模拟错误场景。结果新来的实习生第一天就跑起来了。
- Deadline 是第一生产力:如果不是下周就要演示,我可能还在纠结要不要用 Svelte……
最讽刺的是,昨天运维大哥跑来问我:“你们那个新表单,能不能顺便把日志格式标准化一下?现在查问题全靠 grep。”
我:……(默默打开代码,加上了 structured logging)
写在最后:技术人的成长,是不断在妥协中寻找最优解
从 PM 转码两个月,最大的变化不是学会了写 Promise,而是理解了所有技术决策都是 trade-off。没有银弹,只有更适合当下场景的选择。
现在的我,依然会在凌晨三点被报警电话吵醒,依然会对着满屏 red error 抓狂,但至少——
我知道自己在为什么而妥协,又在坚持什么。
比如坚持代码可读性,坚持写单元测试(哪怕只覆盖核心路径),坚持在 PR 里写清楚背景和影响范围。这些事看起来“没那么 urgent”,但长期来看,它们才是系统不崩的真正护城河。
对了,如果你也在经历技术选型的煎熬,不妨翻翻手边的书,或者问问自己:
“三个月后,接手这段代码的人会骂我还是感谢我?”
答案,往往就在问题里。
(完)
P.S. 刚才提交 PR 的时候,CI 又跑了 11 分 47 秒……运维兄弟,求求你优化下流水线吧!🙏

评论 0