代码写在简历上,不如跑在线上

代码不眠人
2025-12-26 12:06
阅读 980

上周五晚上十一点半,我还在公司死磕一个 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);
  };
}

这个补丁虽小,却是无数线上用户“用脚投票”教会我的。技术分享的意义,就在于把这种血泪经验传递下去,避免后来者重复踩坑。

代码人生的三个信条

写代码这些年,我逐渐形成了自己的技术哲学:

  1. 可维护性 > 新颖性
    能用 for 循环解决的问题,绝不为了炫技上 reduce。毕竟半夜三点被 PagerDuty 叫醒修 bug 的是你自己。

  2. 文档即代码
    我坚持在关键函数里写清楚“为什么这么设计”,而不是“做了什么”。后者看代码就知道,前者才是团队知识沉淀的核心。

  3. 分享即投资
    每次写技术分享,我都会问自己:“如果我是听众,最想带走哪三个知识点?” 这逼着我把零散经验结构化,反而加深了自己的理解。

上周团建,产品经理举着啤酒问我:“你们程序员整天折腾这些有啥用?” 我指了指他手机里的 App:“你刷到的每一条推荐,背后都是我们修过的 bug、压测过的接口、优化过的代码——这就是代码人生的意义。”

成都的夏天湿热黏腻,但当我坐在电脑前,一行行亲手敲下的代码总能带来奇异的清凉感。或许在这个 AI 自动编程的时代,坚持手写代码本身就是一种浪漫主义抵抗——抵抗浮躁,抵抗黑盒,抵抗那些未经思考就粘贴的“解决方案”。

简历会过期,技术会淘汰,但解决问题的能力和分享知识的热情,永远是最硬的通货。共勉。

评论 0

最热最新
暂无评论
匿名用户Lv.1
0
影响力
0
文章
0
粉丝