mirror of
https://github.com/awesome-skills/code-review-skill.git
synced 2026-03-22 10:28:31 +08:00
feat: Add Java guide and optimize Rust guide
- Add `reference/java.md` covering Java 17/21, Spring Boot 3, and Virtual Threads. - Update `reference/rust.md` to include modern idioms (let-else), tracing for observability, and the Typestate pattern. - Update `SKILL.md` and `README.md` to reflect new capabilities.
This commit is contained in:
28
README.md
28
README.md
@@ -6,7 +6,7 @@
|
|||||||
|
|
||||||
## English
|
## English
|
||||||
|
|
||||||
> A modular code review skill for Claude Code, covering React 19, Vue 3, Rust, TypeScript, Python, CSS/Less/Sass, architecture design, and performance optimization.
|
> A modular code review skill for Claude Code, covering React 19, Vue 3, Rust, TypeScript, Java, Python, CSS/Less/Sass, architecture design, and performance optimization.
|
||||||
|
|
||||||
[](https://opensource.org/licenses/MIT)
|
[](https://opensource.org/licenses/MIT)
|
||||||
|
|
||||||
@@ -14,7 +14,7 @@
|
|||||||
|
|
||||||
This is a Claude Code skill designed to help developers conduct effective code reviews. It provides:
|
This is a Claude Code skill designed to help developers conduct effective code reviews. It provides:
|
||||||
|
|
||||||
- **Language-specific patterns** for React 19, Vue 3, Rust, TypeScript/JavaScript, Python
|
- **Language-specific patterns** for React 19, Vue 3, Rust, TypeScript/JavaScript, Java, Python
|
||||||
- **Modern framework support** including React Server Components, TanStack Query v5, Suspense & Streaming
|
- **Modern framework support** including React Server Components, TanStack Query v5, Suspense & Streaming
|
||||||
- **Comprehensive checklists** for security, performance, and code quality
|
- **Comprehensive checklists** for security, performance, and code quality
|
||||||
- **Best practices** for giving constructive feedback
|
- **Best practices** for giving constructive feedback
|
||||||
@@ -30,6 +30,7 @@ This is a Claude Code skill designed to help developers conduct effective code r
|
|||||||
| **Vue 3** | Composition API, reactivity system, defineProps/defineEmits, watch cleanup |
|
| **Vue 3** | Composition API, reactivity system, defineProps/defineEmits, watch cleanup |
|
||||||
| **Rust** | Ownership & borrowing, unsafe code review, async/await, error handling (thiserror vs anyhow) |
|
| **Rust** | Ownership & borrowing, unsafe code review, async/await, error handling (thiserror vs anyhow) |
|
||||||
| **TypeScript** | Type safety, async/await patterns, common pitfalls |
|
| **TypeScript** | Type safety, async/await patterns, common pitfalls |
|
||||||
|
| **Java** | Java 17/21 features (Records, Switch), Spring Boot 3, Virtual Threads, Stream API best practices |
|
||||||
| **Go** | Error handling, goroutines/channels, context propagation, interface design, testing patterns |
|
| **Go** | Error handling, goroutines/channels, context propagation, interface design, testing patterns |
|
||||||
| **CSS/Less/Sass** | CSS variables, !important usage, performance optimization, responsive design, browser compatibility |
|
| **CSS/Less/Sass** | CSS variables, !important usage, performance optimization, responsive design, browser compatibility |
|
||||||
| **TanStack Query** | v5 best practices, queryOptions, useSuspenseQuery, optimistic updates |
|
| **TanStack Query** | v5 best practices, queryOptions, useSuspenseQuery, optimistic updates |
|
||||||
@@ -45,6 +46,7 @@ This is a Claude Code skill designed to help developers conduct effective code r
|
|||||||
| **reference/vue.md** | ~920 | Vue 3.5 patterns + Composition API (on-demand) |
|
| **reference/vue.md** | ~920 | Vue 3.5 patterns + Composition API (on-demand) |
|
||||||
| **reference/rust.md** | ~840 | Rust async/ownership/cancellation safety (on-demand) |
|
| **reference/rust.md** | ~840 | Rust async/ownership/cancellation safety (on-demand) |
|
||||||
| **reference/typescript.md** | ~540 | TypeScript generics/strict mode/ESLint (on-demand) |
|
| **reference/typescript.md** | ~540 | TypeScript generics/strict mode/ESLint (on-demand) |
|
||||||
|
| **reference/java.md** | ~800 | Java 17/21 & Spring Boot 3 patterns (on-demand) |
|
||||||
| **reference/python.md** | ~1070 | Python async/typing/pytest (on-demand) |
|
| **reference/python.md** | ~1070 | Python async/typing/pytest (on-demand) |
|
||||||
| **reference/go.md** | ~990 | Go goroutines/channels/context/interfaces (on-demand) |
|
| **reference/go.md** | ~990 | Go goroutines/channels/context/interfaces (on-demand) |
|
||||||
| **reference/css-less-sass.md** | ~660 | CSS/Less/Sass variables/performance/responsive (on-demand) |
|
| **reference/css-less-sass.md** | ~660 | CSS/Less/Sass variables/performance/responsive (on-demand) |
|
||||||
@@ -97,6 +99,7 @@ code-review-skill/
|
|||||||
│ ├── vue.md # Vue 3 patterns (on-demand)
|
│ ├── vue.md # Vue 3 patterns (on-demand)
|
||||||
│ ├── rust.md # Rust patterns (on-demand)
|
│ ├── rust.md # Rust patterns (on-demand)
|
||||||
│ ├── typescript.md # TypeScript/JS patterns (on-demand)
|
│ ├── typescript.md # TypeScript/JS patterns (on-demand)
|
||||||
|
│ ├── java.md # Java patterns (on-demand)
|
||||||
│ ├── python.md # Python patterns (on-demand)
|
│ ├── python.md # Python patterns (on-demand)
|
||||||
│ ├── go.md # Go patterns (on-demand)
|
│ ├── go.md # Go patterns (on-demand)
|
||||||
│ ├── css-less-sass.md # CSS/Less/Sass patterns (on-demand)
|
│ ├── css-less-sass.md # CSS/Less/Sass patterns (on-demand)
|
||||||
@@ -124,6 +127,13 @@ This means reviewing a React PR only loads SKILL.md + react.md, not Vue/Rust/Pyt
|
|||||||
|
|
||||||
### Key Topics Covered
|
### Key Topics Covered
|
||||||
|
|
||||||
|
#### Java & Spring Boot
|
||||||
|
|
||||||
|
- **Java 17/21 Features**: Records, Pattern Matching for Switch, Text Blocks
|
||||||
|
- **Virtual Threads**: High-throughput I/O with Project Loom
|
||||||
|
- **Spring Boot 3**: Constructor Injection, `@ConfigurationProperties`, ProblemDetail
|
||||||
|
- **JPA Performance**: Solving N+1 problems, correct Entity design (equals/hashCode)
|
||||||
|
|
||||||
#### React 19
|
#### React 19
|
||||||
|
|
||||||
- `useActionState` - Unified form state management
|
- `useActionState` - Unified form state management
|
||||||
@@ -178,7 +188,7 @@ This project is licensed under the MIT License - see the [LICENSE](LICENSE) file
|
|||||||
|
|
||||||
## 中文
|
## 中文
|
||||||
|
|
||||||
> 一个模块化的 Claude Code 代码审查技能,覆盖 React 19、Vue 3、Rust、TypeScript、Python、CSS/Less/Sass、架构设计和性能优化。
|
> 一个模块化的 Claude Code 代码审查技能,覆盖 React 19、Vue 3、Rust、TypeScript、Java、Python、CSS/Less/Sass、架构设计和性能优化。
|
||||||
|
|
||||||
[](https://opensource.org/licenses/MIT)
|
[](https://opensource.org/licenses/MIT)
|
||||||
|
|
||||||
@@ -186,7 +196,7 @@ This project is licensed under the MIT License - see the [LICENSE](LICENSE) file
|
|||||||
|
|
||||||
这是一个为 Claude Code 设计的代码审查技能,旨在帮助开发者进行高效的代码审查。它提供:
|
这是一个为 Claude Code 设计的代码审查技能,旨在帮助开发者进行高效的代码审查。它提供:
|
||||||
|
|
||||||
- **语言特定模式**:覆盖 React 19、Vue 3、Rust、TypeScript/JavaScript、Python
|
- **语言特定模式**:覆盖 React 19、Vue 3、Rust、TypeScript/JavaScript、Java、Python
|
||||||
- **现代框架支持**:包括 React Server Components、TanStack Query v5、Suspense & Streaming
|
- **现代框架支持**:包括 React Server Components、TanStack Query v5、Suspense & Streaming
|
||||||
- **全面的检查清单**:安全、性能和代码质量检查
|
- **全面的检查清单**:安全、性能和代码质量检查
|
||||||
- **最佳实践**:如何提供建设性的反馈
|
- **最佳实践**:如何提供建设性的反馈
|
||||||
@@ -202,6 +212,7 @@ This project is licensed under the MIT License - see the [LICENSE](LICENSE) file
|
|||||||
| **Vue 3** | Composition API、响应性系统、defineProps/defineEmits、watch 清理 |
|
| **Vue 3** | Composition API、响应性系统、defineProps/defineEmits、watch 清理 |
|
||||||
| **Rust** | 所有权与借用、unsafe 代码审查、async/await、错误处理(thiserror vs anyhow) |
|
| **Rust** | 所有权与借用、unsafe 代码审查、async/await、错误处理(thiserror vs anyhow) |
|
||||||
| **TypeScript** | 类型安全、async/await 模式、常见陷阱 |
|
| **TypeScript** | 类型安全、async/await 模式、常见陷阱 |
|
||||||
|
| **Java** | Java 17/21 特性(Records, Switch)、Spring Boot 3、虚拟线程、Stream API 最佳实践 |
|
||||||
| **Go** | 错误处理、goroutine/channel、context 传播、接口设计、测试模式 |
|
| **Go** | 错误处理、goroutine/channel、context 传播、接口设计、测试模式 |
|
||||||
| **CSS/Less/Sass** | CSS 变量规范、!important 使用、性能优化、响应式设计、浏览器兼容性 |
|
| **CSS/Less/Sass** | CSS 变量规范、!important 使用、性能优化、响应式设计、浏览器兼容性 |
|
||||||
| **TanStack Query** | v5 最佳实践、queryOptions、useSuspenseQuery、乐观更新 |
|
| **TanStack Query** | v5 最佳实践、queryOptions、useSuspenseQuery、乐观更新 |
|
||||||
@@ -217,6 +228,7 @@ This project is licensed under the MIT License - see the [LICENSE](LICENSE) file
|
|||||||
| **reference/vue.md** | ~920 | Vue 3.5 + Composition API(按需加载)|
|
| **reference/vue.md** | ~920 | Vue 3.5 + Composition API(按需加载)|
|
||||||
| **reference/rust.md** | ~840 | Rust async/所有权/取消安全性(按需加载)|
|
| **reference/rust.md** | ~840 | Rust async/所有权/取消安全性(按需加载)|
|
||||||
| **reference/typescript.md** | ~540 | TypeScript 泛型/strict 模式/ESLint(按需加载)|
|
| **reference/typescript.md** | ~540 | TypeScript 泛型/strict 模式/ESLint(按需加载)|
|
||||||
|
| **reference/java.md** | ~800 | Java 17/21 & Spring Boot 3 模式(按需加载)|
|
||||||
| **reference/python.md** | ~1070 | Python async/类型注解/pytest(按需加载)|
|
| **reference/python.md** | ~1070 | Python async/类型注解/pytest(按需加载)|
|
||||||
| **reference/go.md** | ~990 | Go goroutine/channel/context/接口(按需加载)|
|
| **reference/go.md** | ~990 | Go goroutine/channel/context/接口(按需加载)|
|
||||||
| **reference/css-less-sass.md** | ~660 | CSS/Less/Sass 变量/性能/响应式(按需加载)|
|
| **reference/css-less-sass.md** | ~660 | CSS/Less/Sass 变量/性能/响应式(按需加载)|
|
||||||
@@ -269,6 +281,7 @@ code-review-skill/
|
|||||||
│ ├── vue.md # Vue 3 模式(按需加载)
|
│ ├── vue.md # Vue 3 模式(按需加载)
|
||||||
│ ├── rust.md # Rust 模式(按需加载)
|
│ ├── rust.md # Rust 模式(按需加载)
|
||||||
│ ├── typescript.md # TypeScript/JS 模式(按需加载)
|
│ ├── typescript.md # TypeScript/JS 模式(按需加载)
|
||||||
|
│ ├── java.md # Java 模式(按需加载)
|
||||||
│ ├── python.md # Python 模式(按需加载)
|
│ ├── python.md # Python 模式(按需加载)
|
||||||
│ ├── go.md # Go 模式(按需加载)
|
│ ├── go.md # Go 模式(按需加载)
|
||||||
│ ├── css-less-sass.md # CSS/Less/Sass 模式(按需加载)
|
│ ├── css-less-sass.md # CSS/Less/Sass 模式(按需加载)
|
||||||
@@ -296,6 +309,13 @@ code-review-skill/
|
|||||||
|
|
||||||
### 核心内容
|
### 核心内容
|
||||||
|
|
||||||
|
#### Java & Spring Boot
|
||||||
|
|
||||||
|
- **Java 17/21 特性**:Records、Switch 模式匹配、文本块
|
||||||
|
- **虚拟线程**:Project Loom 带来的高吞吐量 I/O
|
||||||
|
- **Spring Boot 3**:构造器注入、`@ConfigurationProperties`、ProblemDetail
|
||||||
|
- **JPA 性能**:解决 N+1 问题、正确的 Entity 设计(equals/hashCode)
|
||||||
|
|
||||||
#### React 19
|
#### React 19
|
||||||
|
|
||||||
- `useActionState` - 统一的表单状态管理
|
- `useActionState` - 统一的表单状态管理
|
||||||
|
|||||||
3
SKILL.md
3
SKILL.md
@@ -1,7 +1,7 @@
|
|||||||
---
|
---
|
||||||
name: code-review-excellence
|
name: code-review-excellence
|
||||||
description: |
|
description: |
|
||||||
Provides comprehensive code review guidance for React 19, Vue 3, Rust, TypeScript, and Python.
|
Provides comprehensive code review guidance for React 19, Vue 3, Rust, TypeScript, Java, and Python.
|
||||||
Helps catch bugs, improve code quality, and give constructive feedback.
|
Helps catch bugs, improve code quality, and give constructive feedback.
|
||||||
Use when: reviewing pull requests, conducting PR reviews, code review, reviewing code changes,
|
Use when: reviewing pull requests, conducting PR reviews, code review, reviewing code changes,
|
||||||
establishing review standards, mentoring developers, architecture reviews, security audits,
|
establishing review standards, mentoring developers, architecture reviews, security audits,
|
||||||
@@ -180,6 +180,7 @@ Use labels to indicate priority:
|
|||||||
| **Rust** | [Rust Guide](reference/rust.md) | 所有权/借用, Unsafe 审查, 异步代码, 错误处理 |
|
| **Rust** | [Rust Guide](reference/rust.md) | 所有权/借用, Unsafe 审查, 异步代码, 错误处理 |
|
||||||
| **TypeScript** | [TypeScript Guide](reference/typescript.md) | 类型安全, async/await, 不可变性 |
|
| **TypeScript** | [TypeScript Guide](reference/typescript.md) | 类型安全, async/await, 不可变性 |
|
||||||
| **Python** | [Python Guide](reference/python.md) | 可变默认参数, 异常处理, 类属性 |
|
| **Python** | [Python Guide](reference/python.md) | 可变默认参数, 异常处理, 类属性 |
|
||||||
|
| **Java** | [Java Guide](reference/java.md) | Java 17/21 新特性, Spring Boot 3, 虚拟线程, Stream/Optional |
|
||||||
| **Go** | [Go Guide](reference/go.md) | 错误处理, goroutine/channel, context, 接口设计 |
|
| **Go** | [Go Guide](reference/go.md) | 错误处理, goroutine/channel, context, 接口设计 |
|
||||||
| **CSS/Less/Sass** | [CSS Guide](reference/css-less-sass.md) | 变量规范, !important, 性能优化, 响应式, 兼容性 |
|
| **CSS/Less/Sass** | [CSS Guide](reference/css-less-sass.md) | 变量规范, !important, 性能优化, 响应式, 兼容性 |
|
||||||
|
|
||||||
|
|||||||
405
reference/java.md
Normal file
405
reference/java.md
Normal file
@@ -0,0 +1,405 @@
|
|||||||
|
# Java Code Review Guide
|
||||||
|
|
||||||
|
Java 审查重点:Java 17/21 新特性、Spring Boot 3 最佳实践、并发编程(虚拟线程)、JPA 性能优化以及代码可维护性。
|
||||||
|
|
||||||
|
## 目录
|
||||||
|
|
||||||
|
- [现代 Java 特性 (17/21+)](#现代-java-特性-1721)
|
||||||
|
- [Stream API & Optional](#stream-api--optional)
|
||||||
|
- [Spring Boot 最佳实践](#spring-boot-最佳实践)
|
||||||
|
- [JPA 与 数据库性能](#jpa-与-数据库性能)
|
||||||
|
- [并发与虚拟线程](#并发与虚拟线程)
|
||||||
|
- [Lombok 使用规范](#lombok-使用规范)
|
||||||
|
- [异常处理](#异常处理)
|
||||||
|
- [测试规范](#测试规范)
|
||||||
|
- [Review Checklist](#review-checklist)
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 现代 Java 特性 (17/21+)
|
||||||
|
|
||||||
|
### Record (记录类)
|
||||||
|
|
||||||
|
```java
|
||||||
|
// ❌ 传统的 POJO/DTO:样板代码多
|
||||||
|
public class UserDto {
|
||||||
|
private final String name;
|
||||||
|
private final int age;
|
||||||
|
|
||||||
|
public UserDto(String name, int age) {
|
||||||
|
this.name = name;
|
||||||
|
this.age = age;
|
||||||
|
}
|
||||||
|
// getters, equals, hashCode, toString...
|
||||||
|
}
|
||||||
|
|
||||||
|
// ✅ 使用 Record:简洁、不可变、语义清晰
|
||||||
|
public record UserDto(String name, int age) {
|
||||||
|
// 紧凑构造函数进行验证
|
||||||
|
public UserDto {
|
||||||
|
if (age < 0) throw new IllegalArgumentException("Age cannot be negative");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### Switch 表达式与模式匹配
|
||||||
|
|
||||||
|
```java
|
||||||
|
// ❌ 传统的 Switch:容易漏掉 break,不仅冗长且易错
|
||||||
|
String type = "";
|
||||||
|
switch (obj) {
|
||||||
|
case Integer i: // Java 16+
|
||||||
|
type = String.format("int %d", i);
|
||||||
|
break;
|
||||||
|
case String s:
|
||||||
|
type = String.format("string %s", s);
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
type = "unknown";
|
||||||
|
}
|
||||||
|
|
||||||
|
// ✅ Switch 表达式:无穿透风险,强制返回值
|
||||||
|
String type = switch (obj) {
|
||||||
|
case Integer i -> "int %d".formatted(i);
|
||||||
|
case String s -> "string %s".formatted(s);
|
||||||
|
case null -> "null value"; // Java 21 处理 null
|
||||||
|
default -> "unknown";
|
||||||
|
};
|
||||||
|
```
|
||||||
|
|
||||||
|
### 文本块 (Text Blocks)
|
||||||
|
|
||||||
|
```java
|
||||||
|
// ❌ 拼接 SQL/JSON 字符串
|
||||||
|
String json = "{\n" +
|
||||||
|
" \"name\": \"Alice\",\n" +
|
||||||
|
" \"age\": 20\n" +
|
||||||
|
"}";
|
||||||
|
|
||||||
|
// ✅ 使用文本块:所见即所得
|
||||||
|
String json = """
|
||||||
|
{
|
||||||
|
"name": "Alice",
|
||||||
|
"age": 20
|
||||||
|
}
|
||||||
|
""";
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Stream API & Optional
|
||||||
|
|
||||||
|
### 避免滥用 Stream
|
||||||
|
|
||||||
|
```java
|
||||||
|
// ❌ 简单的循环不需要 Stream(性能开销 + 可读性差)
|
||||||
|
items.stream().forEach(item -> {
|
||||||
|
process(item);
|
||||||
|
});
|
||||||
|
|
||||||
|
// ✅ 简单场景直接用 for-each
|
||||||
|
for (var item : items) {
|
||||||
|
process(item);
|
||||||
|
}
|
||||||
|
|
||||||
|
// ❌ 极其复杂的 Stream 链
|
||||||
|
List<Dto> result = list.stream()
|
||||||
|
.filter(...)
|
||||||
|
.map(...)
|
||||||
|
.peek(...)
|
||||||
|
.sorted(...)
|
||||||
|
.collect(...); // 难以调试
|
||||||
|
|
||||||
|
// ✅ 拆分为有意义的步骤
|
||||||
|
var filtered = list.stream().filter(...).toList();
|
||||||
|
// ...
|
||||||
|
```
|
||||||
|
|
||||||
|
### Optional 正确用法
|
||||||
|
|
||||||
|
```java
|
||||||
|
// ❌ 将 Optional 用作参数或字段(序列化问题,增加调用复杂度)
|
||||||
|
public void process(Optional<String> name) { ... }
|
||||||
|
public class User {
|
||||||
|
private Optional<String> email; // 不推荐
|
||||||
|
}
|
||||||
|
|
||||||
|
// ✅ Optional 仅用于返回值
|
||||||
|
public Optional<User> findUser(String id) { ... }
|
||||||
|
|
||||||
|
// ❌ 既然用了 Optional 还在用 isPresent() + get()
|
||||||
|
Optional<User> userOpt = findUser(id);
|
||||||
|
if (userOpt.isPresent()) {
|
||||||
|
return userOpt.get().getName();
|
||||||
|
} else {
|
||||||
|
return "Unknown";
|
||||||
|
}
|
||||||
|
|
||||||
|
// ✅ 使用函数式 API
|
||||||
|
return findUser(id)
|
||||||
|
.map(User::getName)
|
||||||
|
.orElse("Unknown");
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Spring Boot 最佳实践
|
||||||
|
|
||||||
|
### 依赖注入 (DI)
|
||||||
|
|
||||||
|
```java
|
||||||
|
// ❌ 字段注入 (@Autowired)
|
||||||
|
// 缺点:难以测试(需要反射注入),掩盖了依赖过多的问题,且不可变性差
|
||||||
|
@Service
|
||||||
|
public class UserService {
|
||||||
|
@Autowired
|
||||||
|
private UserRepository userRepo;
|
||||||
|
}
|
||||||
|
|
||||||
|
// ✅ 构造器注入 (Constructor Injection)
|
||||||
|
// 优点:依赖明确,易于单元测试 (Mock),字段可为 final
|
||||||
|
@Service
|
||||||
|
public class UserService {
|
||||||
|
private final UserRepository userRepo;
|
||||||
|
|
||||||
|
public UserService(UserRepository userRepo) {
|
||||||
|
this.userRepo = userRepo;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// 💡 提示:结合 Lombok @RequiredArgsConstructor 可简化代码,但要小心循环依赖
|
||||||
|
```
|
||||||
|
|
||||||
|
### 配置管理
|
||||||
|
|
||||||
|
```java
|
||||||
|
// ❌ 硬编码配置值
|
||||||
|
@Service
|
||||||
|
public class PaymentService {
|
||||||
|
private String apiKey = "sk_live_12345";
|
||||||
|
}
|
||||||
|
|
||||||
|
// ❌ 直接使用 @Value 散落在代码中
|
||||||
|
@Value("${app.payment.api-key}")
|
||||||
|
private String apiKey;
|
||||||
|
|
||||||
|
// ✅ 使用 @ConfigurationProperties 类型安全配置
|
||||||
|
@ConfigurationProperties(prefix = "app.payment")
|
||||||
|
public record PaymentProperties(String apiKey, int timeout, String url) {}
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## JPA 与 数据库性能
|
||||||
|
|
||||||
|
### N+1 查询问题
|
||||||
|
|
||||||
|
```java
|
||||||
|
// ❌ FetchType.EAGER 或 循环中触发懒加载
|
||||||
|
// Entity 定义
|
||||||
|
@Entity
|
||||||
|
public class User {
|
||||||
|
@OneToMany(fetch = FetchType.EAGER) // 危险!
|
||||||
|
private List<Order> orders;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 业务代码
|
||||||
|
List<User> users = userRepo.findAll(); // 1 条 SQL
|
||||||
|
for (User user : users) {
|
||||||
|
// 如果是 Lazy,这里会触发 N 条 SQL
|
||||||
|
System.out.println(user.getOrders().size());
|
||||||
|
}
|
||||||
|
|
||||||
|
// ✅ 使用 @EntityGraph 或 JOIN FETCH
|
||||||
|
@Query("SELECT u FROM User u JOIN FETCH u.orders")
|
||||||
|
List<User> findAllWithOrders();
|
||||||
|
```
|
||||||
|
|
||||||
|
### 事务管理
|
||||||
|
|
||||||
|
```java
|
||||||
|
// ❌ 在 Controller 层开启事务(数据库连接占用时间过长)
|
||||||
|
// ❌ 在 private 方法上加 @Transactional(AOP 不生效)
|
||||||
|
@Transactional
|
||||||
|
private void saveInternal() { ... }
|
||||||
|
|
||||||
|
// ✅ 在 Service 层公共方法加 @Transactional
|
||||||
|
// ✅ 读操作显式标记 readOnly = true (性能优化)
|
||||||
|
@Service
|
||||||
|
public class UserService {
|
||||||
|
@Transactional(readOnly = true)
|
||||||
|
public User getUser(Long id) { ... }
|
||||||
|
|
||||||
|
@Transactional
|
||||||
|
public void createUser(UserDto dto) { ... }
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### Entity 设计
|
||||||
|
|
||||||
|
```java
|
||||||
|
// ❌ 在 Entity 中使用 Lombok @Data
|
||||||
|
// @Data 生成的 equals/hashCode 包含所有字段,可能触发懒加载导致性能问题或异常
|
||||||
|
@Entity
|
||||||
|
@Data
|
||||||
|
public class User { ... }
|
||||||
|
|
||||||
|
// ✅ 仅使用 @Getter, @Setter
|
||||||
|
// ✅ 自定义 equals/hashCode (通常基于 ID)
|
||||||
|
@Entity
|
||||||
|
@Getter
|
||||||
|
@Setter
|
||||||
|
public class User {
|
||||||
|
@Id
|
||||||
|
private Long id;
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean equals(Object o) {
|
||||||
|
if (this == o) return true;
|
||||||
|
if (!(o instanceof User)) return false;
|
||||||
|
return id != null && id.equals(((User) o).id);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int hashCode() {
|
||||||
|
return getClass().hashCode();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 并发与虚拟线程
|
||||||
|
|
||||||
|
### 虚拟线程 (Java 21+)
|
||||||
|
|
||||||
|
```java
|
||||||
|
// ❌ 传统线程池处理大量 I/O 阻塞任务(资源耗尽)
|
||||||
|
ExecutorService executor = Executors.newFixedThreadPool(100);
|
||||||
|
|
||||||
|
// ✅ 使用虚拟线程处理 I/O 密集型任务(高吞吐量)
|
||||||
|
// Spring Boot 3.2+ 开启:spring.threads.virtual.enabled=true
|
||||||
|
ExecutorService executor = Executors.newVirtualThreadPerTaskExecutor();
|
||||||
|
|
||||||
|
// 在虚拟线程中,阻塞操作(如 DB 查询、HTTP 请求)几乎不消耗 OS 线程资源
|
||||||
|
```
|
||||||
|
|
||||||
|
### 线程安全
|
||||||
|
|
||||||
|
```java
|
||||||
|
// ❌ SimpleDateFormat 是线程不安全的
|
||||||
|
private static final SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd");
|
||||||
|
|
||||||
|
// ✅ 使用 DateTimeFormatter (Java 8+)
|
||||||
|
private static final DateTimeFormatter dtf = DateTimeFormatter.ofPattern("yyyy-MM-dd");
|
||||||
|
|
||||||
|
// ❌ HashMap 在多线程环境可能死循环或数据丢失
|
||||||
|
// ✅ 使用 ConcurrentHashMap
|
||||||
|
Map<String, String> cache = new ConcurrentHashMap<>();
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Lombok 使用规范
|
||||||
|
|
||||||
|
```java
|
||||||
|
// ❌ 滥用 @Builder 导致无法强制校验必填字段
|
||||||
|
@Builder
|
||||||
|
public class Order {
|
||||||
|
private String id; // 必填
|
||||||
|
private String note; // 选填
|
||||||
|
}
|
||||||
|
// 调用者可能漏掉 id: Order.builder().note("hi").build();
|
||||||
|
|
||||||
|
// ✅ 关键业务对象建议手动编写 Builder 或构造函数以确保不变量
|
||||||
|
// 或者在 build() 方法中添加校验逻辑 (Lombok @Builder.Default 等)
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 异常处理
|
||||||
|
|
||||||
|
### 全局异常处理
|
||||||
|
|
||||||
|
```java
|
||||||
|
// ❌ 到处 try-catch 吞掉异常或只打印日志
|
||||||
|
try {
|
||||||
|
userService.create(user);
|
||||||
|
} catch (Exception e) {
|
||||||
|
e.printStackTrace(); // 不应该在生产环境使用
|
||||||
|
// return null; // 吞掉异常,上层不知道发生了什么
|
||||||
|
}
|
||||||
|
|
||||||
|
// ✅ 自定义异常 + @ControllerAdvice (Spring Boot 3 ProblemDetail)
|
||||||
|
public class UserNotFoundException extends RuntimeException { ... }
|
||||||
|
|
||||||
|
@RestControllerAdvice
|
||||||
|
public class GlobalExceptionHandler {
|
||||||
|
@ExceptionHandler(UserNotFoundException.class)
|
||||||
|
public ProblemDetail handleNotFound(UserNotFoundException e) {
|
||||||
|
return ProblemDetail.forStatusAndDetail(HttpStatus.NOT_FOUND, e.getMessage());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 测试规范
|
||||||
|
|
||||||
|
### 单元测试 vs 集成测试
|
||||||
|
|
||||||
|
```java
|
||||||
|
// ❌ 单元测试依赖真实数据库或外部服务
|
||||||
|
@SpringBootTest // 启动整个 Context,慢
|
||||||
|
public class UserServiceTest { ... }
|
||||||
|
|
||||||
|
// ✅ 单元测试使用 Mockito
|
||||||
|
@ExtendWith(MockitoExtension.class)
|
||||||
|
class UserServiceTest {
|
||||||
|
@Mock UserRepository repo;
|
||||||
|
@InjectMocks UserService service;
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void shouldCreateUser() { ... }
|
||||||
|
}
|
||||||
|
|
||||||
|
// ✅ 集成测试使用 Testcontainers
|
||||||
|
@Testcontainers
|
||||||
|
@SpringBootTest
|
||||||
|
class UserRepositoryTest {
|
||||||
|
@Container
|
||||||
|
static PostgreSQLContainer<?> postgres = new PostgreSQLContainer<>("postgres:15");
|
||||||
|
// ...
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Review Checklist
|
||||||
|
|
||||||
|
### 基础与规范
|
||||||
|
- [ ] 遵循 Java 17/21 新特性(Switch 表达式, Records, 文本块)
|
||||||
|
- [ ] 避免使用已过时的类(Date, Calendar, SimpleDateFormat)
|
||||||
|
- [ ] 集合操作是否优先使用了 Stream API 或 Collections 方法?
|
||||||
|
- [ ] Optional 仅用于返回值,未用于字段或参数
|
||||||
|
|
||||||
|
### Spring Boot
|
||||||
|
- [ ] 使用构造器注入而非 @Autowired 字段注入
|
||||||
|
- [ ] 配置属性使用了 @ConfigurationProperties
|
||||||
|
- [ ] Controller 职责单一,业务逻辑下沉到 Service
|
||||||
|
- [ ] 全局异常处理使用了 @ControllerAdvice / ProblemDetail
|
||||||
|
|
||||||
|
### 数据库 & 事务
|
||||||
|
- [ ] 读操作事务标记了 `@Transactional(readOnly = true)`
|
||||||
|
- [ ] 检查是否存在 N+1 查询(EAGER fetch 或循环调用)
|
||||||
|
- [ ] Entity 类未使用 @Data,正确实现了 equals/hashCode
|
||||||
|
- [ ] 数据库索引是否覆盖了查询条件
|
||||||
|
|
||||||
|
### 并发与性能
|
||||||
|
- [ ] I/O 密集型任务是否考虑了虚拟线程?
|
||||||
|
- [ ] 线程安全类是否使用正确(ConcurrentHashMap vs HashMap)
|
||||||
|
- [ ] 锁的粒度是否合理?避免在锁内进行 I/O 操作
|
||||||
|
|
||||||
|
### 可维护性
|
||||||
|
- [ ] 关键业务逻辑有充分的单元测试
|
||||||
|
- [ ] 日志记录恰当(使用 Slf4j,避免 System.out)
|
||||||
|
- [ ] 魔法值提取为常量或枚举
|
||||||
@@ -4,18 +4,86 @@
|
|||||||
|
|
||||||
## 目录
|
## 目录
|
||||||
|
|
||||||
|
- [现代 Rust 习语 (Modern Idioms)](#现代-rust-习语-modern-idioms)
|
||||||
- [所有权与借用](#所有权与借用)
|
- [所有权与借用](#所有权与借用)
|
||||||
- [Unsafe 代码审查](#unsafe-代码审查最关键)
|
- [Unsafe 代码审查](#unsafe-代码审查最关键)
|
||||||
- [异步代码](#异步代码)
|
- [异步代码与可观测性](#异步代码与可观测性)
|
||||||
- [取消安全性](#取消安全性)
|
- [取消安全性](#取消安全性)
|
||||||
- [spawn vs await](#spawn-vs-await)
|
- [spawn vs await](#spawn-vs-await)
|
||||||
- [错误处理](#错误处理)
|
- [错误处理](#错误处理)
|
||||||
- [性能](#性能)
|
- [性能](#性能)
|
||||||
- [Trait 设计](#trait-设计)
|
- [API 设计与 Typestate](#api-设计与-typestate)
|
||||||
- [Review Checklist](#rust-review-checklist)
|
- [Review Checklist](#rust-review-checklist)
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
|
## 现代 Rust 习语 (Modern Idioms)
|
||||||
|
|
||||||
|
### let-else (Rust 1.65+)
|
||||||
|
|
||||||
|
```rust
|
||||||
|
// ❌ 传统的 match/if let 嵌套,导致右移(Rightward Drift)
|
||||||
|
fn process_user(id: Option<i32>) {
|
||||||
|
if let Some(user_id) = id {
|
||||||
|
if let Some(user) = find_user(user_id) {
|
||||||
|
// 业务逻辑深层嵌套
|
||||||
|
process(user);
|
||||||
|
} else {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// ✅ 使用 let-else 提前返回,减少嵌套
|
||||||
|
fn process_user(id: Option<i32>) {
|
||||||
|
let Some(user_id) = id else { return };
|
||||||
|
let Some(user) = find_user(user_id) else { return };
|
||||||
|
|
||||||
|
// 业务逻辑在顶层
|
||||||
|
process(user);
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### 派生 Default 与 Smart Constructors
|
||||||
|
|
||||||
|
```rust
|
||||||
|
// ❌ 手动实现 Default 或使用 new() 但参数过多
|
||||||
|
struct Config {
|
||||||
|
timeout: u64,
|
||||||
|
retries: u32,
|
||||||
|
verbose: bool,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Config {
|
||||||
|
fn new(timeout: u64, retries: u32, verbose: bool) -> Self {
|
||||||
|
Self { timeout, retries, verbose }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// ✅ 派生 Default 并提供构建器风格的方法
|
||||||
|
#[derive(Default)]
|
||||||
|
struct Config {
|
||||||
|
timeout: u64,
|
||||||
|
retries: u32,
|
||||||
|
verbose: bool,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Config {
|
||||||
|
// 覆盖默认值的方法
|
||||||
|
fn with_timeout(mut self, timeout: u64) -> Self {
|
||||||
|
self.timeout = timeout;
|
||||||
|
self
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 使用
|
||||||
|
let config = Config::default().with_timeout(5000);
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
## 所有权与借用
|
## 所有权与借用
|
||||||
|
|
||||||
### 避免不必要的 clone()
|
### 避免不必要的 clone()
|
||||||
@@ -188,7 +256,29 @@ pub fn fast_copy(src: &[u8], dst: &mut [u8]) {
|
|||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
## 异步代码
|
## 异步代码与可观测性
|
||||||
|
|
||||||
|
### Tracing vs Println
|
||||||
|
|
||||||
|
```rust
|
||||||
|
// ❌ 在 async 代码中使用 println! 或 log crate
|
||||||
|
// 无法关联同一请求的不同日志,缺乏结构化信息
|
||||||
|
async fn bad_log(user_id: &str) {
|
||||||
|
println!("Processing user {}", user_id);
|
||||||
|
db.query().await;
|
||||||
|
println!("Done");
|
||||||
|
}
|
||||||
|
|
||||||
|
// ✅ 使用 tracing crate
|
||||||
|
// 自动携带上下文(span),支持结构化日志,兼容 OpenTelemetry
|
||||||
|
#[tracing::instrument(skip(data))]
|
||||||
|
async fn good_log(user_id: &str, data: Data) {
|
||||||
|
tracing::info!("Processing user"); // 自动包含 user_id
|
||||||
|
if let Err(e) = db.query().await {
|
||||||
|
tracing::error!(error = ?e, "Database query failed");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
### 避免阻塞操作
|
### 避免阻塞操作
|
||||||
|
|
||||||
@@ -242,33 +332,6 @@ async fn good_lock_tokio(mutex: &tokio::sync::Mutex<Data>) {
|
|||||||
async_operation().await; // OK: tokio Mutex 设计为可跨 await
|
async_operation().await; // OK: tokio Mutex 设计为可跨 await
|
||||||
process(&guard);
|
process(&guard);
|
||||||
}
|
}
|
||||||
|
|
||||||
// 💡 选择指南:
|
|
||||||
// - std::sync::Mutex:低竞争、短临界区、不跨 await
|
|
||||||
// - tokio::sync::Mutex:需要跨 await、高竞争场景
|
|
||||||
```
|
|
||||||
|
|
||||||
### 异步 trait 方法
|
|
||||||
|
|
||||||
```rust
|
|
||||||
// ❌ async trait 方法的陷阱(旧版本)
|
|
||||||
#[async_trait]
|
|
||||||
trait BadRepository {
|
|
||||||
async fn find(&self, id: i64) -> Option<Entity>; // 隐式 Box
|
|
||||||
}
|
|
||||||
|
|
||||||
// ✅ Rust 1.75+:原生 async trait 方法
|
|
||||||
trait Repository {
|
|
||||||
async fn find(&self, id: i64) -> Option<Entity>;
|
|
||||||
|
|
||||||
// 返回具体 Future 类型以避免 allocation
|
|
||||||
fn find_many(&self, ids: &[i64]) -> impl Future<Output = Vec<Entity>> + Send;
|
|
||||||
}
|
|
||||||
|
|
||||||
// ✅ 对于需要 dyn 的场景
|
|
||||||
trait DynRepository: Send + Sync {
|
|
||||||
fn find(&self, id: i64) -> Pin<Box<dyn Future<Output = Option<Entity>> + Send + '_>>;
|
|
||||||
}
|
|
||||||
```
|
```
|
||||||
|
|
||||||
---
|
---
|
||||||
@@ -362,34 +425,6 @@ async fn pinned_select() {
|
|||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
### 文档化取消安全性
|
|
||||||
|
|
||||||
```rust
|
|
||||||
/// Reads a complete message from the stream.
|
|
||||||
///
|
|
||||||
/// # Cancel Safety
|
|
||||||
///
|
|
||||||
/// This method is **not** cancel safe. If cancelled while reading,
|
|
||||||
/// partial data may be lost and the stream state becomes undefined.
|
|
||||||
/// Use `read_message_cancel_safe` if cancellation is expected.
|
|
||||||
async fn read_message(stream: &mut TcpStream) -> Result<Message> {
|
|
||||||
let len = stream.read_u32().await?;
|
|
||||||
let mut buffer = vec![0u8; len as usize];
|
|
||||||
stream.read_exact(&mut buffer).await?;
|
|
||||||
Ok(Message::from_bytes(&buffer))
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Reads a message with cancel safety.
|
|
||||||
///
|
|
||||||
/// # Cancel Safety
|
|
||||||
///
|
|
||||||
/// This method is cancel safe. If cancelled, any partial data
|
|
||||||
/// is preserved in the internal buffer for the next call.
|
|
||||||
async fn read_message_cancel_safe(reader: &mut BufferedReader) -> Result<Message> {
|
|
||||||
reader.read_message_buffered().await
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
## spawn vs await
|
## spawn vs await
|
||||||
@@ -424,7 +459,10 @@ async fn good_background_spawn() {
|
|||||||
// 启动后台任务,不等待完成
|
// 启动后台任务,不等待完成
|
||||||
tokio::spawn(async {
|
tokio::spawn(async {
|
||||||
cleanup_old_sessions().await;
|
cleanup_old_sessions().await;
|
||||||
log_metrics().await;
|
// 使用 tracing 记录错误
|
||||||
|
if let Err(e) = log_metrics().await {
|
||||||
|
tracing::error!(error = ?e, "Failed to log metrics");
|
||||||
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
// 继续执行其他工作
|
// 继续执行其他工作
|
||||||
@@ -457,92 +495,6 @@ async fn good_spawn_arc(data: Arc<Data>) {
|
|||||||
process(&data).await;
|
process(&data).await;
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
// ✅ 方案3:使用作用域任务(tokio-scoped 或 async-scoped)
|
|
||||||
async fn good_scoped_spawn(data: &Data) {
|
|
||||||
// 假设使用 async-scoped crate
|
|
||||||
async_scoped::scope(|s| async {
|
|
||||||
s.spawn(async {
|
|
||||||
process(data).await; // 可以借用
|
|
||||||
});
|
|
||||||
}).await;
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
### JoinHandle 错误处理
|
|
||||||
|
|
||||||
```rust
|
|
||||||
// ❌ 忽略 spawn 的错误
|
|
||||||
async fn bad_ignore_spawn_error() {
|
|
||||||
let handle = tokio::spawn(async {
|
|
||||||
risky_operation().await
|
|
||||||
});
|
|
||||||
let _ = handle.await; // 忽略了 panic 和错误
|
|
||||||
}
|
|
||||||
|
|
||||||
// ✅ 正确处理 JoinHandle 结果
|
|
||||||
async fn good_handle_spawn_error() -> Result<()> {
|
|
||||||
let handle = tokio::spawn(async {
|
|
||||||
risky_operation().await
|
|
||||||
});
|
|
||||||
|
|
||||||
match handle.await {
|
|
||||||
Ok(Ok(result)) => {
|
|
||||||
// 任务成功完成
|
|
||||||
process_result(result);
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
Ok(Err(e)) => {
|
|
||||||
// 任务内部错误
|
|
||||||
Err(e.into())
|
|
||||||
}
|
|
||||||
Err(join_err) => {
|
|
||||||
// 任务 panic 或被取消
|
|
||||||
if join_err.is_panic() {
|
|
||||||
error!("Task panicked: {:?}", join_err);
|
|
||||||
}
|
|
||||||
Err(anyhow!("Task failed: {}", join_err))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
### 结构化并发 vs spawn
|
|
||||||
|
|
||||||
```rust
|
|
||||||
// ✅ 优先使用 join!(结构化并发)
|
|
||||||
async fn structured_concurrency() -> Result<(A, B, C)> {
|
|
||||||
// 所有任务在同一个作用域内
|
|
||||||
// 如果任何一个失败,其他的会被取消
|
|
||||||
tokio::try_join!(
|
|
||||||
fetch_a(),
|
|
||||||
fetch_b(),
|
|
||||||
fetch_c()
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
// ✅ 使用 spawn 时考虑任务生命周期
|
|
||||||
struct TaskManager {
|
|
||||||
handles: Vec<JoinHandle<()>>,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl TaskManager {
|
|
||||||
async fn shutdown(self) {
|
|
||||||
// 优雅关闭:等待所有任务完成
|
|
||||||
for handle in self.handles {
|
|
||||||
if let Err(e) = handle.await {
|
|
||||||
error!("Task failed during shutdown: {}", e);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
async fn abort_all(self) {
|
|
||||||
// 强制关闭:取消所有任务
|
|
||||||
for handle in self.handles {
|
|
||||||
handle.abort();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
```
|
```
|
||||||
|
|
||||||
---
|
---
|
||||||
@@ -583,258 +535,105 @@ fn good_error() -> Result<()> {
|
|||||||
operation().context("failed to perform operation")?;
|
operation().context("failed to perform operation")?;
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
// ✅ 使用 with_context 进行懒计算
|
|
||||||
fn good_error_lazy() -> Result<()> {
|
|
||||||
operation()
|
|
||||||
.with_context(|| format!("failed to process file: {}", filename))?;
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
### 错误类型设计
|
|
||||||
|
|
||||||
```rust
|
|
||||||
// ✅ 使用 #[source] 保留错误链
|
|
||||||
#[derive(Debug, thiserror::Error)]
|
|
||||||
pub enum ServiceError {
|
|
||||||
#[error("database error")]
|
|
||||||
Database(#[source] sqlx::Error),
|
|
||||||
|
|
||||||
#[error("network error: {message}")]
|
|
||||||
Network {
|
|
||||||
message: String,
|
|
||||||
#[source]
|
|
||||||
source: reqwest::Error,
|
|
||||||
},
|
|
||||||
|
|
||||||
#[error("validation failed: {0}")]
|
|
||||||
Validation(String),
|
|
||||||
}
|
|
||||||
|
|
||||||
// ✅ 为常见转换实现 From
|
|
||||||
impl From<sqlx::Error> for ServiceError {
|
|
||||||
fn from(err: sqlx::Error) -> Self {
|
|
||||||
ServiceError::Database(err)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
```
|
```
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
## 性能
|
## API 设计与 Typestate
|
||||||
|
|
||||||
### 避免不必要的 collect()
|
### Typestate 模式 (状态机类型安全)
|
||||||
|
|
||||||
```rust
|
```rust
|
||||||
// ❌ 不必要的 collect——中间分配
|
// ❌ 运行时检查状态
|
||||||
fn bad_sum(items: &[i32]) -> i32 {
|
struct Order {
|
||||||
items.iter()
|
state: String,
|
||||||
.filter(|x| **x > 0)
|
items: Vec<Item>,
|
||||||
.collect::<Vec<_>>() // 不必要!
|
|
||||||
.iter()
|
|
||||||
.sum()
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// ✅ 惰性迭代
|
impl Order {
|
||||||
fn good_sum(items: &[i32]) -> i32 {
|
fn pay(&mut self) {
|
||||||
items.iter().filter(|x| **x > 0).copied().sum()
|
if self.state != "created" {
|
||||||
}
|
panic!("Cannot pay order in state {}", self.state);
|
||||||
```
|
|
||||||
|
|
||||||
### 字符串拼接
|
|
||||||
|
|
||||||
```rust
|
|
||||||
// ❌ 字符串拼接在循环中重复分配
|
|
||||||
fn bad_concat(items: &[&str]) -> String {
|
|
||||||
let mut s = String::new();
|
|
||||||
for item in items {
|
|
||||||
s = s + item; // 每次都重新分配!
|
|
||||||
}
|
}
|
||||||
s
|
self.state = "paid".to_string();
|
||||||
}
|
|
||||||
|
|
||||||
// ✅ 预分配或用 join
|
|
||||||
fn good_concat(items: &[&str]) -> String {
|
|
||||||
items.join("")
|
|
||||||
}
|
|
||||||
|
|
||||||
// ✅ 使用 with_capacity 预分配
|
|
||||||
fn good_concat_capacity(items: &[&str]) -> String {
|
|
||||||
let total_len: usize = items.iter().map(|s| s.len()).sum();
|
|
||||||
let mut result = String::with_capacity(total_len);
|
|
||||||
for item in items {
|
|
||||||
result.push_str(item);
|
|
||||||
}
|
|
||||||
result
|
|
||||||
}
|
|
||||||
|
|
||||||
// ✅ 使用 write! 宏
|
|
||||||
use std::fmt::Write;
|
|
||||||
|
|
||||||
fn good_concat_write(items: &[&str]) -> String {
|
|
||||||
let mut result = String::new();
|
|
||||||
for item in items {
|
|
||||||
write!(result, "{}", item).unwrap();
|
|
||||||
}
|
|
||||||
result
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
### 避免不必要的分配
|
|
||||||
|
|
||||||
```rust
|
|
||||||
// ❌ 不必要的 Vec 分配
|
|
||||||
fn bad_check_any(items: &[Item]) -> bool {
|
|
||||||
let filtered: Vec<_> = items.iter()
|
|
||||||
.filter(|i| i.is_valid())
|
|
||||||
.collect();
|
|
||||||
!filtered.is_empty()
|
|
||||||
}
|
|
||||||
|
|
||||||
// ✅ 使用迭代器方法
|
|
||||||
fn good_check_any(items: &[Item]) -> bool {
|
|
||||||
items.iter().any(|i| i.is_valid())
|
|
||||||
}
|
|
||||||
|
|
||||||
// ❌ String::from 用于静态字符串
|
|
||||||
fn bad_static() -> String {
|
|
||||||
String::from("error message") // 运行时分配
|
|
||||||
}
|
|
||||||
|
|
||||||
// ✅ 返回 &'static str
|
|
||||||
fn good_static() -> &'static str {
|
|
||||||
"error message" // 无分配
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## Trait 设计
|
|
||||||
|
|
||||||
### 避免过度抽象
|
|
||||||
|
|
||||||
```rust
|
|
||||||
// ❌ 过度抽象——不是 Java,不需要 Interface 一切
|
|
||||||
trait Processor { fn process(&self); }
|
|
||||||
trait Handler { fn handle(&self); }
|
|
||||||
trait Manager { fn manage(&self); } // Trait 过多
|
|
||||||
|
|
||||||
// ✅ 只在需要多态时创建 trait
|
|
||||||
// 具体类型通常更简单、更快
|
|
||||||
struct DataProcessor {
|
|
||||||
config: Config,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl DataProcessor {
|
|
||||||
fn process(&self, data: &Data) -> Result<Output> {
|
|
||||||
// 直接实现
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
```
|
|
||||||
|
|
||||||
### Trait 对象 vs 泛型
|
// ✅ 编译时检查状态 (Typestate)
|
||||||
|
struct Order<State> {
|
||||||
```rust
|
items: Vec<Item>,
|
||||||
// ❌ 不必要的 trait 对象(动态分发)
|
state: std::marker::PhantomData<State>,
|
||||||
fn bad_process(handler: &dyn Handler) {
|
|
||||||
handler.handle(); // 虚表调用
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// ✅ 使用泛型(静态分发,可内联)
|
struct Created;
|
||||||
fn good_process<H: Handler>(handler: &H) {
|
struct Paid;
|
||||||
handler.handle(); // 可能被内联
|
struct Shipped;
|
||||||
|
|
||||||
|
impl Order<Created> {
|
||||||
|
fn pay(self) -> Order<Paid> {
|
||||||
|
// 执行支付逻辑
|
||||||
|
Order {
|
||||||
|
items: self.items,
|
||||||
|
state: std::marker::PhantomData,
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// ✅ trait 对象适用场景:异构集合
|
impl Order<Paid> {
|
||||||
fn store_handlers(handlers: Vec<Box<dyn Handler>>) {
|
fn ship(self) -> Order<Shipped> {
|
||||||
// 需要存储不同类型的 handlers
|
// 执行发货逻辑
|
||||||
|
Order {
|
||||||
|
items: self.items,
|
||||||
|
state: std::marker::PhantomData,
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// ✅ 使用 impl Trait 返回类型
|
// 无法对 Created 状态的订单调用 ship():
|
||||||
fn create_handler() -> impl Handler {
|
// let order = Order::<Created>::new();
|
||||||
ConcreteHandler::new()
|
// order.ship(); // 编译错误!
|
||||||
}
|
|
||||||
```
|
```
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
## Rust Review Checklist
|
## Rust Review Checklist
|
||||||
|
|
||||||
### 编译器不能捕获的问题
|
### 现代 Rust 习语
|
||||||
|
- [ ] 使用 `let-else` 减少嵌套
|
||||||
|
- [ ] 使用 `#[derive(Default)]` 而非手动实现
|
||||||
|
- [ ] 优先使用标准库/生态系统中的既定 trait (From/TryFrom)
|
||||||
|
|
||||||
**业务逻辑正确性**
|
### 可观测性 (Observability)
|
||||||
- [ ] 边界条件处理正确
|
- [ ] Async 代码使用 `tracing` 而非 `println!` 或 `log`
|
||||||
- [ ] 状态机转换完整
|
- [ ] `tracing::instrument` 用于关键业务方法
|
||||||
- [ ] 并发场景下的竞态条件
|
- [ ] 错误日志包含上下文和错误链 (`error = ?e`)
|
||||||
|
|
||||||
**API 设计**
|
|
||||||
- [ ] 公共 API 难以误用
|
|
||||||
- [ ] 类型签名清晰表达意图
|
|
||||||
- [ ] 错误类型粒度合适
|
|
||||||
|
|
||||||
### 所有权与借用
|
### 所有权与借用
|
||||||
|
|
||||||
- [ ] clone() 是有意为之,文档说明了原因
|
- [ ] clone() 是有意为之,文档说明了原因
|
||||||
- [ ] Arc<Mutex<T>> 真的需要共享状态吗?
|
- [ ] Arc<Mutex<T>> 真的需要共享状态吗?
|
||||||
- [ ] RefCell 的使用有正当理由
|
|
||||||
- [ ] 生命周期不过度复杂
|
- [ ] 生命周期不过度复杂
|
||||||
- [ ] 考虑使用 Cow 避免不必要的分配
|
- [ ] 考虑使用 Cow 避免不必要的分配
|
||||||
|
|
||||||
### Unsafe 代码(最重要)
|
### Unsafe 代码(最重要)
|
||||||
|
|
||||||
- [ ] 每个 unsafe 块有 SAFETY 注释
|
- [ ] 每个 unsafe 块有 SAFETY 注释
|
||||||
- [ ] unsafe fn 有 # Safety 文档节
|
- [ ] unsafe fn 有 # Safety 文档节
|
||||||
- [ ] 解释了为什么是安全的,不只是做什么
|
- [ ] 解释了为什么是安全的,不只是做什么
|
||||||
- [ ] 列出了必须维护的不变量
|
|
||||||
- [ ] unsafe 边界尽可能小
|
- [ ] unsafe 边界尽可能小
|
||||||
- [ ] 考虑过是否有 safe 替代方案
|
|
||||||
|
|
||||||
### 异步/并发
|
### 异步/并发
|
||||||
|
|
||||||
- [ ] 没有在 async 中阻塞(std::fs、thread::sleep)
|
- [ ] 没有在 async 中阻塞(std::fs、thread::sleep)
|
||||||
- [ ] 没有跨 .await 持有 std::sync 锁
|
- [ ] 没有跨 .await 持有 std::sync 锁
|
||||||
- [ ] spawn 的任务满足 'static
|
- [ ] spawn 的任务满足 'static
|
||||||
- [ ] 锁的获取顺序一致
|
|
||||||
- [ ] Channel 缓冲区大小合理
|
|
||||||
|
|
||||||
### 取消安全性
|
|
||||||
|
|
||||||
- [ ] select! 中的 Future 是取消安全的
|
|
||||||
- [ ] 文档化了 async 函数的取消安全性
|
- [ ] 文档化了 async 函数的取消安全性
|
||||||
- [ ] 取消不会导致数据丢失或不一致状态
|
- [ ] select! 中的 Future 是取消安全的
|
||||||
- [ ] 使用 tokio::pin! 正确处理需要重用的 Future
|
|
||||||
|
|
||||||
### spawn vs await
|
|
||||||
|
|
||||||
- [ ] spawn 只用于真正需要并行的场景
|
|
||||||
- [ ] 简单操作直接 await,不要 spawn
|
|
||||||
- [ ] spawn 的 JoinHandle 结果被正确处理
|
|
||||||
- [ ] 考虑任务的生命周期和关闭策略
|
|
||||||
- [ ] 优先使用 join!/try_join! 进行结构化并发
|
|
||||||
|
|
||||||
### 错误处理
|
### 错误处理
|
||||||
|
|
||||||
- [ ] 库:thiserror 定义结构化错误
|
- [ ] 库:thiserror 定义结构化错误
|
||||||
- [ ] 应用:anyhow + context
|
- [ ] 应用:anyhow + context
|
||||||
- [ ] 没有生产代码 unwrap/expect
|
- [ ] 没有生产代码 unwrap/expect
|
||||||
- [ ] 错误消息对调试有帮助
|
|
||||||
- [ ] must_use 返回值被处理
|
|
||||||
- [ ] 使用 #[source] 保留错误链
|
- [ ] 使用 #[source] 保留错误链
|
||||||
|
|
||||||
### 性能
|
### API 设计
|
||||||
|
- [ ] 使用 Typestate 模式将运行时错误转换为编译时错误
|
||||||
- [ ] 避免不必要的 collect()
|
- [ ] 公共 API 难以误用
|
||||||
- [ ] 大数据传引用
|
- [ ] 类型签名清晰表达意图
|
||||||
- [ ] 字符串用 with_capacity 或 write!
|
|
||||||
- [ ] impl Trait vs Box<dyn Trait> 选择合理
|
|
||||||
- [ ] 热路径避免分配
|
|
||||||
- [ ] 考虑使用 Cow 减少克隆
|
|
||||||
|
|
||||||
### 代码质量
|
|
||||||
|
|
||||||
- [ ] cargo clippy 零警告
|
|
||||||
- [ ] cargo fmt 格式化
|
|
||||||
- [ ] 文档注释完整
|
|
||||||
- [ ] 测试覆盖边界条件
|
|
||||||
- [ ] 公共 API 有文档示例
|
|
||||||
|
|||||||
Reference in New Issue
Block a user