主题
界面
主题
项目做完了,最后一步是把它变成你的东西:
① 复盘:哪些做得好、哪些踩了坑、下次怎么办 ✓
② 提炼:这个项目最有说服力的技术点是什么 ✓✓
③ 表达:面试官 30 秒内能听懂你做了什么、难在哪 ✓✓✓★ 一条残酷的事实:做了但讲不出来 = 没做 ✗✗
先看两种简历写法的对比 ✗✓:
✗ 弱:「独立开发了一个图书管理系统,使用 Spring Boot + MySQL,
实现了借书还书等功能,学习了分层架构。」
✓ 强:「设计并实现图书借阅系统(Spring Boot + JdbcTemplate + H2/PostgreSQL),
30 个测试全部通过;针对并发超借问题,用「条件 UPDATE 原子扣减」替代
check-then-act,10 线程抢 1 本库存恰好 1 个成功(并发测试验证);
借还操作全部幂等(条件更新),重复归还库存只加一次。」★ 差别不在项目,在表达 ✓✓:弱版本任何人都能写,强版本只有做过的人才写得出来 ✓
★ 三个问题,每个都要有具体证据 ✓✓:
| 问题 | 本项目的回答(真实) |
|---|---|
| 做得好的 ★★★ | 并发安全用测试证明(10 抢 1 恰好 1 个成功);规则全部配置化;约束兜底(CHECK 拦住负库存) |
| 踩了坑的 ★★★ | Mockito 在 JDK 25 跑不了;KeyHolder 多列;测试数据被准备工作清掉(不报错只结果错);@RequestParam 缺 name 全部 500 |
| 下次怎么办 ★★★ | 先验证测试工具在当前环境能跑;并发测试绝不用 Mock;测试数据准备要"先建被依赖的" |
★★ 复盘的大忌 ✗✗:
· "学到了很多" ✗(空话) · "下次注意" ✗(没有具体动作)
每一条都要有:现象 → 根因 → 具体改变 ✓✓
★ 一条来自本项目的完整复盘示例 ✓✓✓:
坑:并发测试数据被自己的准备工作清掉
现象:10 个线程全部失败,成功数 0 —— 但测试不报错,只是结果不对 ⚠️
根因:prepareBook() 内部调用了 cleanup(),把刚插好的读者删了
改变:① 准备顺序固定为"先建被依赖的"(先书后读者);
② 并发测试的断言写"恰好 N 个成功",让数据异常必然暴露 ✓✓★ 按说服力排序 ✓:
| # | 亮点 | 一句话 | 证据 |
|---|---|---|---|
| ① | 并发安全用测试证明 ★★★ | 「把 check-then-act 换成条件 UPDATE 原子扣减,并发测试 10 抢 1 恰好 1 个成功」 | BorrowConcurrencyTest ✓ |
| ② | 幂等设计 ★★★ | 「借还操作全部用条件更新实现幂等,重复归还库存只加一次(并发测试验证)」 | 同上 ✓ |
| ③ | 规则配置化 ★★ | 「业务规则全部来自配置,改上限/借期不用发版」 | LibraryRules + application.yml ✓ |
★★ 为什么这三条最值钱 ✓✓:
- 它们都是"踩过坑才会想到"的设计 —— 证明你理解问题而不只是套模板 ✓
- 它们都有可验证的证据 —— 不是"我做了",而是"测试证明" ✓✓
- 它们能自然引出追问 —— 你可以引导面试官问到你最熟的地方 ✓✓✓
★ 反例(不要这样提炼) ✗✗:
· "使用了 Spring Boot 框架" ✗(这是技术栈,不是亮点) · "实现了 CRUD 功能" ✗(这是任务清单) · "代码结构清晰" ✗(这是自我评价,没有证据)
★ STAR = Situation(背景)Task(任务)Action(行动)Result(结果) ✓
★ 本项目的完整 STAR ✓✓:
| 要素 | 内容 |
|---|---|
| S 背景 | 图书借阅场景,核心风险是并发超借(多读者同时抢最后一本) |
| T 任务 | 设计并实现借阅系统,要求:不超借、不重复归还、规则可调整 |
| A 行动 | ① 库存冗余字段 + 条件 UPDATE 原子扣减;② 归还用条件更新实现幂等;③ 业务规则抽成配置;④ 数据库 CHECK 约束兜底 |
| R 结果 | 30 个测试全绿;并发测试证明 10 抢 1 恰好 1 个成功;重复归还库存只加一次 |
★★ 写简历的模板 ✓✓:
【项目】图书借阅管理系统(个人项目,Spring Boot 3.4 / JdbcTemplate / H2+PostgreSQL)
· 针对并发超借风险,将「查询-判断-更新」改为带条件的原子 UPDATE
(WHERE available_copies > 0),并以并发测试验证:10 线程抢 1 本,
恰好 1 个成功、库存恰好归零;
· 借还操作全部按「条件更新」实现幂等,重复归还库存只增加一次;
· 业务规则(借阅上限/借期/续借次数)全部配置化,调整无需发版;
· 以数据库 CHECK 约束兜底库存合法性,应用层校验失败也不会产生脏数据;
30 个分层测试(领域/接口/集成/并发)全部通过。★★★ 一条黄金法则 ✓✓✓:
每一条都要能回答"你怎么证明?" ✓
写了"并发安全"→ 追问一定是"怎么验证的?" → 你有并发测试 ✓
写了"幂等"→ 追问"重复请求会怎样?" → 你有条件更新 + 测试 ✓
★ 本项目的五个高频追问与答法 ✓✓:
| 追问 | 答法要点 |
|---|---|
| "为什么不用乐观锁/分布式锁?" | 单库单行场景,条件 UPDATE 就是最简单的原子方案;行锁由数据库保证;引入锁反而增加复杂度 ✓ |
| "库存和借阅记录怎么保证一致?" | 同一事务;且顺序讲究:借书先原子扣库存(失败直接拒)、还书先标记记录(成功才加库存)✓ |
| "为什么不用 MyBatis/JPA?" | 本项目要把 SQL 看清;JdbcTemplate 让每条 SQL 可见、可测;选型看目标,不看流行度 ✓ |
| "测试怎么分层的?" | 四层:领域(毫秒级)/ 接口(协议)/ 集成(真实库)/ 并发;并发的坑只有并发测试能抓 ✓ |
| "如果并发量再大十倍怎么办?" | 单库行锁会成瓶颈 → 预扣库存到 Redis(《缓存穿透、击穿与雪崩》)或分段库存;但要先说清当前方案在什么量级内是够的 ✓✓ |
★★ 最后一问的答法结构 ✓✓✓:
先说清"当前方案够用到什么量级",再说"量级大了怎么演进" ✓
而不是一上来就"上 Redis、上分库分表" ✗(那是过度设计的信号)✓
★ 把复盘写成文件,而不是聊完就散 ✓:
docs/05-projects/project-a/RETROSPECTIVE.md
· 做得好的 3 条(各带证据)
· 踩坑的 5 条(各带 现象/根因/改变)
· 下次的 3 个具体动作
· 简历版描述(STAR)
· 追问清单与答法★ 本项目的坑已经全部沉淀在两处 ✓:
| 沉淀处 | 内容 |
|---|---|
.codebuddy/rules/build-and-verify.md | 工程类(引号、构建、脚本编码、死链)✓ |
| 本节 + 各讲义的"踩坑"段落 | 项目类(Mockito/JDK25、KeyHolder、测试数据顺序)✓ |
★★ 一句话 ✓✓:坑要么变成约束(下次不再犯),要么变成讲义(变成可教的知识) ✓✓ 否则同一个坑一定会再踩 ✗✗
· 以条件 UPDATE(WHERE available_copies > 0)实现库存原子扣减,
并发测试证明 10 线程抢 1 本恰好 1 个成功面试官:你在这个项目里遇到最难的问题是什么?
答:测试环境问题。我按文档用 Mockito 写 Service 单测,结果在 JDK 25 上
全部报"cannot mock this class"。排查后发现是 Mockito 版本对 JDK 25
的支持问题。我改用真实数据库 + @Transactional 测业务规则 ——
代价是测试慢一点,但换来的是断言真实数据、不会假通过。
这让我意识到:测试方案要先验证"在当前环境能不能跑",再谈分层。✗ "当时环境有问题,后来就好了。"
→ 面试官听到的是:你没搞清楚根因 ✗✗复盘依据(全部真实、可复现) ✓:
| 材料 | 位置 |
|---|---|
| 30 个测试 | labs/project-a/src/test/ |
| 并发验证输出 | BorrowConcurrencyTest 的 System.out |
| 踩坑记录 | 各讲义"踩坑与修复"段 + 约束文件 |
| 部署脚本 | scripts/run-project-a.ps1 |
★ 自检清单 ✓:
| # | 错误 | 说明 |
|---|---|---|
| 1 | 简历写技术栈清单当亮点 ✗✗ | 那是"用过",不是"做过" ✓✓ |
| 2 | 亮点没有证据 ✗✗ | 一追问就露馅 ✓✓ |
| 3 | 复盘只有感想没有动作 ✗✗ | 下次还会踩同一个坑 ✓✓ |
| 4 | 夸大("支撑百万并发")✗✗ | 一追问细节就穿帮 ✓✓ |
| 5 | 把坑归结为"环境问题" ✗✗ | 说明没找到根因 ✓ |
| 6 | 一上来就讲"我用了多少中间件" ✗ | 先讲问题,再讲方案 ✓✓ |
目标:产出一份能直接用的复盘 + 简历描述。
步骤:
验收方式:30 秒内讲清项目 + 每个亮点都有证据。
扩展任务:给项目 B(高并发)提前做设计预演:
"实现了消息队列功能,解决了系统耦合问题。"项目 A 至此完成(7/7)✓。接下来是 《项目 B:高并发场景与架构设计》: 把同样的方法用在一个"并发是主要矛盾"的场景上。