从测试转开发三年,我踩过的坑和悟出的道
大家好,我是北漂打工人一枚,每天早上7点起床,8点准时坐在工位上敲代码——没错,就是那种被同事调侃“卷王附体”的早起型选手。坐标北京,通州到国贸一小时通勤,地铁上不是在刷LeetCode就是在看GitHub trending。三年前,我还是个天天写自动化脚本、跟开发battle bug归属的测试工程师;如今,我已经坐在开发岗上,一边改需求一边祈祷别在周五下午收到线上告警。
今天想聊聊一个听起来有点“虚”但实则贯穿我整个转型之路的话题:技术探索与实践。别急着划走!这不是什么高屋建瓴的理论课,而是我这三年里在真实项目里摸爬滚打、被产品追着改需求、被运维甩锅、被线上事故教育后,总结出来的一套“活命指南”。
被逼出来的技术探索
去年双11前夕,我们团队接到一个“不可能任务”:在两周内重构一个核心数据上报服务,要求吞吐量提升3倍,同时支持动态采样策略。当时我刚接手这个模块不到一个月,前任开发已经提桶跑路,留下的代码注释是:“此处逻辑很妙,勿动”。结果一上线就崩了,错误日志刷屏:“ConcurrentModificationException”。
那一刻我真的想砸电脑。
但骂完之后,还是得干活。因为我知道,作为一个从测试转过来的开发,我的“容错率”比科班出身的同事更低——你不能只说“这不行”,你得拿出“怎么行”的方案。于是,我开始深入研究高并发场景下的数据采集架构。
一开始我想直接上Kafka,毕竟“大厂标配”。但一问运维,人家直接回:“你们这QPS连500都不到,搞Kafka纯属炫技,还得多维护一套集群。” 打脸来得很快。
后来我翻了翻公司内部中间件文档,发现有个轻量级的消息队列组件叫 LightMQ,是我们基础架构组自研的,支持背压(backpressure)和本地缓存降级。虽然文档少得可怜,连示例代码都是2019年的,但胜在低侵入、低运维成本。
技术探索不是选最牛的,而是选最适合当前业务阶段和团队能力的。 这是我从测试转开发后最深刻的体会。测试时期看问题更偏向“边界”和“异常”,而开发需要平衡性能、可维护性、交付节奏和团队协作成本。
实战:用Disruptor优化上报链路
最终我们决定用 LMAX Disruptor 作为核心缓冲层,配合 LightMQ 做异步落盘。为什么选 Disruptor?因为它无锁、环形缓冲、超高吞吐——特别适合我们这种“突发流量+小数据包”的场景。
但光有想法不够,得落地。下面是我写的简化版核心逻辑(已脱敏):
// 上报事件对象
public class ReportEvent {
public long userId;
public String action;
public long timestamp;
// ... 其他字段
}
// 事件处理器:写入LightMQ
public class ReportEventHandler implements EventHandler<ReportEvent> {
private final LightMQProducer producer;
public ReportEventHandler(LightMQProducer producer) {
this.producer = producer;
}
@Override
public void onEvent(ReportEvent event, long sequence, boolean endOfBatch) {
try {
// 构造消息
Message msg = new Message(event.userId, event.action, event.timestamp);
producer.sendAsync(msg); // 异步发送,不阻塞环形缓冲区
} catch (Exception e) {
// 记录失败指标,便于告警
Metrics.counter("report.send.fail").inc();
log.error("Failed to send report event", e);
}
}
}
Disruptor 的 RingBuffer 初始化也很关键:
int bufferSize = 1024; // 必须是2的幂
WaitStrategy waitStrategy = new BlockingWaitStrategy(); // 平衡延迟与CPU占用
Disruptor<ReportEvent> disruptor = new Disruptor<>(
ReportEvent::new,
bufferSize,
Executors.defaultThreadFactory(),
ProducerType.SINGLE, // 我们是单生产者(上报入口唯一)
waitStrategy
);
disruptor.handleEventsWith(new ReportEventHandler(lightMQProducer));
disruptor.start();
这里有个坑: 一开始我用了 BusySpinWaitStrategy,想着“极致性能”。结果上线后 CPU 直接飙到 90%+,运维半夜打电话问我是不是挖矿了。后来换成 BlockingWaitStrategy,CPU 回落到 15%,吞吐量只降了不到 5%——性能优化不是一味追求极限,而是找到性价比最高的平衡点。
可读性 > 聪明代码
作为一个曾经的测试,我对“黑盒代码”深恶痛绝。所以现在写代码,我特别注重可读性和可维护性。比如上面那个 ReportEvent,有人可能会用 Map 或 JSON 字符串传参,觉得“灵活”。但我坚持用 POJO,因为:
- IDE 能自动补全
- 类型安全,编译期就能发现问题
- 新人接手时不用猜字段含义
我还强制要求所有核心方法写 行为注释(Behavioral Comment),而不是“做什么”,而是“为什么这么做”。比如:
// 为什么用单生产者?
// 因为所有上报请求都经过统一网关,天然串行化,
// 多生产者反而增加 RingBuffer 管理开销。
ProducerType.SINGLE
团队里有个应届生曾吐槽:“哥,你这代码写得太啰嗦了。” 我回他:“等你半夜被PagerDuty叫起来修bug时,就会感谢现在的我。”
技术分享不是秀肌肉,而是共建认知
我们团队每周五下午有个“Tech Share”环节。很多人把它当成表演舞台,上来就讲“我用Rust重写了XX,性能提升10倍!” 但我觉得,真正有价值的技术分享,是暴露问题、展示权衡、传递上下文。
上个月我就分享了这次上报服务重构的全过程,PPT标题就叫《一次差点让我离职的重构》。内容包括:
- 为什么不用Kafka(成本 vs 收益)
- Disruptor 的 CPU 坑是怎么踩的
- 如何设计降级开关(当 LightMQ 不可用时,直接写本地日志+告警)
- 监控指标怎么埋(QPS、延迟、失败率、RingBuffer 填充率)
结果会后两个后端同学跑来问:“你们那个降级方案能不能复用到我们的订单服务?” —— 这才是技术分享的意义:不是证明你多牛,而是帮团队少踩坑。
实战经验总结:探索要有“锚点”
很多人觉得“技术探索”就是学新框架、追新趋势。但我的经验是:脱离业务场景的探索,都是耍流氓。
我给自己定了三条原则:
- 问题驱动:先有痛点,再找方案。不要为了用某项技术而制造问题。
- 渐进式演进:能改一行代码解决的,绝不重写整个模块。小步快跑,快速验证。
- 可回滚:任何新技术引入,必须配套降级方案。线上系统不是试验田。
下面是我做技术选型时常用的评估维度表:
| 维度 | 权重 | 说明 |
|---|---|---|
| 业务匹配度 | ★★★★☆ | 是否解决当前核心问题? |
| 学习成本 | ★★★☆☆ | 团队是否能在1-2周内掌握? |
| 运维复杂度 | ★★★★☆ | 是否需要额外监控/告警/部署流程? |
| 社区活跃度 | ★★☆☆☆ | 内部组件可适当放宽 |
| 可测试性 | ★★★★★ | 作为前测试,这点我死守 |
你看,“社区活跃度”权重最低——因为我们用的是内部中间件,只要基础架构组有人维护就行。但“可测试性”给满星,因为我太清楚:测不了的代码,上线就是定时炸弹。
最后一点感悟
从测试转开发这三年,我最大的成长不是学会了多少框架,而是明白了:技术的价值不在炫技,而在解决问题、降低不确定性、让系统更可靠。
上周五晚上,我又加班到九点,修复了一个因时区处理不当导致的数据错乱问题。提交代码前,我默默加了一行单元测试,覆盖了夏令时切换的边界情况。保存时,突然想起三年前在测试岗上,我正是因为发现类似问题而被开发怼:“你懂业务逻辑吗?”
现在,我既懂逻辑,也能写逻辑。
技术探索的路上,没有白走的路。每一个深夜debug的崩溃,每一次Code Review的争论,每一行被删掉又重写的代码,都在把我从“工具人”变成“问题终结者”。
希望这篇带点血泪、带点自嘲、带点实战经验的技术分享,能给你一点启发。如果你也在转型路上,别怕慢,别怕问“蠢问题”——毕竟,我们都是从“Hello World”开始的。
对了,明天又是周一。7点起床,8点开工。愿你的代码少bug,需求少变更,线上稳如狗。

评论 0