deep-java-review

v1.0.2

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

0· 67·0 current·0 all-time

Install

OpenClaw Prompt Flow

Install with OpenClaw

Best for remote or guided setup. Copy the exact prompt, then paste it into OpenClaw for qfann/deep-java-review.

Previewing Install & Setup.
Prompt PreviewInstall & Setup
Install the skill "deep-java-review" (qfann/deep-java-review) from ClawHub.
Skill page: https://clawhub.ai/qfann/deep-java-review
Keep the work scoped to this skill only.
After install, inspect the skill metadata and help me finish setup.
Use only the metadata you can verify from ClawHub; do not invent missing requirements.
Ask before making any broader environment changes.

Command Line

CLI Commands

Use the direct CLI path if you want to install manually and keep every step visible.

OpenClaw CLI

Bare skill slug

openclaw skills install deep-java-review

ClawHub CLI

Package manager switcher

npx clawhub@latest install deep-java-review
Security Scan
Capability signals
CryptoCan make purchases
These labels describe what authority the skill may exercise. They are separate from suspicious or malicious moderation verdicts.
VirusTotalVirusTotal
Benign
View report →
OpenClawOpenClaw
Benign
high confidence
Purpose & Capability
Name/description (deep Java code review + PRD generation) match the SKILL.md steps: obtaining git diff, tracing call chains, running AST/LSP searches, and producing structured reports. Declared tools (git, grep, lsp_*, ast_grep_search, bash, read/write) are appropriate for the described functionality and no unrelated credentials or binaries are requested.
Instruction Scope
Instructions explicitly operate on repository changes (git diff, staged/unstaged changes) and use language-server/AST searches to trace call chains and infer business logic. The scope is focused on code, SQL/DDL in the repo, and generating PRD/report outputs. There are no instructions to read unrelated system files, exfiltrate data to external endpoints, or access secrets beyond repository contents.
Install Mechanism
No install spec or code files (instruction-only). This minimizes risk because nothing is downloaded or written to disk by an installer.
Credentials
The skill declares no required environment variables, credentials, or config paths. That is proportional to its purpose of analyzing local repo code and generating documentation.
Persistence & Privilege
always:false and default autonomous invocation are set. The skill does not request permanent system presence or access to other skills' configs; nothing in SKILL.md indicates it will modify agent/system settings beyond producing reports.
Assessment
This skill is internally consistent: it will read your repository (git diffs, source files, SQL/DDL) and use code-analysis tools to generate review reports and PRDs. Before enabling it, ensure you run it only on repositories you trust to be analyzed (it will examine all changed files). No external credentials are requested, but the agent will need local access to the repo and LSP/AST tooling; verify those tools are available and review generated reports before sharing externally. If you have private secrets in the repo, consider removing them or restricting the run scope first.

Like a lobster shell, security has layers — review code before you run it.

latestvk97629h7ewxssee1p0awepeq1x85qn7j
67downloads
0stars
3versions
Updated 6h ago
v1.0.2
MIT-0

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时,每个变更点都必须包含

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

Comments

Loading comments...