deep-java-review

Data & APIs

Java项目代码review工具。分析Git变更+完整调用链路上下文,推断业务需求,进行多维度评分和分类汇总,生成完整PRD文档。包含细粒度Java代码审查清单(Null安全、异常处理、Streams、并发、equals/hashCode、资源管理、API设计、性能、MyBatis/ORM、事务边界、SQL/DDL质量)。

Install

openclaw skills install deep-java-review

Java Code Review - Java项目代码评审工具

概述

智能化Java项目代码评审工具,完整流程(7步):

  1. Git Diff 变更获取 - 拉取代码变更内容(支持 --cached / 分支对比)
  2. 完整上下文分析 - 分析调用链路,推断业务逻辑
  3. PRD文档生成 - 覆盖所有变更点(新增+修改)
  4. 代码检测(P0/P1快速扫描) - 严重缺陷和代码缺陷快速定位
  5. 细粒度代码审查清单 - 12类深度检查(A-L):Null安全、异常处理、Streams、并发、Java惯用法、资源管理、API设计、性能、测试、MyBatis/ORM、事务边界、SQL/DDL质量
  6. 多维度评分 - 代码质量评分
  7. Review报告生成 - 输出结构化报告

何时使用此技能

  • 用户需要进行代码评审
  • 用户需要理解变更的业务含义
  • 用户需要生成完整PRD文档
  • 用户需要评分和质量评估
  • 用户说 "review代码" / "检查PR" / "代码review" / "检查下代码"

第1步:Git Diff 变更获取

1.1 确定基准分支

优先询问用户,如用户未指定则使用以下策略:

  • 当前分支有上游跟踪 → 使用 @{u}(上游分支)
  • 存在 develop 分支 → 使用 develop
  • 存在 main/master 分支 → 使用 main/master
  • 否则 → 使用 HEAD~10(最近10次提交)

1.2 获取变更

# 场景1:工作区+暂存区 vs 基准分支(最常用)
git diff <基准分支>..HEAD --name-only
git diff <基准分支>..HEAD -U500
git diff <基准分支>..HEAD --stat

# 场景2:仅检查已暂存(staged)的变更
git diff --cached -U500
git diff --cached --stat

# 场景3:工作区未暂存变更
git diff -U500

1.3 收集信息

  • 新增的文件(新增功能)
  • 修改的文件(功能修改)
  • 删除的文件(功能删除)
  • 变更行数统计

第2步:完整上下文分析

2.1 确定入口点

对于每个变更的方法/类,向上追溯找到业务入口:

  • Controller层的HTTP入口(@PostMapping/@GetMapping)
  • Service层的业务入口方法
  • MQ消息入口(@RabbitListener/@KafkaListener)
  • 定时任务入口(@Scheduled)
  • 外部API入口(Feign Client)

2.2 追踪方法链

使用 lsp_find_references 分析每个关键方法:

入口点
  ↓ 调用
变更方法
  ↓ 调用
子方法1 → 子方法2 → ... → 数据库/外部服务

2.3 业务逻辑推断

基于完整链路推断业务逻辑:

变更方法:InventoryService.deduct()

完整链路:
Controller.createOrder() [下单入口]
  ↓
OrderService.create() [订单创建]
    ↓
InventoryService.deduct() [变更点] ← 库存扣减
    ↓
PaymentService.pay() [支付]
    ↓
NotificationService.notify() [通知]

推断业务需求:
用户下单 → 自动扣减库存 → 支付 → 通知
核心流程:订单+库存+支付+消息 四模块联动

第3步:PRD文档生成(核心能力)

重要:PRD必须覆盖所有变更点,包括新增功能和修改功能。

3.1 PRD文档结构

# 产品需求文档 PRD

## 1. 概述

### 1.1 产品名称
[模块名称-版本号]

### 1.2 需求背景
[为什么需要这个功能,解决什么问题]

### 1.3 需求范围
- 涉及模块:xxx
- 用户类型:xxx
- 使用场景:xxx

---

## 2. 功能需求

### 2.1 功能清单

| # | 功能点名称 | 功能类型 | 优先级 | 涉及文件 | 行号 |
|---|-----------|--------|--------|---------|-----|
| 1 | 功能点A | 新增 | P0 | XxxService.java | 1-100 |
| 2 | 功能点B | 新增 | P1 | XxxServiceImpl.java | 200-300 |
| 3 | 功能点C | 修改 | P2 | XxxConverter.java | 10-30 |
| 4 | 字段调整 | 修改 | P2 | DDL脚本 | - |

### 2.2 功能详情(每个变更点都需要详细描述)

#### 2.2.1 [功能点A-新增功能]

**功能类型**:新增

**功能描述**:
[详细描述这个功能做什么]

**业务规则**:
- 规则1
- 规则2

**核心链路**:

[入口] Controller.method() [POST /api/xxx] ↓ Service.method() [核心] 核心业务逻辑 ← 变更内容 ↓ DAO.method() [终点] 数据库/外部服务


**入参说明**:
| 参数 | 类型 | 必填 | 说明 |
|------|------|------|------|
| param1 | String | 是 | xxx |
| param2 | Integer | 否 | xxx |

**出参说明**:
| 参数 | 类型 | 说明 |
|------|------|------|
| result1 | String | xxx |

**异常处理**:
| 异常场景 | 错误码 | 提示信息 |
|----------|--------|----------|
| 场景1 | E_xxx | xxx |

---

#### 2.2.2 [功能点B-修改功能]

**功能类型**:修改

**涉及文件**:`xxx.java`
**涉及行号**:xxx-xxx

**修改前**:
```java
// 原代码

修改后

// 新代码

变更原因: [为什么要这样修改]

核心链路

[入口] Controller.method() [POST /api/xxx]
  ↓ Service.method()
[核心] 修改的方法 ← 变更内容
  ↓ DAO.method()
[终点] 数据库/外部服务

影响分析

  • 影响的上游:xxx
  • 影响的下游:xxx
  • 兼容性:是否向前兼容
  • 数据库:是否需要迁移

2.2.3 [功能点C-优化功能]

功能类型:优化

优化前: [优化前的描述]

优化后: [优化后的描述]

优化效果

  • 性能提升:xxx%
  • 代码改进:xxx

3. 数据库变更

3.1 新增表

CREATE TABLE xxx (
  id VARCHAR(36) PRIMARY KEY,
  tenant_id VARCHAR(32) NOT NULL,
  org_id VARCHAR(32),
  doc_id VARCHAR(36) NOT NULL,
  retry_type VARCHAR(32),
  retry_count INT DEFAULT 0,
  last_retry_time DATETIME,
  create_time DATETIME NOT NULL,
  PRIMARY KEY (id),
  KEY idx_doc_id (doc_id)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='xxx表';

3.2 修改表

-- 修改字段长度
ALTER TABLE xxx MODIFY COLUMN goods_name VARCHAR(300) COMMENT '商品名称';

-- 新增字段
ALTER TABLE xxx ADD COLUMN operator VARCHAR(300) COMMENT '操作人';

3.3 数据迁移

[如有数据迁移需求,描述迁移脚本]


4. 接口设计

4.1 新增接口

接口名称请求方式路径说明
接口APOST/api/xxxxxx

请求参数

{
  "ids": ["xxx"],
  "operator": "xxx"
}

响应参数

{
  "code": "0",
  "message": "成功",
  "data": null
}

4.2 修改接口

接口名称请求方式路径说明
接口APOST/api/xxx参数调整

变更说明: [描述接口参数变更]


5. 涉及的代码变更

5.1 新增文件

文件路径说明
xxx.java新增功能

5.2 修改文件

文件路径修改内容
xxx.java方法A新增、方法B修改

5.3 删除文件

文件路径删除原因
xxx.java功能废弃

6. 非功能需求

6.1 性能要求

  • 响应时间:< xxx ms
  • 并发能力:xxx TPS

6.2 安全要求

  • 权限校验:是/否
  • 数据加密:是/否

6.3 兼容性要求

  • 向前兼容:是/否
  • 多数据库支持:MySQL/Oracle/PostgreSQL

7. 测试要点

7.1 功能测试

  • 测试点1
  • 测试点2

7.2 异常测试

  • 测试点1
  • 测试点2

7.3 边界测试

  • 测试点1
  • 测试点2

7.4 回归测试

  • 测试点1(确认原有功能不受影响)
  • 测试点2

8. 上线注意点

  • 注意点1
  • 注意点2

9. 变更记录

版本日期变更内容作者
v2.4.0.02026-04-30初始版本xxx

---

## 第4步:代码检测(P0/P1快速扫描)

在进入细粒度审查前,先快速扫描严重缺陷和代码缺陷。

### P0严重缺陷

| 检查项 | 检测模式 | 扣分 |
|--------|----------|------|
| 空指针 | `$OBJ.$METHOD()` obj未检查 | -10 |
| SQL注入 | `"SELECT " + $TABLE` 直接拼接 | -10 |
| 密钥泄露 | `password/token = "xxx"` | -10 |
| 事务缺失 | @Transactional 关键方法无事务 | -10 |

### P1代码缺陷

| 检查项 | 检测模式 | 扣分 |
|--------|----------|------|
| 资源泄漏 | new FileInputStream 未关闭 | -5 |
| 线程安全 | private static 共享变量 | -5 |
| 空catch | catch块为空或仅打印日志 | -5 |
| 循环查询 | for循环内数据库操作 | -5 |

### P2代码规范(快速扫描)

以下为不与第5步细粒度审查清单重叠的独立检查项:

| 检查项 | 检测模式 | 扣分 |
|--------|----------|------|
| 参数过多 | 方法参数超过3个 | -2 |
| 魔法数字 | 字面量未定义为常量 | -2 |
| 原始泛型 | List/Map 未指定类型参数 | -2 |

> **注意**:Streams滥用、Boolean参数、Optional.get()、equals缺hashCode、catch Exception 等 P2 检查项已整合到第5步对应分类中,此处不再重复。

---

## 第5步:细粒度代码审查清单 (Detailed Review Checklist)

### 审查策略

1. **Quick scan** - 理解变更意图,确定审查范围
2. **Checklist pass** - 按以下分类逐项检查
3. **Summary** - 按严重度汇总(Critical → Improvement → Minor)

### A. Null 安全

```java
// ❌ NPE 风险:链式调用不检查中间null
String name = user.getName().toUpperCase();

// ✅ 安全:Optional + map
String name = Optional.ofNullable(user.getName())
    .map(String::toUpperCase)
    .orElse("");

// ✅ 也可以:提前返回
if (user.getName() == null) {
    return "";
}
return user.getName().toUpperCase();

检查项:

  • 链式方法调用是否缺少null检查
  • 公共API缺少 @Nullable / @NonNull 注解
  • Optional.get() 没有 isPresent() 检查——改用 orElseThrow()
  • 方法返回 null 而非 Optional 或空集合
  • 对集合入参做null检查 → 使用 Objects.requireNonNull()

🔧 检测方法:

  • grep: Optional\.get\(\) → 检查是否有 isPresent() 先导
  • ast_grep_search: $OBJ.$METHOD() pattern 查找链式调用中缺少 null 守卫
  • lsp_find_references: 追踪方法返回 null 的调用方

建议:

  • 返回值可能为空时,使用 Optional<T> 替代 null
  • 构造/方法入参使用 Objects.requireNonNull()
  • 返回空集合用 Collections.emptyList() 替代 null

B. 异常处理

// ❌ 吞掉异常(最坏实践)
try {
    process();
} catch (Exception e) {
    // 没有日志,没有处理
}

// ❌ 捕获范围过宽
catch (Exception e) { }
catch (Throwable t) { }

// ❌ 丢失原始异常栈(不传cause)
catch (IOException e) {
    throw new RuntimeException(e.getMessage());  // 丢失堆栈!
}

// ✅ 正确处理
catch (IOException e) {
    log.error("处理文件失败: {}", filename, e);
    throw new ProcessingException("文件处理失败", e);  // 传递cause
}

检查项:

  • 空 catch 块
  • 捕获范围过宽(Exception, Throwable
  • 丢失原始异常(未链式传递 cause)
  • 用异常做流程控制
  • 受检异常泄露到API边界

🔧 检测方法:

  • ast_grep_search: catch (Exceptioncatch (Throwable → 宽泛捕获
  • grep: catch\s*\( 带上下文行查找空 catch 块
  • grep: throw new.*getMessage\(\) → 丢失原始异常栈

建议:

  • 日志同时记录上下文 + 完整栈信息
  • 使用具体的异常子类
  • 领域错误使用自定义异常

C. Streams & 集合操作

// ❌ 遍历中修改集合 → ConcurrentModificationException
for (Item item : items) {
    if (item.isExpired()) {
        items.remove(item);
    }
}

// ✅ 使用 removeIf
items.removeIf(Item::isExpired);

// ❌ 简单逻辑滥用Stream
list.stream().forEach(System.out::println);

// ✅ 简单场景直接用for循环
for (Item item : list) {
    System.out.println(item);
}

// ❌ 假设 Collectors.toList() 返回可变List
List<String> names = users.stream()
    .map(User::getName)
    .collect(Collectors.toList());
names.add("extra");  // 可能不可变!

// ✅ 显式要求可变集合
List<String> names = users.stream()
    .map(User::getName)
    .collect(Collectors.toCollection(ArrayList::new));

检查项:

  • 遍历时修改集合(用 removeIf() 替代)
  • 简单操作用Stream替代for循环(可读性反而差)
  • Collectors.toList() 假定返回可变列表
  • 未使用 List.of(), Set.of(), Map.of() 创建不可变集合
  • 不理解就乱用并行流(parallelStream)

🔧 检测方法:

  • ast_grep_search: .stream().forEach → 可能的滥用模式
  • grep: \.remove\(for\s*\( 块内 → 遍历时修改
  • grep: \.collect\(Collectors\.toList\(\)\) → 检查后续是否有 add/remove 操作

建议:

  • 防御性拷贝用 List.copyOf()
  • Stream 用于数据转换,循环用于副作用操作
  • 简单逻辑(1-2行映射)用Stream,复杂逻辑用for循环

D. 并发安全

// ❌ 非线程安全的集合
private Map<String, User> cache = new HashMap<>();

// ✅ 线程安全集合
private Map<String, User> cache = new ConcurrentHashMap<>();

// ❌ check-then-act 竞态条件
if (!map.containsKey(key)) {
    map.put(key, computeValue());  // 多线程下可能重复计算
}

// ✅ 原子操作
map.computeIfAbsent(key, k -> computeValue());

// ❌ 双重检查锁(没有volatile则broken)
if (instance == null) {
    synchronized(this) {
        if (instance == null) {
            instance = new Instance();  // 可能看到半初始化对象
        }
    }
}

// ✅ 使用静态内部类 / volatile
private volatile Instance instance;

检查项:

  • 共享可变状态缺少同步
  • check-then-act 操作缺少原子性
  • 共享变量缺少 volatile
  • 在非final对象上同步
  • 线程不安全的懒初始化

🔧 检测方法:

  • grep: private static.*Map|List|Set → 静态共享集合
  • ast_grep_search: synchronized\( → 检查同步对象是否为 final
  • grep: if \(.*== null\) 在 synchronized 块内 → 双重检查锁

建议:

  • 优先使用不可变对象
  • 优先用 java.util.concurrent 并发集合类
  • 简单场景用 AtomicReference, AtomicInteger
  • 考虑添加 @ThreadSafe / @NotThreadSafe 注解

E. Java 惯用法 (equals/hashCode/toString/Builder)

equals & hashCode:

// ❌ 只重写equals不重写hashCode → 破坏HashMap契约!
@Override
public boolean equals(Object o) { ... }
// hashCode 缺失!

// ❌ hashCode用了可变字段 → 放入HashMap后字段变了就找不到了
@Override
public int hashCode() {
    return Objects.hash(id, mutableField);  // 危险!
}

// ✅ 同时重写,只用不可变字段
@Override
public boolean equals(Object o) {
    if (this == o) return true;
    if (!(o instanceof User user)) return false;
    return Objects.equals(id, user.id);
}

@Override
public int hashCode() {
    return Objects.hash(id);
}

toString:

// ❌ 包含敏感数据
return "User{password='" + password + "'}";

// ✅ 调试友好但安全
return "User{id=" + id + ", name='" + name + "'}";

Builder模式:

// ✅ 当构造参数 > 3 时推荐
User user = User.builder()
    .name("张三")
    .email("zhangsan@example.com")
    .build();

检查项:

  • equals 有但 hashCode 缺失 → 确定要破坏HashMap/HashSet吗?
  • hashCode 使用了可变字段
  • 领域对象缺少 toString(调试困难)
  • 构造参数 > 3-4 → 建议Builder
  • 未使用 Java 16+ instanceof pattern matching

🔧 检测方法:

  • ast_grep_search: public boolean equals → 检查同文件是否有 public int hashCode
  • grep: Objects\.hash\(.*this\. → hashCode 使用了实例字段(可能可变)
  • grep: password|secret|tokentoString\(\) 方法内 → 敏感数据泄露

F. 资源管理 (Try-with-resources)

// ❌ 资源泄漏
FileInputStream fis = new FileInputStream(file);
// ... 抛异常前没close

// ✅ try-with-resources
try (FileInputStream fis = new FileInputStream(file)) {
    // 自动关闭
}

// ❌ 多个资源嵌套,可能内层未关闭
try (BufferedWriter writer = new BufferedWriter(new FileWriter(file))) {
    // 如果 BufferedWriter 构造失败,FileWriter 可能泄漏
}

// ✅ 独立声明
try (FileWriter fw = new FileWriter(file);
     BufferedWriter writer = new BufferedWriter(fw)) {
    // 两者都被保证关闭
}

检查项:

  • Closeable/AutoCloseable 未使用 try-with-resources
  • 数据库连接/Statement/ResultSet 未正确关闭
  • 嵌套资源声明顺序错误

🔧 检测方法:

  • grep: new (File|Input|Output|Reader|Writer|Connection|Statement) → 检查是否在 try-with-resources 内
  • ast_grep_search: new $CLOSEABLE($$$) 不在 try ( 块内 → 资源泄漏风险

强制规则:任何实现了 Closeable/AutoCloseable 的资源,必须使用 try-with-resources。

G. API 设计

// ❌ Boolean参数 → 调用端看不懂语义
process(data, true, false);

// ✅ 使用枚举
process(data, ProcessMode.ASYNC, ErrorHandling.STRICT);

// ❌ 找不到返回null (调用方不知道)
public User findById(Long id) {
    return users.get(id);  // 可能null
}

// ✅ 语义明确的 Optional
public Optional<User> findById(Long id) {
    return Optional.ofNullable(users.get(id));
}

// ❌ 入参允许null → 调用方不知道能不能传null
public void process(List<Item> items) {
    if (items == null) items = Collections.emptyList();
}

// ✅ 明确要求非null
public void process(List<Item> items) {
    Objects.requireNonNull(items, "items must not be null");
}

检查项:

  • Boolean参数 → 建议用枚举替代
  • 方法参数 > 3 → 考虑参数对象
  • 同一模块内null处理不一致
  • 公共API入参缺少校验(Objects.requireNonNull

🔧 检测方法:

  • ast_grep_search: $VISIBILITY $RET $NAME(boolean $PARAM → Boolean 参数
  • grep: public.*\(.*,.*,.*,.*, → 参数超过3个的方法签名
  • grep: Objects\.requireNonNull → 检查公共方法是否都有入参校验

H. 性能注意事项

// ❌ 循环内String拼接 → 每次创建新String对象
String result = "";
for (String s : strings) {
    result += s;
}

// ✅ StringBuilder
StringBuilder sb = new StringBuilder();
for (String s : strings) {
    sb.append(s);
}

// ❌ 循环内编译正则 → 每次都重新编译
for (String line : lines) {
    if (line.matches("pattern.*")) { }
}

// ✅ 预编译为静态常量
private static final Pattern PATTERN = Pattern.compile("pattern.*");
for (String line : lines) {
    if (PATTERN.matcher(line).matches()) { }
}

// ❌ N+1 查询
for (User user : users) {
    List<Order> orders = orderRepo.findByUserId(user.getId());
}

// ✅ 批量查询
Map<Long, List<Order>> ordersByUser = orderRepo.findByUserIds(userIds);

检查项:

  • 循环内 String 拼接 → 用 StringBuilder
  • 循环内正则编译 → 预编译为 Pattern 常量
  • N+1 查询模式
  • 循环内创建可复用对象
  • 未使用原始类型Stream(IntStream, LongStream

🔧 检测方法:

  • grep: \+=for\s*\( 块内 → 循环内 String 拼接
  • grep: \.matches\(for\s*\( 块内 → 循环内正则编译
  • ast_grep_search: for.*\{.*\.(select|find|query) → N+1 查询模式

I. 测试提示

建议为以下场景补充测试:

  • Null入参
  • 空集合入参
  • 边界值
  • 异常路径
  • 并发访问(如适用)

🔧 检测方法:

  • glob: **/src/test/**/*Test*.java → 检查是否有对应测试文件
  • grep: @Test → 统计测试方法数量 vs 变更方法数量

J. MyBatis/ORM 检查

// ❌ N+1 查询:循环内逐条查询
for (Order order : orders) {
    List<OrderItem> items = orderItemMapper.selectByOrderId(order.getId());
    order.setItems(items);
}

// ✅ 批量查询 + 内存组装
List<Long> orderIds = orders.stream().map(Order::getId).collect(Collectors.toList());
List<OrderItem> allItems = orderItemMapper.selectByOrderIds(orderIds);
Map<Long, List<OrderItem>> itemsMap = allItems.stream()
    .collect(Collectors.groupingBy(OrderItem::getOrderId));
orders.forEach(o -> o.setItems(itemsMap.getOrDefault(o.getId(), Collections.emptyList())));

// ❌ 分页插件滥用:先查全部再内存分页
List<User> allUsers = userMapper.selectAll();
List<User> page = allUsers.subList(offset, offset + limit);

// ✅ 使用分页插件
PageHelper.startPage(pageNum, pageSize);
List<User> users = userMapper.selectByCondition(condition);

检查项:

  • XML映射ID与方法名不一致
  • N+1 查询模式(循环内调用 Mapper)
  • 分页插件滥用(全量查询后内存分页)
  • 一级缓存/二级缓存不当使用(脏读风险)
  • <if test> 条件缺少对空字符串的判断
  • #{}${} 混用(${} 有 SQL 注入风险)

🔧 检测方法:

  • grep: <select|<update|<insert|<delete*.xml → 检查 id 命名是否与方法名一致
  • ast_grep_search: for.*\{.*Mapper\.|for.*\{.*mapper\. → N+1 查询模式
  • grep: PageHelper\.startPage → 检查是否紧跟 Mapper 调用(分页生效)
  • grep: \$\{*.xml → SQL 注入风险(应使用 #{}

K. 事务边界检查

// ❌ 事务传播级别错误:REQUIRES_NEW 导致外层事务回滚失效
@Transactional(propagation = Propagation.REQUIRED)
public void processOrder(Order order) {
    orderMapper.update(order);
    paymentService.pay(order);  // 内部 REQUIRES_NEW,pay 成功但外层回滚
}

// ❌ 同类方法调用事务失效(Spring AOP 代理限制)
public void batchProcess(List<Order> orders) {
    for (Order order : orders) {
        this.processSingle(order);  // @Transactional 不生效!
    }
}

// ✅ 注入自身代理或拆分到不同类
@Transactional
public void processSingle(Order order) { ... }

// ❌ 长事务导致锁竞争
@Transactional
public void exportReport() {
    // 大量查询 + 文件生成 + 邮件发送 都在一个事务内
    List<Data> data = dataMapper.selectAll();  // 持有数据库连接
    File file = generateExcel(data);           // 耗时操作
    emailService.send(file);                   // 外部IO
}

检查项:

  • 事务传播级别错误(REQUIRES_NEW 嵌套回滚边界)
  • 同类方法调用导致事务失效
  • 长事务(事务内包含外部IO、大量计算)
  • @Transactional 仅用于 public 方法(非 public 不生效)
  • 事务内捕获异常未重新抛出(吞掉回滚)
  • readOnly=true 未设置(查询方法应标记只读)

🔧 检测方法:

  • grep: @Transactional → 检查 propagation 属性
  • grep: REQUIRES_NEW → 嵌套事务回滚边界
  • grep: @Transactional 在非 public 方法上 → 事务不生效
  • ast_grep_search: @Transactional.*\{.*(?:send|http|file|sleep) → 长事务嫌疑

L. SQL/DDL 质量检查

-- ❌ 缺索引:大表无索引导致全表扫描
CREATE TABLE t_order (
  id BIGINT PRIMARY KEY,
  user_id BIGINT NOT NULL,
  order_no VARCHAR(64) NOT NULL,
  status TINYINT,
  create_time DATETIME
) ENGINE=InnoDB;  -- 缺少 user_id 和 order_no 索引!

-- ✅ 合理索引
CREATE TABLE t_order (
  id BIGINT PRIMARY KEY,
  user_id BIGINT NOT NULL,
  order_no VARCHAR(64) NOT NULL,
  status TINYINT DEFAULT 0,
  create_time DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
  KEY idx_user_id (user_id),
  UNIQUE KEY uk_order_no (order_no),
  KEY idx_status_create (status, create_time)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='订单表';

-- ❌ 字段类型不当
ALTER TABLE t_order ADD COLUMN amount VARCHAR(32);  -- 金额应用 DECIMAL

-- ✅ 正确类型
ALTER TABLE t_order ADD COLUMN amount DECIMAL(18,2) NOT NULL DEFAULT 0.00 COMMENT '订单金额';

检查项:

  • 缺索引(WHERE/JOIN/ORDER BY 字段无索引)
  • 字段类型不当(金额用 VARCHAR、状态用 VARCHAR 而非 TINYINT)
  • 缺 COMMENT(表、字段无注释)
  • 字符集不一致(表间 JOIN 字段字符集不同)
  • DDL 缺少回滚脚本
  • VARCHAR 长度不合理(过长或过短)
  • 缺默认值(NOT NULL 字段无 DEFAULT)

🔧 检测方法:

  • grep: CREATE TABLECOMMENT → 缺表注释
  • grep: ALTER TABLE → 检查是否有对应回滚脚本
  • grep: VARCHAR\( → 检查长度是否合理(如 VARCHAR(1) 或 VARCHAR(5000))
  • grep: amount|price|money.*VARCHAR → 金额字段类型不当

严重度分级参考

严重度标准对应评分
Critical安全漏洞、可能丢失数据、生产环境必崩P0 (-10)
High大概率Bug、严重性能问题、破坏API契约P1 (-5)
Medium代码坏味、可维护性问题、缺少最佳实践P2 (-2)
Low风格问题、微优化、建议性P2 (-2)

第6步:多维度评分系统

评分维度

维度权重计算方式
代码质量20%(100 - 扣分) * 权重
安全性25%(100 - P0问题*10) * 权重
性能20%(100 - 性能问题*8) * 权重
可维护性15%(100 - 规范问题*5) * 权重
业务风险20%(100 - 风险问题*10) * 权重

扣分规则

问题类型扣分
P0问题-10分/个
P1问题-5分/个
P2建议-2分/个

最终评级

等级分数颜色行动
S90-100🟢 绿可合并
A80-89🟢 绿可合并
B70-79🟡 黄需要确认
C60-69🟠 橙需要修改
D<60🔴 红拒绝合并

第7步:Review报告生成

# 代码评审报告

## 基本信息
- 项目名称:xxx
- 评审范围:xxx → xxx
- 变更文件数:N
- 变更行数:+X / -Y

## 📊 质量评分

| 维度 | 得分 | 评级 |
|------|------|------|
| 代码质量 | 85 | A |
| 安全性 | 90 | S |
| 性能 | 80 | A |
| 可维护性 | 75 | B |
| 业务风险 | 70 | B |
| **总分** | **82** | **A** |

## 📋 需求汇总

### 需求1:[功能点A]
- 涉及文件:XxxService.java (新增100行)
- 变更类型:新增功能
- 入口:XxxController.create() (POST /api/xxx)
- 核心链路:

[入口] XxxController.create() ↓ XxxService.process() [核心] XxxHandler.handle() ← 变更点 ↓ XxxRepository.save() [终点] 数据库持久化

- 风险等级:🟡 中

### 需求2:[功能点B]
- 涉及文件:XxxServiceImpl.java, XxxController.java
- 变更类型:新增功能
- 入口:POST /api/xxx/action
- 核心链路:

[入口] XxxController.action() [核心] XxxServiceImpl.action() ← 变更点 ↓ 逻辑处理 [终点] 数据库更新

- 风险等级:🟡 中

## 🔴 严重问题 (P0)
- [ ] 问题1
- 文件:xxx.java
- 行号:xxx
- 修复方案

## 🟡 中等问题 (P1)
- [ ] 问题1
- 文件:xxx.java
- 优化方案

## 🟢 建议优化 (P2)
- [ ] 优化1

## 评审结论
- 总体评级:S/A/B/C/D
- 是否可以合并:是/需修改/拒绝

输出

  1. Review报告code-review-report.md(含评分 + 业务分析 + 细粒度代码检查)
  2. PRD文档prd-{版本号}.md
  3. 控制台摘要 → 评分+问题统计

Review 执行流程总结

Trigger: "review代码" / "检查PR" / "代码评审"

Step 1: git diff 获取变更
  ├─ 确定基准分支(询问用户 / 自动检测)
  ├─ 支持 --cached(仅暂存区)/ 分支对比
  ├─ 统计变更文件和行数
  └─ 识别变更类型(新增/修改/删除)

Step 2: 上下文分析
  ├─ lsp_find_references 追踪调用链
  ├─ 确定业务入口(Controller/MQ/定时任务/Feign)
  └─ 推断业务逻辑

Step 3: PRD文档生成(覆盖所有变更点)
  ├─ 功能清单(含文件+行号)
  ├─ 核心链路图
  ├─ 数据库变更
  └─ 接口设计

Step 4: 代码检测(P0/P1快速扫描)
  ├─ P0严重缺陷(NPE/SQL注入/密钥/事务)
  ├─ P1代码缺陷(资源泄漏/线程安全/空catch/循环查询)
  └─ P2代码规范(参数过多/魔法数字/原始泛型)

Step 5: 细粒度审查清单(12类深度检查)
  ├─ A. Null安全
  ├─ B. 异常处理
  ├─ C. Streams & 集合
  ├─ D. 并发安全
  ├─ E. Java惯用法 (equals/hashCode)
  ├─ F. 资源管理 (try-with-resources)
  ├─ G. API设计
  ├─ H. 性能
  ├─ I. 测试提示
  ├─ J. MyBatis/ORM 检查
  ├─ K. 事务边界检查
  └─ L. SQL/DDL 质量检查

Step 6: 多维度评分
  ├─ 代码质量 20%
  ├─ 安全性 25%
  ├─ 性能 20%
  ├─ 可维护性 15%
  └─ 业务风险 20%

Step 7: 生成Review报告
  ├─ 基本信息
  ├─ 质量评分(含维度拆分)
  ├─ 需求汇总(含调用链路)
  ├─ 问题清单(Critical/High/Medium/Low)
  └─ 评审结论

PRD生成 Checklist

生成PRD时,每个变更点都必须包含

  • 功能名称
  • 功能类型(新增/修改/删除/优化)
  • 涉及文件和行号
  • 核心业务链路
  • 入参/出参说明
  • 异常处理
  • 数据库变更(如有)
  • 测试要点