fix: 符合官方 Skill 规范

- description 改为第三人称
- 目录重命名 references → reference(官方规范)
- 文件引用改用 Markdown 链接格式
- 更新 README.md 路径
This commit is contained in:
Tu Shaokun
2025-11-29 13:48:01 +08:00
parent 9a4d4c4535
commit 8dc3304eca
10 changed files with 26 additions and 25 deletions

View File

@@ -0,0 +1,136 @@
# Code Review Best Practices
Comprehensive guidelines for conducting effective code reviews.
## Review Philosophy
### Goals of Code Review
**Primary Goals:**
- Catch bugs and edge cases before production
- Ensure code maintainability and readability
- Share knowledge across the team
- Enforce coding standards consistently
- Improve design and architecture decisions
**Secondary Goals:**
- Mentor junior developers
- Build team culture and trust
- Document design decisions through discussions
### What Code Review is NOT
- A gatekeeping mechanism to block progress
- An opportunity to show off knowledge
- A place to nitpick formatting (use linters)
- A way to rewrite code to personal preference
## Review Timing
### When to Review
| Trigger | Action |
|---------|--------|
| PR opened | Review within 24 hours, ideally same day |
| Changes requested | Re-review within 4 hours |
| Blocking issue found | Communicate immediately |
### Time Allocation
- **Small PR (<100 lines)**: 10-15 minutes
- **Medium PR (100-400 lines)**: 20-40 minutes
- **Large PR (>400 lines)**: Request to split, or 60+ minutes
## Review Depth Levels
### Level 1: Skim Review (5 minutes)
- Check PR description and linked issues
- Verify CI/CD status
- Look at file changes overview
- Identify if deeper review needed
### Level 2: Standard Review (20-30 minutes)
- Full code walkthrough
- Logic verification
- Test coverage check
- Security scan
### Level 3: Deep Review (60+ minutes)
- Architecture evaluation
- Performance analysis
- Security audit
- Edge case exploration
## Communication Guidelines
### Tone and Language
**Use collaborative language:**
- "What do you think about..." instead of "You should..."
- "Could we consider..." instead of "This is wrong"
- "I'm curious about..." instead of "Why didn't you..."
**Be specific and actionable:**
- Include code examples when suggesting changes
- Link to documentation or past discussions
- Explain the "why" behind suggestions
### Handling Disagreements
1. **Seek to understand**: Ask clarifying questions
2. **Acknowledge valid points**: Show you've considered their perspective
3. **Provide data**: Use benchmarks, docs, or examples
4. **Escalate if needed**: Involve senior dev or architect
5. **Know when to let go**: Not every hill is worth dying on
## Review Prioritization
### Must Fix (Blocking)
- Security vulnerabilities
- Data corruption risks
- Breaking changes without migration
- Critical performance issues
- Missing error handling for user-facing features
### Should Fix (Important)
- Test coverage gaps
- Moderate performance concerns
- Code duplication
- Unclear naming or structure
- Missing documentation for complex logic
### Nice to Have (Non-blocking)
- Style preferences beyond linting
- Minor optimizations
- Additional test cases
- Documentation improvements
## Anti-Patterns to Avoid
### Reviewer Anti-Patterns
- **Rubber stamping**: Approving without actually reviewing
- **Bike shedding**: Debating trivial details extensively
- **Scope creep**: "While you're at it, can you also..."
- **Ghosting**: Requesting changes then disappearing
- **Perfectionism**: Blocking for minor style preferences
### Author Anti-Patterns
- **Mega PRs**: Submitting 1000+ line changes
- **No context**: Missing PR description or linked issues
- **Defensive responses**: Arguing every suggestion
- **Silent updates**: Making changes without responding to comments
## Metrics and Improvement
### Track These Metrics
- Time to first review
- Review cycle time
- Number of review rounds
- Defect escape rate
- Review coverage percentage
### Continuous Improvement
- Hold retrospectives on review process
- Share learnings from escaped bugs
- Update checklists based on common issues
- Celebrate good reviews and catches

File diff suppressed because it is too large Load Diff

77
reference/python.md Normal file
View File

@@ -0,0 +1,77 @@
# Python Code Review Guide
> Python 代码审查指南,覆盖常见陷阱和最佳实践。
---
## 常见陷阱
### 可变默认参数
```python
# ❌ Mutable default arguments
def add_item(item, items=[]): # Bug! Shared across calls
items.append(item)
return items
# ✅ Use None as default
def add_item(item, items=None):
if items is None:
items = []
items.append(item)
return items
```
### 异常捕获过宽
```python
# ❌ Catching too broad
try:
result = risky_operation()
except: # Catches everything, even KeyboardInterrupt!
pass
# ✅ Catch specific exceptions
try:
result = risky_operation()
except ValueError as e:
logger.error(f"Invalid value: {e}")
raise
```
### 可变类属性
```python
# ❌ Using mutable class attributes
class User:
permissions = [] # Shared across all instances!
# ✅ Initialize in __init__
class User:
def __init__(self):
self.permissions = []
```
---
## Python Review Checklist
### 数据结构
- [ ] 没有使用可变默认参数list、dict、set
- [ ] 类属性不是可变对象
- [ ] 理解浅拷贝和深拷贝的区别
### 异常处理
- [ ] 捕获特定异常类型,不使用裸 `except:`
- [ ] 异常信息有意义,便于调试
- [ ] 必要时重新抛出异常(`raise`
### 性能
- [ ] 大数据集使用生成器而非列表
- [ ] 避免循环中重复创建对象
- [ ] 使用 `collections` 模块的高效数据结构
### 代码风格
- [ ] 遵循 PEP 8 风格指南
- [ ] 使用类型注解type hints
- [ ] 函数和类有 docstring

799
reference/react.md Normal file
View File

@@ -0,0 +1,799 @@
# React Code Review Guide
React 审查重点Hooks 规则、性能优化的适度性、组件设计、以及现代 React 19/RSC 模式。
## 目录
- [基础 Hooks 规则](#基础-hooks-规则)
- [useEffect 模式](#useeffect-模式)
- [useMemo / useCallback](#usememo--usecallback)
- [组件设计](#组件设计)
- [Error Boundaries & Suspense](#error-boundaries--suspense)
- [Server Components (RSC)](#server-components-rsc)
- [React 19 Actions & Forms](#react-19-actions--forms)
- [Suspense & Streaming SSR](#suspense--streaming-ssr)
- [TanStack Query v5](#tanstack-query-v5)
- [Review Checklists](#review-checklists)
---
## 基础 Hooks 规则
```tsx
// ❌ 条件调用 Hooks — 违反 Hooks 规则
function BadComponent({ isLoggedIn }) {
if (isLoggedIn) {
const [user, setUser] = useState(null); // Error!
}
return <div>...</div>;
}
// ✅ Hooks 必须在组件顶层调用
function GoodComponent({ isLoggedIn }) {
const [user, setUser] = useState(null);
if (!isLoggedIn) return <LoginPrompt />;
return <div>{user?.name}</div>;
}
```
---
## useEffect 模式
```tsx
// ❌ 依赖数组缺失或不完整
function BadEffect({ userId }) {
const [user, setUser] = useState(null);
useEffect(() => {
fetchUser(userId).then(setUser);
}, []); // 缺少 userId 依赖!
}
// ✅ 完整的依赖数组
function GoodEffect({ userId }) {
const [user, setUser] = useState(null);
useEffect(() => {
let cancelled = false;
fetchUser(userId).then(data => {
if (!cancelled) setUser(data);
});
return () => { cancelled = true; }; // 清理函数
}, [userId]);
}
// ❌ useEffect 用于派生状态(反模式)
function BadDerived({ items }) {
const [filteredItems, setFilteredItems] = useState([]);
useEffect(() => {
setFilteredItems(items.filter(i => i.active));
}, [items]); // 不必要的 effect + 额外渲染
return <List items={filteredItems} />;
}
// ✅ 直接在渲染时计算,或用 useMemo
function GoodDerived({ items }) {
const filteredItems = useMemo(
() => items.filter(i => i.active),
[items]
);
return <List items={filteredItems} />;
}
// ❌ useEffect 用于事件响应
function BadEventEffect() {
const [query, setQuery] = useState('');
useEffect(() => {
if (query) {
analytics.track('search', { query }); // 应该在事件处理器中
}
}, [query]);
}
// ✅ 在事件处理器中执行副作用
function GoodEvent() {
const [query, setQuery] = useState('');
const handleSearch = (q: string) => {
setQuery(q);
analytics.track('search', { query: q });
};
}
```
---
## useMemo / useCallback
```tsx
// ❌ 过度优化 — 常量不需要 useMemo
function OverOptimized() {
const config = useMemo(() => ({ timeout: 5000 }), []); // 无意义
const handleClick = useCallback(() => {
console.log('clicked');
}, []); // 如果不传给 memo 组件,无意义
}
// ✅ 只在需要时优化
function ProperlyOptimized() {
const config = { timeout: 5000 }; // 简单对象直接定义
const handleClick = () => console.log('clicked');
}
// ❌ useCallback 依赖总是变化
function BadCallback({ data }) {
// data 每次渲染都是新对象useCallback 无效
const process = useCallback(() => {
return data.map(transform);
}, [data]);
}
// ✅ useMemo + useCallback 配合 React.memo 使用
const MemoizedChild = React.memo(function Child({ onClick, items }) {
return <div onClick={onClick}>{items.length}</div>;
});
function Parent({ rawItems }) {
const items = useMemo(() => processItems(rawItems), [rawItems]);
const handleClick = useCallback(() => {
console.log(items.length);
}, [items]);
return <MemoizedChild onClick={handleClick} items={items} />;
}
```
---
## 组件设计
```tsx
// ❌ 在组件内定义组件 — 每次渲染都创建新组件
function BadParent() {
function ChildComponent() { // 每次渲染都是新函数!
return <div>child</div>;
}
return <ChildComponent />;
}
// ✅ 组件定义在外部
function ChildComponent() {
return <div>child</div>;
}
function GoodParent() {
return <ChildComponent />;
}
// ❌ Props 总是新对象引用
function BadProps() {
return (
<MemoizedComponent
style={{ color: 'red' }} // 每次渲染新对象
onClick={() => {}} // 每次渲染新函数
/>
);
}
// ✅ 稳定的引用
const style = { color: 'red' };
function GoodProps() {
const handleClick = useCallback(() => {}, []);
return <MemoizedComponent style={style} onClick={handleClick} />;
}
```
---
## Error Boundaries & Suspense
```tsx
// ❌ 没有错误边界
function BadApp() {
return (
<Suspense fallback={<Loading />}>
<DataComponent /> {/* 错误会导致整个应用崩溃 */}
</Suspense>
);
}
// ✅ Error Boundary 包裹 Suspense
function GoodApp() {
return (
<ErrorBoundary fallback={<ErrorUI />}>
<Suspense fallback={<Loading />}>
<DataComponent />
</Suspense>
</ErrorBoundary>
);
}
```
---
## Server Components (RSC)
```tsx
// ❌ 在 Server Component 中使用客户端特性
// app/page.tsx (Server Component by default)
function BadServerComponent() {
const [count, setCount] = useState(0); // Error! No hooks in RSC
return <button onClick={() => setCount(c => c + 1)}>{count}</button>;
}
// ✅ 交互逻辑提取到 Client Component
// app/counter.tsx
'use client';
function Counter() {
const [count, setCount] = useState(0);
return <button onClick={() => setCount(c => c + 1)}>{count}</button>;
}
// app/page.tsx (Server Component)
function GoodServerComponent() {
const data = await fetchData(); // 可以直接 await
return (
<div>
<h1>{data.title}</h1>
<Counter /> {/* 客户端组件 */}
</div>
);
}
// ❌ 'use client' 放置不当 — 整个树都变成客户端
// layout.tsx
'use client'; // 这会让所有子组件都成为客户端组件
export default function Layout({ children }) { ... }
// ✅ 只在需要交互的组件使用 'use client'
// 将客户端逻辑隔离到叶子组件
```
---
## React 19 Actions & Forms
React 19 引入了 Actions 系统和新的表单处理 Hooks简化异步操作和乐观更新。
### useActionState
```tsx
// ❌ 传统方式:多个状态变量
function OldForm() {
const [isPending, setIsPending] = useState(false);
const [error, setError] = useState<string | null>(null);
const [data, setData] = useState(null);
const handleSubmit = async (formData: FormData) => {
setIsPending(true);
setError(null);
try {
const result = await submitForm(formData);
setData(result);
} catch (e) {
setError(e.message);
} finally {
setIsPending(false);
}
};
}
// ✅ React 19: useActionState 统一管理
import { useActionState } from 'react';
function NewForm() {
const [state, formAction, isPending] = useActionState(
async (prevState, formData: FormData) => {
try {
const result = await submitForm(formData);
return { success: true, data: result };
} catch (e) {
return { success: false, error: e.message };
}
},
{ success: false, data: null, error: null }
);
return (
<form action={formAction}>
<input name="email" />
<button disabled={isPending}>
{isPending ? 'Submitting...' : 'Submit'}
</button>
{state.error && <p className="error">{state.error}</p>}
</form>
);
}
```
### useFormStatus
```tsx
// ❌ Props 透传表单状态
function BadSubmitButton({ isSubmitting }) {
return <button disabled={isSubmitting}>Submit</button>;
}
// ✅ useFormStatus 访问父 <form> 状态(无需 props
import { useFormStatus } from 'react-dom';
function SubmitButton() {
const { pending, data, method, action } = useFormStatus();
// 注意:必须在 <form> 内部的子组件中使用
return (
<button disabled={pending}>
{pending ? 'Submitting...' : 'Submit'}
</button>
);
}
// ❌ useFormStatus 在 form 同级组件中调用——不工作
function BadForm() {
const { pending } = useFormStatus(); // 这里无法获取状态!
return (
<form action={action}>
<button disabled={pending}>Submit</button>
</form>
);
}
// ✅ useFormStatus 必须在 form 的子组件中
function GoodForm() {
return (
<form action={action}>
<SubmitButton /> {/* useFormStatus 在这里面调用 */}
</form>
);
}
```
### useOptimistic
```tsx
// ❌ 等待服务器响应再更新 UI
function SlowLike({ postId, likes }) {
const [likeCount, setLikeCount] = useState(likes);
const [isPending, setIsPending] = useState(false);
const handleLike = async () => {
setIsPending(true);
const newCount = await likePost(postId); // 等待...
setLikeCount(newCount);
setIsPending(false);
};
}
// ✅ useOptimistic 即时反馈,失败自动回滚
import { useOptimistic } from 'react';
function FastLike({ postId, likes }) {
const [optimisticLikes, addOptimisticLike] = useOptimistic(
likes,
(currentLikes, increment: number) => currentLikes + increment
);
const handleLike = async () => {
addOptimisticLike(1); // 立即更新 UI
try {
await likePost(postId); // 后台同步
} catch {
// React 自动回滚到 likes 原值
}
};
return <button onClick={handleLike}>{optimisticLikes} likes</button>;
}
```
### Server Actions (Next.js 15+)
```tsx
// ❌ 客户端调用 API
'use client';
function ClientForm() {
const handleSubmit = async (formData: FormData) => {
const res = await fetch('/api/submit', {
method: 'POST',
body: formData,
});
// ...
};
}
// ✅ Server Action + useActionState
// actions.ts
'use server';
export async function createPost(prevState: any, formData: FormData) {
const title = formData.get('title');
await db.posts.create({ title });
revalidatePath('/posts');
return { success: true };
}
// form.tsx
'use client';
import { createPost } from './actions';
function PostForm() {
const [state, formAction, isPending] = useActionState(createPost, null);
return (
<form action={formAction}>
<input name="title" />
<SubmitButton />
</form>
);
}
```
---
## Suspense & Streaming SSR
Suspense 和 Streaming 是 React 18+ 的核心特性,在 2025 年的 Next.js 15 等框架中广泛使用。
### 基础 Suspense
```tsx
// ❌ 传统加载状态管理
function OldComponent() {
const [data, setData] = useState(null);
const [isLoading, setIsLoading] = useState(true);
useEffect(() => {
fetchData().then(setData).finally(() => setIsLoading(false));
}, []);
if (isLoading) return <Spinner />;
return <DataView data={data} />;
}
// ✅ Suspense 声明式加载状态
function NewComponent() {
return (
<Suspense fallback={<Spinner />}>
<DataView /> {/* 内部使用 use() 或支持 Suspense 的数据获取 */}
</Suspense>
);
}
```
### 多个独立 Suspense 边界
```tsx
// ❌ 单一边界——所有内容一起加载
function BadLayout() {
return (
<Suspense fallback={<FullPageSpinner />}>
<Header />
<MainContent /> {/* 慢 */}
<Sidebar /> {/* 快 */}
</Suspense>
);
}
// ✅ 独立边界——各部分独立流式传输
function GoodLayout() {
return (
<>
<Header /> {/* 立即显示 */}
<div className="flex">
<Suspense fallback={<ContentSkeleton />}>
<MainContent /> {/* 独立加载 */}
</Suspense>
<Suspense fallback={<SidebarSkeleton />}>
<Sidebar /> {/* 独立加载 */}
</Suspense>
</div>
</>
);
}
```
### Next.js 15 Streaming
```tsx
// app/page.tsx - 自动 Streaming
export default async function Page() {
// 这个 await 不会阻塞整个页面
const data = await fetchSlowData();
return <div>{data}</div>;
}
// app/loading.tsx - 自动 Suspense 边界
export default function Loading() {
return <Skeleton />;
}
```
### use() Hook (React 19)
```tsx
// ✅ 在组件中读取 Promise
import { use } from 'react';
function Comments({ commentsPromise }) {
const comments = use(commentsPromise); // 自动触发 Suspense
return (
<ul>
{comments.map(c => <li key={c.id}>{c.text}</li>)}
</ul>
);
}
// 父组件创建 Promise子组件消费
function Post({ postId }) {
const commentsPromise = fetchComments(postId); // 不 await
return (
<article>
<PostContent id={postId} />
<Suspense fallback={<CommentsSkeleton />}>
<Comments commentsPromise={commentsPromise} />
</Suspense>
</article>
);
}
```
---
## TanStack Query v5
TanStack Query 是 React 生态中最流行的数据获取库v5 是当前稳定版本。
### 基础配置
```tsx
// ❌ 不正确的默认配置
const queryClient = new QueryClient(); // 默认配置可能不适合
// ✅ 生产环境推荐配置
const queryClient = new QueryClient({
defaultOptions: {
queries: {
staleTime: 1000 * 60 * 5, // 5 分钟内数据视为新鲜
gcTime: 1000 * 60 * 30, // 30 分钟后垃圾回收v5 重命名)
retry: 3,
refetchOnWindowFocus: false, // 根据需求决定
},
},
});
```
### queryOptions (v5 新增)
```tsx
// ❌ 重复定义 queryKey 和 queryFn
function Component1() {
const { data } = useQuery({
queryKey: ['users', userId],
queryFn: () => fetchUser(userId),
});
}
function prefetchUser(queryClient, userId) {
queryClient.prefetchQuery({
queryKey: ['users', userId], // 重复!
queryFn: () => fetchUser(userId), // 重复!
});
}
// ✅ queryOptions 统一定义,类型安全
import { queryOptions } from '@tanstack/react-query';
const userQueryOptions = (userId: string) =>
queryOptions({
queryKey: ['users', userId],
queryFn: () => fetchUser(userId),
});
function Component1({ userId }) {
const { data } = useQuery(userQueryOptions(userId));
}
function prefetchUser(queryClient, userId) {
queryClient.prefetchQuery(userQueryOptions(userId));
}
// getQueryData 也是类型安全的
const user = queryClient.getQueryData(userQueryOptions(userId).queryKey);
```
### 常见陷阱
```tsx
// ❌ staleTime 为 0 导致过度请求
useQuery({
queryKey: ['data'],
queryFn: fetchData,
// staleTime 默认为 0每次组件挂载都会 refetch
});
// ✅ 设置合理的 staleTime
useQuery({
queryKey: ['data'],
queryFn: fetchData,
staleTime: 1000 * 60, // 1 分钟内不会重新请求
});
// ❌ 在 queryFn 中使用不稳定的引用
function BadQuery({ filters }) {
useQuery({
queryKey: ['items'], // queryKey 没有包含 filters
queryFn: () => fetchItems(filters), // filters 变化不会触发重新请求
});
}
// ✅ queryKey 包含所有影响数据的参数
function GoodQuery({ filters }) {
useQuery({
queryKey: ['items', filters], // filters 是 queryKey 的一部分
queryFn: () => fetchItems(filters),
});
}
```
### useSuspenseQuery
```tsx
// ❌ 使用 useQuery + enabled 实现条件查询
function BadSuspenseQuery({ userId }) {
const { data } = useSuspenseQuery({
queryKey: ['user', userId],
queryFn: () => fetchUser(userId),
enabled: !!userId, // useSuspenseQuery 不支持 enabled
});
}
// ✅ 组件组合实现条件渲染
function GoodSuspenseQuery({ userId }) {
// useSuspenseQuery 保证 data 是 T 不是 T | undefined
const { data } = useSuspenseQuery({
queryKey: ['user', userId],
queryFn: () => fetchUser(userId),
});
return <UserProfile user={data} />;
}
function Parent({ userId }) {
if (!userId) return <NoUserSelected />;
return (
<Suspense fallback={<UserSkeleton />}>
<GoodSuspenseQuery userId={userId} />
</Suspense>
);
}
```
### 乐观更新 (v5 简化)
```tsx
// ❌ 手动管理缓存的乐观更新(复杂)
const mutation = useMutation({
mutationFn: updateTodo,
onMutate: async (newTodo) => {
await queryClient.cancelQueries({ queryKey: ['todos'] });
const previousTodos = queryClient.getQueryData(['todos']);
queryClient.setQueryData(['todos'], (old) => [...old, newTodo]);
return { previousTodos };
},
onError: (err, newTodo, context) => {
queryClient.setQueryData(['todos'], context.previousTodos);
},
onSettled: () => {
queryClient.invalidateQueries({ queryKey: ['todos'] });
},
});
// ✅ v5 简化:使用 variables 进行乐观 UI
function TodoList() {
const { data: todos } = useQuery(todosQueryOptions);
const { mutate, variables, isPending } = useMutation({
mutationFn: addTodo,
onSuccess: () => {
queryClient.invalidateQueries({ queryKey: ['todos'] });
},
});
return (
<ul>
{todos?.map(todo => <TodoItem key={todo.id} todo={todo} />)}
{/* 乐观显示正在添加的 todo */}
{isPending && <TodoItem todo={variables} isOptimistic />}
</ul>
);
}
```
### v5 状态字段变化
```tsx
// v4: isLoading 表示首次加载或后续获取
// v5: isPending 表示没有数据isLoading = isPending && isFetching
const { data, isPending, isFetching, isLoading } = useQuery({...});
// isPending: 缓存中没有数据(首次加载)
// isFetching: 正在请求中(包括后台刷新)
// isLoading: isPending && isFetching首次加载中
// ❌ v4 代码直接迁移
if (isLoading) return <Spinner />; // v5 中行为可能不同
// ✅ 明确意图
if (isPending) return <Spinner />; // 没有数据时显示加载
// 或
if (isLoading) return <Spinner />; // 首次加载中
```
---
## Review Checklists
### Hooks 规则
- [ ] Hooks 在组件/自定义 Hook 顶层调用
- [ ] 没有条件/循环中调用 Hooks
- [ ] useEffect 依赖数组完整
- [ ] useEffect 有清理函数(订阅/定时器/请求)
- [ ] 没有用 useEffect 计算派生状态
### 性能优化(适度原则)
- [ ] useMemo/useCallback 只用于真正需要的场景
- [ ] React.memo 配合稳定的 props 引用
- [ ] 没有在组件内定义子组件
- [ ] 没有在 JSX 中创建新对象/函数(除非传给非 memo 组件)
- [ ] 长列表使用虚拟化react-window/react-virtual
### 组件设计
- [ ] 组件职责单一,不超过 200 行
- [ ] 逻辑与展示分离Custom Hooks
- [ ] Props 接口清晰,使用 TypeScript
- [ ] 避免 Props Drilling考虑 Context 或组合)
### 状态管理
- [ ] 状态就近原则(最小必要范围)
- [ ] 复杂状态用 useReducer
- [ ] 全局状态用 Context 或状态库
- [ ] 避免不必要的状态(派生 > 存储)
### 错误处理
- [ ] 关键区域有 Error Boundary
- [ ] Suspense 配合 Error Boundary 使用
- [ ] 异步操作有错误处理
### Server Components (RSC)
- [ ] 'use client' 只用于需要交互的组件
- [ ] Server Component 不使用 Hooks/事件处理
- [ ] 客户端组件尽量放在叶子节点
- [ ] 数据获取在 Server Component 中进行
### React 19 Forms
- [ ] 使用 useActionState 替代多个 useState
- [ ] useFormStatus 在 form 子组件中调用
- [ ] useOptimistic 不用于关键业务(支付等)
- [ ] Server Action 正确标记 'use server'
### Suspense & Streaming
- [ ] 按用户体验需求划分 Suspense 边界
- [ ] 每个 Suspense 有对应的 Error Boundary
- [ ] 提供有意义的 fallback骨架屏 > Spinner
- [ ] 避免在 layout 层级 await 慢数据
### TanStack Query
- [ ] queryKey 包含所有影响数据的参数
- [ ] 设置合理的 staleTime不是默认 0
- [ ] useSuspenseQuery 不使用 enabled
- [ ] Mutation 成功后 invalidate 相关查询
- [ ] 理解 isPending vs isLoading 区别
### 测试
- [ ] 使用 @testing-library/react
- [ ] 用 screen 查询元素
- [ ] 用 userEvent 代替 fireEvent
- [ ] 优先使用 *ByRole 查询
- [ ] 测试行为而非实现细节

256
reference/rust.md Normal file
View File

@@ -0,0 +1,256 @@
# Rust Code Review Guide
> Rust 代码审查指南。编译器能捕获内存安全问题但审查者需要关注编译器无法检测的问题——业务逻辑、API 设计、性能和可维护性。
---
## 所有权与借用
### 避免不必要的 clone()
```rust
// ❌ clone() 是"Rust 的胶带"——用于绕过借用检查器
fn bad_process(data: &Data) -> Result<()> {
let owned = data.clone(); // 为什么需要 clone
expensive_operation(owned)
}
// ✅ 审查时问clone 是否必要?能否用借用?
fn good_process(data: &Data) -> Result<()> {
expensive_operation(data) // 传递引用
}
```
### Arc<Mutex<T>> 的使用
```rust
// ❌ Arc<Mutex<T>> 可能隐藏不必要的共享状态
struct BadService {
cache: Arc<Mutex<HashMap<String, Data>>>, // 真的需要共享?
}
// ✅ 考虑是否需要共享,或者设计可以避免
struct GoodService {
cache: HashMap<String, Data>, // 单一所有者
}
```
---
## Unsafe 代码审查(最关键!)
```rust
// ❌ unsafe 没有安全文档——这是红旗
unsafe fn bad_transmute<T, U>(t: T) -> U {
std::mem::transmute(t)
}
// ✅ 每个 unsafe 必须解释:为什么安全?什么不变量?
/// Transmutes `T` to `U`.
///
/// # Safety
///
/// - `T` and `U` must have the same size and alignment
/// - `T` must be a valid bit pattern for `U`
/// - The caller ensures no references to `t` exist after this call
unsafe fn documented_transmute<T, U>(t: T) -> U {
// SAFETY: Caller guarantees size/alignment match and bit validity
std::mem::transmute(t)
}
```
---
## 异步代码
### 避免阻塞操作
```rust
// ❌ 在 async 上下文中阻塞——会饿死其他任务
async fn bad_async() {
let data = std::fs::read_to_string("file.txt").unwrap(); // 阻塞!
std::thread::sleep(Duration::from_secs(1)); // 阻塞!
}
// ✅ 使用异步 API
async fn good_async() {
let data = tokio::fs::read_to_string("file.txt").await?;
tokio::time::sleep(Duration::from_secs(1)).await;
}
```
### Mutex 和 .await
```rust
// ❌ 跨 .await 持有 std::sync::Mutex——可能死锁
async fn bad_lock(mutex: &std::sync::Mutex<Data>) {
let guard = mutex.lock().unwrap();
async_operation().await; // 持锁等待!
process(&guard);
}
// ✅ 最小化锁范围,或使用 tokio::sync::Mutex
async fn good_lock(mutex: &std::sync::Mutex<Data>) {
let data = mutex.lock().unwrap().clone(); // 立即释放
async_operation().await;
process(&data);
}
```
---
## 错误处理
### 库 vs 应用的错误类型
```rust
// ❌ 库代码用 anyhow——调用者无法 match 错误
pub fn parse_config(s: &str) -> anyhow::Result<Config> { ... }
// ✅ 库用 thiserror应用用 anyhow
#[derive(Debug, thiserror::Error)]
pub enum ConfigError {
#[error("invalid syntax at line {line}: {message}")]
Syntax { line: usize, message: String },
#[error("missing required field: {0}")]
MissingField(String),
#[error(transparent)]
Io(#[from] std::io::Error),
}
pub fn parse_config(s: &str) -> Result<Config, ConfigError> { ... }
```
### 保留错误上下文
```rust
// ❌ 吞掉错误上下文
fn bad_error() -> Result<()> {
operation().map_err(|_| anyhow!("failed"))?; // 原始错误丢失
Ok(())
}
// ✅ 使用 context 保留错误链
fn good_error() -> Result<()> {
operation().context("failed to perform operation")?;
Ok(())
}
```
---
## 性能
### 避免不必要的 collect()
```rust
// ❌ 不必要的 collect——中间分配
fn bad_sum(items: &[i32]) -> i32 {
items.iter()
.filter(|x| **x > 0)
.collect::<Vec<_>>() // 不必要!
.iter()
.sum()
}
// ✅ 惰性迭代
fn good_sum(items: &[i32]) -> i32 {
items.iter().filter(|x| **x > 0).sum()
}
```
### 字符串拼接
```rust
// ❌ 字符串拼接在循环中重复分配
fn bad_concat(items: &[&str]) -> String {
let mut s = String::new();
for item in items {
s = s + item; // 每次都重新分配!
}
s
}
// ✅ 预分配或用 join
fn good_concat(items: &[&str]) -> String {
items.join("") // 或用 with_capacity
}
```
---
## Trait 设计
```rust
// ❌ 过度抽象——不是 Java不需要 Interface 一切
trait Processor { fn process(&self); }
trait Handler { fn handle(&self); }
trait Manager { fn manage(&self); } // Trait 过多
// ✅ 只在需要多态时创建 trait
// 具体类型通常更简单、更快
```
---
## Rust Review Checklist
### 编译器不能捕获的问题
**业务逻辑正确性**
- [ ] 边界条件处理正确
- [ ] 状态机转换完整
- [ ] 并发场景下的竞态条件
**API 设计**
- [ ] 公共 API 难以误用
- [ ] 类型签名清晰表达意图
- [ ] 错误类型粒度合适
### 所有权与借用
- [ ] clone() 是有意为之,文档说明了原因
- [ ] Arc<Mutex<T>> 真的需要共享状态吗?
- [ ] RefCell 的使用有正当理由
- [ ] 生命周期不过度复杂
### Unsafe 代码(最重要)
- [ ] 每个 unsafe 块有 SAFETY 注释
- [ ] unsafe fn 有 # Safety 文档节
- [ ] 解释了为什么是安全的,不只是做什么
- [ ] 列出了必须维护的不变量
- [ ] unsafe 边界尽可能小
- [ ] 考虑过是否有 safe 替代方案
### 异步/并发
- [ ] 没有在 async 中阻塞std::fs、thread::sleep
- [ ] 没有跨 .await 持有 std::sync 锁
- [ ] spawn 的任务满足 'static
- [ ] 锁的获取顺序一致
- [ ] Channel 缓冲区大小合理
### 错误处理
- [ ]thiserror 定义结构化错误
- [ ] 应用anyhow + context
- [ ] 没有生产代码 unwrap/expect
- [ ] 错误消息对调试有帮助
- [ ] must_use 返回值被处理
### 性能
- [ ] 避免不必要的 collect()
- [ ] 大数据传引用
- [ ] 字符串用 with_capacity 或 write!
- [ ] impl Trait vs Box<dyn Trait> 选择合理
- [ ] 热路径避免分配
### 代码质量
- [ ] cargo clippy 零警告
- [ ] cargo fmt 格式化
- [ ] 文档注释完整
- [ ] 测试覆盖边界条件
- [ ] 公共 API 有文档示例

View File

@@ -0,0 +1,265 @@
# Security Review Guide
Security-focused code review checklist based on OWASP Top 10 and best practices.
## Authentication & Authorization
### Authentication
- [ ] Passwords hashed with strong algorithm (bcrypt, argon2)
- [ ] Password complexity requirements enforced
- [ ] Account lockout after failed attempts
- [ ] Secure password reset flow
- [ ] Multi-factor authentication for sensitive operations
- [ ] Session tokens are cryptographically random
- [ ] Session timeout implemented
### Authorization
- [ ] Authorization checks on every request
- [ ] Principle of least privilege applied
- [ ] Role-based access control (RBAC) properly implemented
- [ ] No privilege escalation paths
- [ ] Direct object reference checks (IDOR prevention)
- [ ] API endpoints protected appropriately
### JWT Security
```typescript
// ❌ Insecure JWT configuration
jwt.sign(payload, 'weak-secret');
// ✅ Secure JWT configuration
jwt.sign(payload, process.env.JWT_SECRET, {
algorithm: 'RS256',
expiresIn: '15m',
issuer: 'your-app',
audience: 'your-api'
});
// ❌ Not verifying JWT properly
const decoded = jwt.decode(token); // No signature verification!
// ✅ Verify signature and claims
const decoded = jwt.verify(token, publicKey, {
algorithms: ['RS256'],
issuer: 'your-app',
audience: 'your-api'
});
```
## Input Validation
### SQL Injection Prevention
```python
# ❌ Vulnerable to SQL injection
query = f"SELECT * FROM users WHERE id = {user_id}"
# ✅ Use parameterized queries
cursor.execute("SELECT * FROM users WHERE id = %s", (user_id,))
# ✅ Use ORM with proper escaping
User.objects.filter(id=user_id)
```
### XSS Prevention
```typescript
// ❌ Vulnerable to XSS
element.innerHTML = userInput;
// ✅ Use textContent for plain text
element.textContent = userInput;
// ✅ Use DOMPurify for HTML
element.innerHTML = DOMPurify.sanitize(userInput);
// ✅ React automatically escapes (but watch dangerouslySetInnerHTML)
return <div>{userInput}</div>; // Safe
return <div dangerouslySetInnerHTML={{__html: userInput}} />; // Dangerous!
```
### Command Injection Prevention
```python
# ❌ Vulnerable to command injection
os.system(f"convert {filename} output.png")
# ✅ Use subprocess with list arguments
subprocess.run(['convert', filename, 'output.png'], check=True)
# ✅ Validate and sanitize input
import shlex
safe_filename = shlex.quote(filename)
```
### Path Traversal Prevention
```typescript
// ❌ Vulnerable to path traversal
const filePath = `./uploads/${req.params.filename}`;
// ✅ Validate and sanitize path
const path = require('path');
const safeName = path.basename(req.params.filename);
const filePath = path.join('./uploads', safeName);
// Verify it's still within uploads directory
if (!filePath.startsWith(path.resolve('./uploads'))) {
throw new Error('Invalid path');
}
```
## Data Protection
### Sensitive Data Handling
- [ ] No secrets in source code
- [ ] Secrets stored in environment variables or secret manager
- [ ] Sensitive data encrypted at rest
- [ ] Sensitive data encrypted in transit (HTTPS)
- [ ] PII handled according to regulations (GDPR, etc.)
- [ ] Sensitive data not logged
- [ ] Secure data deletion when required
### Configuration Security
```yaml
# ❌ Secrets in config files
database:
password: "super-secret-password"
# ✅ Reference environment variables
database:
password: ${DATABASE_PASSWORD}
```
### Error Messages
```typescript
// ❌ Leaking sensitive information
catch (error) {
return res.status(500).json({
error: error.stack, // Exposes internal details
query: sqlQuery // Exposes database structure
});
}
// ✅ Generic error messages
catch (error) {
logger.error('Database error', { error, userId }); // Log internally
return res.status(500).json({
error: 'An unexpected error occurred'
});
}
```
## API Security
### Rate Limiting
- [ ] Rate limiting on all public endpoints
- [ ] Stricter limits on authentication endpoints
- [ ] Per-user and per-IP limits
- [ ] Graceful handling when limits exceeded
### CORS Configuration
```typescript
// ❌ Overly permissive CORS
app.use(cors({ origin: '*' }));
// ✅ Restrictive CORS
app.use(cors({
origin: ['https://your-app.com'],
methods: ['GET', 'POST'],
credentials: true
}));
```
### HTTP Headers
```typescript
// Security headers to set
app.use(helmet({
contentSecurityPolicy: {
directives: {
defaultSrc: ["'self'"],
scriptSrc: ["'self'"],
styleSrc: ["'self'", "'unsafe-inline'"],
}
},
hsts: { maxAge: 31536000, includeSubDomains: true },
noSniff: true,
xssFilter: true,
frameguard: { action: 'deny' }
}));
```
## Cryptography
### Secure Practices
- [ ] Using well-established algorithms (AES-256, RSA-2048+)
- [ ] Not implementing custom cryptography
- [ ] Using cryptographically secure random number generation
- [ ] Proper key management and rotation
- [ ] Secure key storage (HSM, KMS)
### Common Mistakes
```typescript
// ❌ Weak random generation
const token = Math.random().toString(36);
// ✅ Cryptographically secure random
const crypto = require('crypto');
const token = crypto.randomBytes(32).toString('hex');
// ❌ MD5/SHA1 for passwords
const hash = crypto.createHash('md5').update(password).digest('hex');
// ✅ Use bcrypt or argon2
const bcrypt = require('bcrypt');
const hash = await bcrypt.hash(password, 12);
```
## Dependency Security
### Checklist
- [ ] Dependencies from trusted sources only
- [ ] No known vulnerabilities (npm audit, cargo audit)
- [ ] Dependencies kept up to date
- [ ] Lock files committed (package-lock.json, Cargo.lock)
- [ ] Minimal dependency usage
- [ ] License compliance verified
### Audit Commands
```bash
# Node.js
npm audit
npm audit fix
# Python
pip-audit
safety check
# Rust
cargo audit
# General
snyk test
```
## Logging & Monitoring
### Secure Logging
- [ ] No sensitive data in logs (passwords, tokens, PII)
- [ ] Logs protected from tampering
- [ ] Appropriate log retention
- [ ] Security events logged (login attempts, permission changes)
- [ ] Log injection prevented
```typescript
// ❌ Logging sensitive data
logger.info(`User login: ${email}, password: ${password}`);
// ✅ Safe logging
logger.info('User login attempt', { email, success: true });
```
## Security Review Severity Levels
| Severity | Description | Action |
|----------|-------------|--------|
| **Critical** | Immediate exploitation possible, data breach risk | Block merge, fix immediately |
| **High** | Significant vulnerability, requires specific conditions | Block merge, fix before release |
| **Medium** | Moderate risk, defense in depth concern | Should fix, can merge with tracking |
| **Low** | Minor issue, best practice violation | Nice to fix, non-blocking |
| **Info** | Suggestion for improvement | Optional enhancement |

101
reference/typescript.md Normal file
View File

@@ -0,0 +1,101 @@
# TypeScript/JavaScript Code Review Guide
> TypeScript/JavaScript 通用代码审查指南,覆盖类型安全、异步处理、常见陷阱。
---
## 类型安全
### 避免使用 any
```typescript
// ❌ Using any defeats type safety
function processData(data: any) { // Avoid any
return data.value;
}
// ✅ Use proper types
interface DataPayload {
value: string;
}
function processData(data: DataPayload) {
return data.value;
}
```
---
## 异步处理
### 错误处理
```typescript
// ❌ Not handling async errors
async function fetchUser(id: string) {
const response = await fetch(`/api/users/${id}`);
return response.json(); // What if network fails?
}
// ✅ Handle errors properly
async function fetchUser(id: string): Promise<User> {
try {
const response = await fetch(`/api/users/${id}`);
if (!response.ok) {
throw new Error(`HTTP ${response.status}`);
}
return await response.json();
} catch (error) {
console.error('Failed to fetch user:', error);
throw error;
}
}
```
---
## 不可变性
### 避免直接修改 Props/参数
```typescript
// ❌ Mutation of props
function UserProfile({ user }: Props) {
user.lastViewed = new Date(); // Mutating prop!
return <div>{user.name}</div>;
}
// ✅ Don't mutate props
function UserProfile({ user, onView }: Props) {
useEffect(() => {
onView(user.id); // Notify parent to update
}, [user.id]);
return <div>{user.name}</div>;
}
```
---
## TypeScript Review Checklist
### 类型系统
- [ ] 没有使用 `any`(使用 `unknown` 代替未知类型)
- [ ] 接口和类型定义完整
- [ ] 使用泛型提高代码复用性
- [ ] 联合类型有正确的类型收窄
### 异步代码
- [ ] async 函数有错误处理
- [ ] Promise rejection 被正确处理
- [ ] 避免 callback hell使用 async/await
- [ ] 并发请求使用 `Promise.all``Promise.allSettled`
### 不可变性
- [ ] 不直接修改函数参数
- [ ] 使用 spread 操作符创建新对象/数组
- [ ] 考虑使用 `readonly` 修饰符
### 代码质量
- [ ] 启用 strict 模式
- [ ] ESLint/TSLint 无警告
- [ ] 函数有返回类型注解
- [ ] 避免类型断言(`as`),除非确实必要

240
reference/vue.md Normal file
View File

@@ -0,0 +1,240 @@
# Vue 3 Code Review Guide
> Vue 3 Composition API 代码审查指南覆盖响应性系统、Props/Emits、Watchers、Composables 等核心主题。
---
## 响应性系统
### 解构 reactive 对象
```vue
<!-- 解构 reactive 会丢失响应性 -->
<script setup lang="ts">
const state = reactive({ count: 0, name: 'Vue' })
const { count, name } = state // 丢失响应性!
</script>
<!-- 使用 toRefs 保持响应性 -->
<script setup lang="ts">
const state = reactive({ count: 0, name: 'Vue' })
const { count, name } = toRefs(state) // 保持响应性
// 或者直接使用 ref
const count = ref(0)
const name = ref('Vue')
</script>
```
### computed 副作用
```vue
<!-- computed 中产生副作用 -->
<script setup lang="ts">
const fullName = computed(() => {
console.log('Computing...') // 副作用!
otherRef.value = 'changed' // 修改其他状态!
return `${firstName.value} ${lastName.value}`
})
</script>
<!-- computed 只用于派生状态 -->
<script setup lang="ts">
const fullName = computed(() => {
return `${firstName.value} ${lastName.value}`
})
// 副作用放在 watch 或事件处理中
watch(fullName, (name) => {
console.log('Name changed:', name)
})
</script>
```
---
## Props & Emits
### 直接修改 props
```vue
<!-- 直接修改 props -->
<script setup lang="ts">
const props = defineProps<{ user: User }>()
props.user.name = 'New Name' // 永远不要直接修改 props
</script>
<!-- 使用 emit 通知父组件更新 -->
<script setup lang="ts">
const props = defineProps<{ user: User }>()
const emit = defineEmits<{
update: [name: string]
}>()
const updateName = (name: string) => emit('update', name)
</script>
```
### defineProps 类型声明
```vue
<!-- defineProps 缺少类型声明 -->
<script setup lang="ts">
const props = defineProps(['title', 'count']) // 无类型检查
</script>
<!-- 使用类型声明 + withDefaults -->
<script setup lang="ts">
interface Props {
title: string
count?: number
items?: string[]
}
const props = withDefaults(defineProps<Props>(), {
count: 0,
items: () => [] // 对象/数组默认值需要工厂函数
})
</script>
```
---
## Watchers
### watch 清理函数
```vue
<!-- watch 缺少清理函数可能内存泄漏 -->
<script setup lang="ts">
watch(searchQuery, async (query) => {
const controller = new AbortController()
const data = await fetch(`/api/search?q=${query}`, {
signal: controller.signal
})
results.value = await data.json()
// 如果 query 快速变化,旧请求不会被取消!
})
</script>
<!-- 使用 onCleanup 清理副作用 -->
<script setup lang="ts">
watch(searchQuery, async (query, _, onCleanup) => {
const controller = new AbortController()
onCleanup(() => controller.abort()) // 取消旧请求
try {
const data = await fetch(`/api/search?q=${query}`, {
signal: controller.signal
})
results.value = await data.json()
} catch (e) {
if (e.name !== 'AbortError') throw e
}
})
</script>
```
---
## 模板最佳实践
### v-for 的 key
```vue
<!-- v-for 中使用 index 作为 key -->
<template>
<li v-for="(item, index) in items" :key="index">
{{ item.name }}
</li>
</template>
<!-- 使用唯一标识作为 key -->
<template>
<li v-for="item in items" :key="item.id">
{{ item.name }}
</li>
</template>
```
### v-if 和 v-for 优先级
```vue
<!-- v-if v-for 同时使用 -->
<template>
<li v-for="user in users" v-if="user.active" :key="user.id">
{{ user.name }}
</li>
</template>
<!-- 使用 computed 过滤 -->
<script setup lang="ts">
const activeUsers = computed(() =>
users.value.filter(user => user.active)
)
</script>
<template>
<li v-for="user in activeUsers" :key="user.id">
{{ user.name }}
</li>
</template>
```
---
## Composables
### Props 传递给 composable
```vue
<!-- 传递 props composable 丢失响应性 -->
<script setup lang="ts">
const props = defineProps<{ userId: string }>()
const { user } = useUser(props.userId) // 丢失响应性!
</script>
<!-- 使用 toRef computed 保持响应性 -->
<script setup lang="ts">
const props = defineProps<{ userId: string }>()
const userIdRef = toRef(props, 'userId')
const { user } = useUser(userIdRef) // 保持响应性
// 或使用 computed
const { user } = useUser(computed(() => props.userId))
</script>
```
---
## Vue 3 Review Checklist
### 响应性系统
- [ ] ref 用于基本类型reactive 用于对象
- [ ] 没有解构 reactive 对象(或使用了 toRefs
- [ ] props 传递给 composable 时保持了响应性
- [ ] shallowRef/shallowReactive 用于大型对象优化
### Props & Emits
- [ ] defineProps 使用 TypeScript 类型声明
- [ ] 复杂默认值使用 withDefaults + 工厂函数
- [ ] defineEmits 有完整的类型定义
- [ ] 没有直接修改 props
### Watchers
- [ ] watch/watchEffect 有适当的清理函数
- [ ] 异步 watch 处理了竞态条件
- [ ] flush: 'post' 用于 DOM 操作的 watcher
- [ ] 避免过度使用 watcher优先用 computed
### 模板
- [ ] v-for 使用唯一且稳定的 key
- [ ] v-if 和 v-for 没有在同一元素上
- [ ] 事件处理使用 kebab-case
- [ ] 大型列表使用虚拟滚动
### Composables
- [ ] 相关逻辑提取到 composables
- [ ] composables 返回响应式引用(不是 .value
- [ ] 纯函数不要包装成 composable
- [ ] 副作用在组件卸载时清理
### 性能
- [ ] 大型组件拆分为小组件
- [ ] 使用 defineAsyncComponent 懒加载
- [ ] 避免不必要的响应式转换
- [ ] v-memo 用于昂贵的列表渲染