综合练习
模拟 PR Review。
综合练习
这一课你将能做到
- 拿到一个真实 PR,独立完成从「快速扫读」到「深度审查」的完整流程
- 输出一份结构化的 review 报告,包含问题清单、优先级、修改建议
- 识别本站在 Spring Boot + MySQL + Redis + Next.js 技术栈下的典型问题模式
为什么要学
前面 11 课分别讲了 API、实现、文档、测试、风格、流程、清单、安全、AI、反馈、团队。这一课把所有知识点串起来,用一个完整的实战案例检验你是否真的会审。
人话:学了一堆理论,不如亲手审一个 PR。这一课就是你的「毕业考试」。
先建立图像
一个完整 PR 的审查动线
第一步:快速扫读(5 分钟)
├─ 看 PR 描述:做了什么、为什么、影响范围
├─ 看文件列表:改了哪些文件、多大
└─ 看 CI 状态:测试过了吗、Lint 过了吗
第二步:定位高风险区域(10 分钟)
├─ 新增/修改接口 → API 语义审查
├─ 涉及数据库 → SQL、事务、索引
├─ 涉及缓存 → 一致性、穿透/击穿/雪崩
├─ 涉及外部调用 → 超时、重试、降级
└─ 涉及权限 → 鉴权、越权
第三步:深度审查(20 分钟)
├─ 逐行读代码,对照 Checklist
├─ 思考「如果输入是 X 会怎样」
└─ 思考「如果并发/失败/超时了会怎样」
第四步:输出报告(5 分钟)
├─ P0 阻断项
├─ P1 强烈建议
└─ P2 建议改进
跟着做
练习:审查本站「课程发布」PR
假设你收到这样一个 PR:
标题:feat(course): 新增课程发布功能
描述:讲师可以发布课程,发布后 C 端可见,同时更新搜索索引
改动文件:
- CourseController.java(+50 行)
- CourseService.java(+120 行)
- CourseMapper.java(+20 行)
- CourseMapper.xml(+30 行)
- CourseServiceTest.java(+80 行)
- application.yml(+5 行)
第一步:快速扫读
✓ PR 大小:305 行,在合理范围内
✓ CI 状态:全部通过
✗ PR 描述:太简略,没说「发布失败怎么处理」「索引更新失败怎么办」
动作:先评论一句
[question] PR 描述里没提到「发布失败时状态是否回滚」
和「搜索索引更新失败的处理策略」,能否补充一下?
这会影响我对异常处理逻辑的审查重点。
第二步:定位高风险区域
高风险点:
1. CourseService.publish() → 事务边界、状态变更
2. searchClient.index() → 外部调用、失败处理
3. CourseMapper.xml 新 SQL → 索引、性能
4. 权限校验 → 只有课程作者能发布吗?
第三步:深度审查
打开 CourseService.java,看到:
@Transactional
public void publishCourse(Long courseId, Long userId) {
Course course = courseMapper.selectById(courseId);
if (course == null) {
throw new BizException("课程不存在");
}
if (!course.getAuthorId().equals(userId)) {
throw new BizException("只有作者可以发布");
}
if (course.getStatus() == CourseStatus.PUBLISHED) {
throw new BizException("课程已发布");
}
courseMapper.updateStatus(courseId, CourseStatus.PUBLISHED);
searchClient.index(courseId); // ← 注意这里
mqProducer.send(new CoursePublishedEvent(courseId)); // ← 还有这里
}
问题识别:
[P0] 事务内调用外部服务
- searchClient.index() 是 ES 调用,可能超时
- mqProducer.send() 是 MQ 调用,可能失败
- 两者都在 @Transactional 内,会导致事务超长或消息先于提交发出
[P0] 权限校验在 Service 层,但 Controller 层没有 @PreAuthorize
- 如果其他 Service 调用这个方法,权限校验会被绕过
- 建议:Controller 层加 @PreAuthorize,Service 层保留业务校验
[P1] 课程状态变更没有乐观锁
- 两个请求同时发布,可能导致状态不一致
- 建议:updateStatus 加 WHERE status = DRAFT 条件
[P1] searchClient.index() 失败会怎样?
- 如果 ES 挂了,整个发布会失败,但 DB 已提交
- 建议:用 Outbox 模式,或 try-catch + 异步重试
[P2] 异常信息可以更明确
- "课程不存在" 可以带上 courseId
- "只有作者可以发布" 可以带上当前用户和作者 ID