10 年 Java 后端:从写代码到做系统,我的工程化思维进化之路
> 这一章写给那些工作了 3-5 年,代码写得不错,但总觉得"做项目还是心里没底"的 Java 后端开发者。讲一讲我从"能实现功能"到"能交付系统"的思维转变。
一、问题背景:为什么很多 Java 程序员写了 5 年代码,还是不会"做系统"
面试的时候,很多人能把 HashMap 源码讲得头头是道,能把 Spring 的循环依赖说清楚。
但真到了项目里:
-
**需求一变,代码就乱**:产品改个需求,改了三天还没改完,越改 bug 越多;
-
**写的时候觉得没问题,上线就炸**:本地跑一切正常,一到生产,并发一来就挂,数据量一大就慢;
-
**只看到代码,看不到系统**:关注的是这个类怎么写,那个方法怎么优化,而不是整个系统的稳定性、可维护性、交付效率;
-
**出了问题才救火,不会提前预防**:大部分时间在修 bug,很少主动去想"这个地方以后会不会出问题"。
这不是技术能力的问题,是**思维方式的问题**。
很多 Java 程序员的成长路径是:学会语言 → 学会框架 → 学会中间件,然后就停住了。但真正的后端开发,核心不是"会用什么技术",而是"怎么用技术把一个系统稳稳地交付出去,并且在未来三年还能维护得动"。
这一章,我把自己 10 年踩过的坑、总结的方法,整理出来。不管你是想进阶高级开发,还是想转架构,这些思路都能用。
二、核心判断:后端开发的核心,是"管理复杂度",不是"炫技"
我刚工作的前三年,痴迷于"技术深度":
-
这个框架的源码我看过了;
-
这个算法的时间复杂度我优化到 O(logN) 了;
-
这个新特性我用上了,代码更优雅了。
但真到了大项目里,这些东西根本救不了你。真正能救你的,是你能不能把一个复杂系统,拆成简单的、可预测的、出了问题能快速定位的模块。
后端开发的本质,是**在有限的时间和资源里,把业务需求转化为稳定、可维护、可扩展的系统**。
这里的几个关键词:
-
**稳定**:上线不炸,出问题能快速恢复;
-
**可维护**:三个月后你自己还能看懂,别人接手也不骂娘;
-
**可扩展**:业务变了,不用推倒重来;
-
**有限时间**:项目不是给你无限时间打磨的,要交付。
想明白这点,你看代码的视角就变了:你不再关心"这行代码写得够不够优雅",而是关心"这个设计,半年后会不会害死我"。
三、方法框架:我的工程化 5 条军规
这些不是什么书上的理论,是我踩了无数坑,熬了无数夜,总结出来的。每一条背后,都是线上事故的血泪。
1. 先做"可观测",再做"功能"
很多人写代码的顺序是:写业务逻辑 → 跑通测试 → 上线。
我的顺序是:写业务逻辑 → 加日志 → 加监控 → 加告警 → 跑通测试 → 上线。
区别在哪里?
前者上线了,出了问题你不知道,知道了也查不出来。后者上线了,出了问题告警先通知你,你打开日志和监控,5 分钟就能定位到问题。
我自己的标准是:**任何一个接口,没有日志和监控,我就不允许它上线**。
日志要打什么?
-
入参和出参(敏感信息脱敏);
-
关键节点的耗时;
-
异常的完整堆栈;
-
业务状态的变化(比如"订单从待支付变成已支付")。
监控要看什么?
-
QPS、响应时间、错误率;
-
数据库连接池、线程池的状态;
-
关键业务指标(比如每天下单成功的数量)。
没有这些,你就是在裸奔。
2. 任何操作,都要考虑"失败了怎么办"
新人写代码,只考虑成功的路径:
```java
public void createOrder(Request request) {
// 1. 校验参数
// 2. 扣库存
// 3. 生成订单
// 4. 发送消息
// 5. 返回成功
}
```
老程序员写代码,每一步都在想失败了怎么办:
-
校验参数失败,返回什么错误?
-
扣库存失败,怎么回滚?
-
生成订单成功了,发消息失败了怎么办?
-
发消息超时了,是重试还是回滚?
-
这个方法执行到一半,服务挂了,数据会不会不一致?
我的习惯是:**写任何代码之前,先列出来所有可能失败的情况,以及对应的处理策略**。
常见的失败处理策略:
-
**快速失败**:前置校验,不行就直接返回,别走到后面再炸;
-
**重试**:网络调用、消息发送这种临时性失败,加重试,但要有限制(最多 3 次,指数退避);
-
**幂等**:同一个请求调用多次,结果要一样;
-
**补偿**:真失败了,要有机制能补回来(定时任务、人工核对);
-
**降级**:非核心功能挂了,别影响主流程。
3. 写代码的时候,就要想着"怎么测试"
很多人写代码,写完了丢给测试,测试测出 bug 再改。这样的效率很低。
我写代码的时候,就会想:
-
这个方法的输入有哪些边界情况?
-
这个依赖我能不能 Mock 掉?
-
这个逻辑我能不能写个单元测试,不用启动整个项目就能测?
-
这个功能我怎么在本地快速验证,不用等测试环境部署。
我的标准是:**核心业务逻辑,没有单元测试覆盖,我就不提交代码**。
不是说要追求 100% 的测试覆盖率,而是你要对核心逻辑有信心。尤其是那些处理钱、处理状态的代码,一定要写测试。
4. 不要"过度设计",但要"预留扩展点"
架构圈有句话叫" premature optimization is the root of all evil"(过早优化是万恶之源),很多人理解错了,觉得"那就怎么简单怎么来"。
我的理解是:**不要为了不确定的未来,做复杂的设计;但要为了可能的变化,预留扩展点**。
比如:
-
你现在用 MySQL 存数据,不用一上来就分库分表,但要把数据访问层抽象好,以后真要分的时候不用改业务代码;
-
你现在用 Redis 做缓存,不用一上来就搞多级缓存,但要把缓存逻辑封装好,以后换实现方便;
-
你现在只有一个支付渠道,不用一上来就做支付网关,但要把支付接口抽象好,以后加第二个渠道的时候不用改主流程。
怎么判断要不要预留扩展点?我有个简单的标准:**这个需求半年内会不会变?如果大概率会变,就预留;如果不确定,先写死,以后再重构**。
5. 代码是写给人看的,不是写给机器看的
很多人写代码,追求"一行代码实现复杂功能",觉得这样很厉害。但实际上,这样的代码三个月后你自己都看不懂。
我对代码的要求是:
-
**命名要准确**:别用 `data`、`info`、`process` 这种模糊的名字,叫 `userOrderPaymentRecord`,虽然长,但一看就知道是什么;
-
**方法要短小**:一个方法超过 50 行,我就想拆;
-
**注释要讲"为什么",不是"是什么"**:别写 `// 扣库存`,要写 `// 这里用乐观锁扣库存,因为并发不高,不需要分布式锁`;
-
**业务逻辑要集中**:别把同一个业务的逻辑散落在 10 个地方。
代码的可读性,直接决定了维护成本。你今天少打了几个字,明天可能要花几个小时去理解。
四、落地步骤:从今天开始,你就能做的 5 件事
说再多道理,不如从一件小事开始做。我给你列了 5 个具体的动作,今天写代码的时候就能用上。
第一步:给你接下来要写的接口,加上完整的日志和监控
不用搞复杂的 AOP 或者什么高大上的东西,就在你的 Controller 或者 Service 里,手动把关键信息打出来:
```java
log.info("createOrder start, userId:{}, goodsId:{}, amount:{}", userId, goodsId, amount);
long start = System.currentTimeMillis();
try {
// 业务逻辑
log.info("createOrder success, orderId:{}, cost:{}ms", orderId, System.currentTimeMillis() - start);
} catch (Exception e) {
log.error("createOrder failed, userId:{}, goodsId:{}", userId, goodsId, e);
throw e;
}
```
就这么简单,出了问题你至少能看到是哪个用户、哪个商品、花了多长时间、报了什么错。
第二步:写代码之前,先列出来 3 个可能失败的场景
比如你要写一个"用户下单"的接口,先想:
-
扣库存失败了怎么办?
-
生成订单成功了,发消息失败了怎么办?
-
用户重复提交了怎么办?
不用一开始就把所有情况都处理完美,但你要想到这些情况,并且在代码里留下处理的地方,或者至少在注释里写清楚"这个地方暂时没处理,以后要注意"。
第三步:给你的核心业务逻辑,写 3 个单元测试
不用追求全量覆盖,就写 3 个最关键的:
-
正常流程的测试;
-
参数错误的测试;
-
依赖失败的测试。
能写单元测试的代码,本身就是解耦的、好维护的。
第四步:Code Review 的时候,先看"可读性",再看"正确性"
我做 Code Review 的顺序是:
-
命名能不能看懂?
-
逻辑是不是集中?
-
有没有注释说明"为什么这么做"?
-
边界情况有没有处理?
-
逻辑是不是正确?
很多人反过来,先看逻辑对不对。但实际上,逻辑错了容易改,代码写得烂了,以后改都没法改。
第五步:每做完一个项目,复盘 3 个问题
项目上线后,不管成不成功,都花一个小时复盘:
-
这个项目里,我踩了哪些本来可以避免的坑?
-
哪些设计做得好,以后可以复用?
-
如果再做一次,我会在哪里多花时间?
把这些记下来,你成长的速度会比只埋头写代码快得多。
五、常见坑:后端开发最容易犯的几个错误
这些坑我都踩过,有些还踩了不止一次。
1. 为了用新技术而用新技术
看到 Redis 集群就想上,看到微服务就想拆,看到新框架就想试。结果就是项目复杂度陡增,你花了很多时间研究新技术,业务进度还慢了。
我的原则是:**能用成熟技术解决的,就不用新技术**。新技术的坑你不知道,踩了就是大事故。
2. 追求"完美设计",导致进度延期
总觉得这个设计还可以更好,那个地方还可以再优化,结果项目延期了。
记住:**能按时交付的够用的设计,好过不能交付的完美设计**。设计是迭代出来的,不是一步到位的。
3. 只关心自己写的那部分代码,不关心整个系统
很多人只关心自己的模块,别人的模块出了问题跟自己没关系。
但实际上,线上出了问题,不管是哪个模块的锅,只要影响了业务,你都要能定位到。你对整个系统的理解越深,解决问题的能力就越强。
4. 不写文档,不做沉淀
觉得"代码就是文档"。但实际上,三个月后你自己都不记得当时为什么这么写了。
我的习惯是:每个复杂的业务逻辑,都在代码旁边写清楚"当时为什么这么设计,有哪些约束,哪些地方是可以优化的"。
5. 不做监控,出了问题才救火
觉得"上线了再说,有问题再改"。结果就是凌晨三点被电话叫醒,查半天查不出来。
记住:**监控是你能睡安稳觉的前提**。
六、总结:后端开发的护城河,是"靠谱",不是"会的多"
很多人问我,35+ 的 Java 后端,竞争力在哪里?
我的答案是:**靠谱**。
什么叫靠谱?
-
交给你的需求,你能评估清楚工作量,按时交付;
-
你做的系统,上线不炸,出了问题你能快速解决;
-
你写的代码,别人接手不骂娘;
-
遇到复杂问题,你能给出可行的方案。
这些东西,比你会 10 种框架、读过 100 篇源码,有价值得多。
年轻的时候,你可以拼学习能力,拼体力,拼加班。但年纪大了,你要拼的是"让人放心"。
客户愿意把项目交给你,老板愿意把重要的系统交给你,不是因为你技术最牛,而是因为你靠谱,你做的东西稳。
这就是 35+ 程序员的核心竞争力。
行动清单:如果你也想提升工程化能力,可以从这几步开始
-
今天写代码的时候,给你的接口加上完整的日志,包括入参、出参、耗时、异常;
-
找一个你最近写的方法,列出来 3 个可能失败的场景,加上对应的处理或者注释;
-
给你的核心业务逻辑,写 3 个单元测试;
-
下次 Code Review 的时候,先看可读性,再看正确性;
-
这个周末,花一个小时,复盘一下你最近做的项目,写下来你踩过的 3 个坑。
不用一下子变成架构师,就从这 5 件小事开始做,你会发现,做项目的感觉越来越稳。