代码写在简历上,不如跑在线上
上周五晚上十一点半,我还在公司死磕一个 Rust 的 unsafe 块。窗外成都的夜雨淅淅沥沥,楼下茶馆早关了门,只有我工位上的机械键盘还在咔嗒作响。突然微信弹出一条消息:“哥,你那篇关于 JS 内存泄漏的分享我们团队看完了,真救了急!”——那一刻,疲惫感瞬间被一种微妙的满足冲淡。
我是那种典型的“手写党”:写 JavaScript 不用 snippets,连 console.log 都坚持手敲;VSCode 装了一堆插件,但 Tab 键从来不是我的好朋友。最近被 Rust 的 ownership 模型迷得神魂颠倒,白天写业务代码,晚上啃《The Rust Programming Language》,日子过得像在打两份工。不过说真的,在这个 AI 编码助手满天飞的年代,我反而更珍惜一行行亲手敲出来的代码——因为它们才是我真正的简历。
简历?不如线上跑着的服务来得实在
去年秋招季,有个应届生拿着简历来找我内推。简历写得花里胡哨:“精通 React/Vue/Angular,熟悉 Webpack/Vite/Rollup,掌握 Node.js 全栈开发……”结果一问项目细节,支支吾吾说不清为什么选 Vue3 而不是 React,Webpack 和 Vite 的 tree-shaking 差异也答不上来。最后我委婉地说:“兄弟,技术栈列再多,不如把 GitHub 上那个 demo 修好——你那个 todo-list 还有内存泄漏呢。”
这让我想起自己刚入行那会儿。第一份简历上赫然写着“熟练掌握 JavaScript”,结果面试官让我手写一个 debounce 函数,我紧张得连 setTimeout 的返回值都忘了 clear。后来痛定思痛,开始把每一个小项目都当成作品集:哪怕只是个简单的天气插件,我也要加上性能监控、错误上报、单元测试。代码人生不是靠简历吹出来的,是靠线上每一行跑过的字节堆出来的。
现在带新人,我第一句话永远是:“别急着更新简历,先把你上周改的那个 bug 复盘清楚。”因为真正能打动人的,从来不是“使用过什么技术”,而是“用它解决了什么问题”。
从一次 JS 内存泄漏说起
上个月我们团队搞大促预演,前端页面在 Chrome 里跑着跑着就卡成 PPT。DevTools 一看,heap snapshot 显示 detached DOM 节点堆积如山——典型的内存泄漏。
罪魁祸首是一段看似无害的事件监听代码:
// 问题代码:组件销毁后未移除监听器
class ProductList {
constructor(container) {
this.container = container;
this.init();
}
init() {
// 直接绑定 this,导致闭包引用整个实例
window.addEventListener('resize', () => {
this.recalculateLayout();
});
}
recalculateLayout() {
// 重排逻辑
}
}
表面看没问题,但每次组件销毁(比如路由切换),这个匿名箭头函数依然持有对 this 的引用,而 this 又引用了 DOM 容器,于是整个组件树都被 GC 忽略了。
修复方案很简单:显式管理生命周期。
// 修复版:手动清理监听器
class ProductList {
constructor(container) {
this.container = container;
this.resizeHandler = this.handleResize.bind(this); // 提前绑定
this.init();
}
init() {
window.addEventListener('resize', this.resizeHandler);
}
destroy() {
window.removeEventListener('resize', this.resizeHandler);
// 清理其他资源...
}
handleResize() {
this.recalculateLayout();
}
}
关键点在于:JavaScript 的垃圾回收不是魔法,你得告诉它什么时候可以放手。 尤其是在 SPA 应用中,组件反复创建销毁,稍不注意就成了内存“钉子户”。
我把这个案例整理成内部技术分享,标题就叫《那些年我们踩过的 JS 内存坑》。没想到隔壁组的测试同学听完后说:“难怪我们自动化脚本跑久了浏览器直接崩,原来不是我们写的用例有问题!”——技术分享的价值,往往超出你的预期。
技术探索:在保守与激进之间找平衡
作为“手写代码保守派”,我对新工具总是保持三分警惕。去年团队引入 Vite 时,我就投了反对票:“Webpack 配得好好的,为啥要换?”结果被现实狠狠打脸——本地启动速度从 45s 降到 800ms,HMR 快到我以为电脑坏了。
这让我意识到:保守不是拒绝变化,而是拒绝盲目跟风。 我现在的策略是:核心业务稳如老狗,边缘实验大胆尝试。比如最近研究 Rust,就是拿它重写了几个内部 CLI 工具。以前用 Node.js 写的脚本处理大文件时经常 OOM,换成 Rust 后内存占用稳定在 10MB 以内,而且编译一次到处运行,再也不用担心同事的 Node 版本不对。
下面是我对比两种技术栈处理 1GB 日志文件的性能数据:
| 指标 | Node.js (v18) | Rust (release) |
|---|---|---|
| 内存峰值 | 1.2 GB | 18 MB |
| 处理时间 | 23.4s | 6.1s |
| 二进制大小 | N/A (需 Node 环境) | 4.2 MB (单文件) |
当然,Rust 的学习曲线陡峭得像青城山后山。光是理解 lifetime 就花了我两个周末,期间无数次想摔键盘。但每当看到 cargo build --release 输出那个小小的可执行文件时,又觉得一切都值得。
实践出真知:那些只有线上才知道的事
再好的理论,不经过线上流量的毒打都是纸老虎。记得有一次,我在本地用 Jest 测得完美无缺的防抖函数,上线后却导致搜索建议疯狂闪烁。排查半天才发现:测试环境网络快如闪电,而真实用户用的是 3G 网络,API 响应时间波动极大。
最终解决方案是在 debounce 基础上加了“最小等待时间”:
function smartDebounce(fn, delay, minWait = 100) {
let timeoutId;
let lastCallTime = 0;
return function (...args) {
const now = Date.now();
const timeSinceLastCall = now - lastCallTime;
// 如果距离上次调用太近,强制延长等待
const effectiveDelay =
timeSinceLastCall < minWait ? minWait : delay;
clearTimeout(timeoutId);
timeoutId = setTimeout(() => {
lastCallTime = Date.now();
fn.apply(this, args);
}, effectiveDelay);
};
}
这个补丁虽小,却是无数线上用户“用脚投票”教会我的。技术分享的意义,就在于把这种血泪经验传递下去,避免后来者重复踩坑。
代码人生的三个信条
写代码这些年,我逐渐形成了自己的技术哲学:
可维护性 > 新颖性
能用 for 循环解决的问题,绝不为了炫技上 reduce。毕竟半夜三点被 PagerDuty 叫醒修 bug 的是你自己。文档即代码
我坚持在关键函数里写清楚“为什么这么设计”,而不是“做了什么”。后者看代码就知道,前者才是团队知识沉淀的核心。分享即投资
每次写技术分享,我都会问自己:“如果我是听众,最想带走哪三个知识点?” 这逼着我把零散经验结构化,反而加深了自己的理解。
上周团建,产品经理举着啤酒问我:“你们程序员整天折腾这些有啥用?” 我指了指他手机里的 App:“你刷到的每一条推荐,背后都是我们修过的 bug、压测过的接口、优化过的代码——这就是代码人生的意义。”
成都的夏天湿热黏腻,但当我坐在电脑前,一行行亲手敲下的代码总能带来奇异的清凉感。或许在这个 AI 自动编程的时代,坚持手写代码本身就是一种浪漫主义抵抗——抵抗浮躁,抵抗黑盒,抵抗那些未经思考就粘贴的“解决方案”。
简历会过期,技术会淘汰,但解决问题的能力和分享知识的热情,永远是最硬的通货。共勉。

评论 0