fix: correct review-guide errors and sync skill metadata

Correctness fixes in the reference guides:
- django: len()/count() caching explanation was reversed; fix slicing/index
  cache example; replace nonexistent __acall__ async middleware with the
  documented markcoroutinefunction pattern; drop removed
  SECURE_BROWSER_XSS_FILTER; REFERRER_POLICY -> SECURE_REFERRER_POLICY
- security: path-traversal guard compared a relative path to an absolute one
  (rejected every valid file); compare absolute-vs-absolute
- nestjs: e2e ValidationPipe needs forbidNonWhitelisted/transform for the
  "extra field -> 400" test to pass
- rust: select! cancel-safety example used read on both sides; bad case now
  uses non-cancel-safe read_exact
- code-quality: "read less" example still read the whole file; use readline()
- svelte: drop nonexistent unstate() (use $state.snapshot); fix devalue/Date
  note; comma-operator each-key -> template literal
- c/cpp/qt: restore mangled markers that rendered as a literal ?
- csharp: drop fabricated perf numbers
- java: scope HashMap infinite-loop note to Java 7 and earlier
- kotlin: closeableScope -> built-in viewModelScope
- css: deprecated darken()/clip:rect() -> color.adjust/clip-path
- typescript: legacy .eslintrc -> flat config (typescript-eslint v8)

Tooling and metadata:
- pr-analyzer.py: filename regex corrupted lib//web//db/ paths; parse the diff
  header via backreference and add utf-8/error handling; add test
- SKILL.md: canonical name code-review-skill; document severity tiers; wire in
  pr-analyzer.py
- README/CONTRIBUTING: fix stale skill name and line counts; complete the guide
  tree; add a conventions section
This commit is contained in:
Tu Shaokun
2026-06-05 09:44:47 +08:00
parent ca6a249dbb
commit b14b3f657f
21 changed files with 289 additions and 158 deletions
+23 -2
View File
@@ -17,11 +17,19 @@ code-review-skill/
├── reference/ # On-demand language/framework guides
│ ├── react.md # React 19 / Next.js / TanStack Query v5
│ ├── vue.md # Vue 3.5 Composition API
│ ├── angular.md # Angular 17+, Signals, Standalone, RxJS
│ ├── svelte.md # Svelte 5 / SvelteKit, runes, SSR boundary
│ ├── rust.md # Ownership, async, unsafe, cancellation
│ ├── typescript.md # Type safety, generics, strict mode
│ ├── nestjs.md # NestJS DI, modules, Guards/Pipes, DTOs
│ ├── python.md # Type hints, async, testing
│ ├── django.md # Django / DRF, N+1, serializers, async views
│ ├── fastapi.md # FastAPI, Depends, Pydantic v2, async
│ ├── java.md # Java 17/21, Spring Boot 3, virtual threads
│ ├── kotlin.md # Kotlin / Android, coroutines, Flow, Compose
│ ├── go.md # Error handling, goroutines, context
│ ├── csharp.md # C# / .NET 8, async, EF Core, ASP.NET Core
│ ├── php.md # PHP 8.x, types, PDO, security, Composer
│ ├── c.md # Memory safety, UB, error handling
│ ├── cpp.md # RAII, move semantics, exception safety
│ ├── qt.md # Object model, signals/slots, GUI perf
@@ -30,6 +38,7 @@ code-review-skill/
│ ├── performance-review-guide.md # Web Vitals, N+1, complexity
│ ├── security-review-guide.md # OWASP Top 10, JWT, validation
│ ├── common-bugs-checklist.md # Quick-reference bug patterns
│ ├── code-quality-universal.md # Language-agnostic quality anti-patterns
│ └── code-review-best-practices.md # Communication & process
├── assets/ # Templates and quick reference
│ ├── review-checklist.md
@@ -73,7 +82,7 @@ allowed-tools: ["Read", "Grep", "Glob"] # 可选:限制工具访问
- 避免下划线或大写字母
```
✅ 正确:code-review-excellence, typescript-advanced-types
✅ 正确:code-review-skill, typescript-advanced-types
❌ 错误:CodeReview, code_review, TYPESCRIPT
```
@@ -149,6 +158,18 @@ Claude 只在需要时加载支持文件,不会一次性加载所有内容。
- 使用正斜杠 `/`,不使用反斜杠
- 不需要 `./` 前缀
### 约定(Conventions)
**严重级别(severity)**:审查意见统一使用 SKILL.md「Technique 4」的标记方案,三档由红到绿表示优先级:
- 🔴 `[blocking]` - 合并前必须修复
- 🟡 `[important]` - 应当修复,有异议可讨论
- 🟢 `[nit]` - 可选优化,不阻塞合并
新增 reference 指南时请沿用这套标记,不要自创等价的名称(如 critical/warning/suggestion)。
**语言策略**:现有指南是中英混合的——部分通篇中文,部分(如 fastapi.md、php.md)以英文为主。新增内容时**跟随同一领域既有指南的语言**:改某个指南就用它的语言;新建指南可自行选择中文或英文,但单个文件内部保持一致。
---
## 贡献类型
@@ -306,7 +327,7 @@ feat: 添加 Go 语言代码审查指南
将修改后的 Skill 复制到 `~/.claude/skills/` 目录,然后在 Claude Code 中测试:
```bash
cp -r ai-code-review-guide ~/.claude/skills/code-review-excellence
cp -r code-review-skill ~/.claude/skills/code-review-skill
```
### Q: 我应该更新 SKILL.md 还是 reference 文件?
+12 -12
View File
@@ -99,7 +99,7 @@ It covers **20+ languages and frameworks** with over **16,000 lines** of careful
<td rowspan="9"><strong>Backend</strong></td>
<td>&#9749; Java 17/21 + Spring Boot 3</td>
<td><code>reference/java.md</code></td>
<td>~800</td>
<td>~410</td>
</tr>
<tr>
<td>&#9889; FastAPI</td>
@@ -155,12 +155,12 @@ It covers **20+ languages and frameworks** with over **16,000 lines** of careful
<tr>
<td>&#9881;&#65039; C</td>
<td><code>reference/c.md</code></td>
<td>~210</td>
<td>~290</td>
</tr>
<tr>
<td>&#128297; C++</td>
<td><code>reference/cpp.md</code></td>
<td>~300</td>
<td>~390</td>
</tr>
<tr>
<td>&#128421;&#65039; Qt Framework</td>
@@ -176,12 +176,12 @@ It covers **20+ languages and frameworks** with over **16,000 lines** of careful
<tr>
<td>&#9889; Performance Review</td>
<td><code>reference/performance-review-guide.md</code></td>
<td>~850</td>
<td>~820</td>
</tr>
<tr>
<td>&#128269; Universal Quality Anti-Patterns</td>
<td><code>reference/code-quality-universal.md</code></td>
<td>~320</td>
<td>~490</td>
</tr>
</tbody>
</table>
@@ -382,7 +382,7 @@ Contributions are welcome! See [CONTRIBUTING.md](./CONTRIBUTING.md) for guidelin
**Ideas:**
- New language guides (Ruby, Elixir, Scala...)
- Framework-specific guides (Laravel, Spring WebFlux, FastAPI...)
- Framework-specific guides (Laravel, Spring WebFlux...)
- Additional checklists and templates
- Translations of core documentation
@@ -427,7 +427,7 @@ MIT &copy; [awesome-skills](https://github.com/awesome-skills)
| | &#128293; Svelte 5 / SvelteKit | `reference/svelte.md` | ~1,060 |
| | &#127912; CSS / Less / Sass | `reference/css-less-sass.md` | ~660 |
| | &#128311; TypeScript | `reference/typescript.md` | ~540 |
| **后端** | &#9749; Java 17/21 + Spring Boot 3 | `reference/java.md` | ~800 |
| **后端** | &#9749; Java 17/21 + Spring Boot 3 | `reference/java.md` | ~410 |
| | &#9889; FastAPI | `reference/fastapi.md` | ~590 |
| | PHP 8.x | `reference/php.md` | ~700 |
| | &#128230; NestJS | `reference/nestjs.md` | ~590 |
@@ -438,12 +438,12 @@ MIT &copy; [awesome-skills](https://github.com/awesome-skills)
| | &#128187; C# / .NET 8 | `reference/csharp.md` | ~520 |
| **移动 / 系统** | &#128241; Kotlin / Android | `reference/kotlin.md` | ~1,020 |
| | &#127822; Swift / SwiftUI | `reference/swift.md` | ~930 |
| | &#9881;&#65039; C | `reference/c.md` | ~210 |
| | &#128297; C++ | `reference/cpp.md` | ~300 |
| | &#9881;&#65039; C | `reference/c.md` | ~290 |
| | &#128297; C++ | `reference/cpp.md` | ~390 |
| | &#128421;&#65039; Qt 框架 | `reference/qt.md` | ~190 |
| **架构** | &#127963;&#65039; 架构设计审查 | `reference/architecture-review-guide.md` | ~470 |
| | &#9889; 性能审查 | `reference/performance-review-guide.md` | ~850 |
| | &#128269; 通用质量反模式 | `reference/code-quality-universal.md` | ~320 |
| | &#9889; 性能审查 | `reference/performance-review-guide.md` | ~820 |
| | &#128269; 通用质量反模式 | `reference/code-quality-universal.md` | ~490 |
---
@@ -641,7 +641,7 @@ Use code-review-skill to review this PR
**可贡献方向:**
- 新增语言指南(Ruby、Elixir、Scala...)
- 框架专属指南(Laravel、Spring WebFlux、FastAPI...)
- 框架专属指南(Laravel、Spring WebFlux...)
- 补充检查清单和审查模板
- 核心文档的多语言翻译
+6 -2
View File
@@ -1,5 +1,5 @@
---
name: code-review-excellence
name: code-review-skill
description: |
Provides comprehensive code review guidance for React 19, Vue 3, Angular 17+, Svelte 5, Rust, TypeScript, Java, PHP, Python, Django, Go, C#/.NET, Kotlin, Swift, NestJS, C/C++, and more.
Helps catch bugs, improve code quality, and give constructive feedback.
@@ -14,7 +14,7 @@ allowed-tools:
- WebFetch # 查阅最新文档和最佳实践
---
# Code Review Excellence
# Code Review Skill
Transform code reviews from gatekeeping to knowledge sharing through constructive feedback, systematic analysis, and collaborative improvement.
@@ -99,6 +99,8 @@ Before diving into code, understand:
4. Understand the business requirement
5. Note any relevant architectural decisions
> For large diffs, pipe the diff through [`scripts/pr-analyzer.py`](scripts/pr-analyzer.py) (`git diff main...HEAD | python scripts/pr-analyzer.py`) to triage complexity and get a suggested review approach before reading.
### Phase 2: High-Level Review (5-10 minutes)
1. **Architecture & Design** - Does the solution fit the problem?
@@ -170,6 +172,8 @@ Use labels to indicate priority:
- 📚 `[learning]` - Educational comment, no action needed
- 🎉 `[praise]` - Good work, keep it up!
**Severity levels:** 🔴 / 🟡 / 🟢 are the three severity tiers used as the standard across all guides in this skill — 🔴 blocks the merge, 🟡 should be addressed, 🟢 is optional. The remaining markers (💡 / 📚 / 🎉) are non-blocking annotations.
## Language-Specific Guides
根据审查的代码语言,查阅对应的详细指南:
+6 -6
View File
@@ -38,11 +38,11 @@ Copy and use this template for your code reviews.
💡 **[suggestion]** [Alternative approach to consider]
## Questions
## Learning Notes
❓ [Clarification needed about X]
📚 [Educational context worth sharing about X]
❓ [Question about design decision Y]
📚 [Background behind design decision Y]
## Security Considerations
@@ -106,9 +106,9 @@ Not blocking, but consider [improvement].
[Why this is good]
```
### Question
### Learning
```
❓ **[question]** [Your question]
📚 **[learning]** [Educational note]
I'm curious about the decision to [X]. Could you explain [Y]?
For context, [X] works this way because [Y]. No action needed — just sharing.
```
+1 -1
View File
@@ -76,7 +76,7 @@ Quick reference checklist for code reviews.
| 🟡 `[important]` | Should fix | Discuss if disagree |
| 🟢 `[nit]` | Nice to have | Non-blocking |
| 💡 `[suggestion]` | Alternative | Consider |
| ❓ `[question]` | Need clarity | Respond |
| 📚 `[learning]` | Educational comment | No action needed |
| 🎉 `[praise]` | Good work | Celebrate! |
---
+22 -22
View File
@@ -22,13 +22,13 @@
### Always carry size with buffers
```c
// ? Bad: ignores destination size
// ❌ Bad: ignores destination size
bool copy_name(char *dst, size_t dst_size, const char *src) {
strcpy(dst, src);
return true;
}
// ? Good: validate size and terminate
// ✅ Good: validate size and terminate
bool copy_name(char *dst, size_t dst_size, const char *src) {
size_t len = strlen(src);
if (len + 1 > dst_size) {
@@ -44,20 +44,20 @@ bool copy_name(char *dst, size_t dst_size, const char *src) {
Prefer `snprintf`, `fgets`, and explicit bounds over `gets`, `strcpy`, or `sprintf`.
```c
// ? Bad: unbounded write
// ❌ Bad: unbounded write
sprintf(buf, "%s", input);
// ? Good: bounded write
// ✅ Good: bounded write
snprintf(buf, buf_size, "%s", input);
```
### Use the right copy primitive
```c
// ? Bad: memcpy with overlapping regions
// ❌ Bad: memcpy with overlapping regions
memcpy(dst, src, len);
// ? Good: memmove handles overlap
// ✅ Good: memmove handles overlap
memmove(dst, src, len);
```
@@ -70,7 +70,7 @@ memmove(dst, src, len);
Track ownership and clean up on every error path.
```c
// ? Good: cleanup label avoids leaks
// ✅ Good: cleanup label avoids leaks
int load_file(const char *path) {
int rc = -1;
FILE *f = NULL;
@@ -107,23 +107,23 @@ cleanup:
### Common UB patterns
```c
// ? Bad: use after free
// ❌ Bad: use after free
char *p = malloc(10);
free(p);
p[0] = 'a';
// ? Bad: uninitialized read
// ❌ Bad: uninitialized read
int x;
if (x > 0) { /* UB */ }
// ? Bad: signed overflow
// ❌ Bad: signed overflow
int sum = a + b;
```
### Avoid pointer arithmetic past the object
```c
// ? Bad: pointer past the end then dereference
// ❌ Bad: pointer past the end then dereference
int arr[4];
int *p = arr + 4;
int v = *p; // UB
@@ -136,11 +136,11 @@ int v = *p; // UB
### Avoid signed/unsigned surprises
```c
// ? Bad: negative converted to large size_t
// ❌ Bad: negative converted to large size_t
int len = -1;
size_t n = len;
// ? Good: validate before converting
// ✅ Good: validate before converting
if (len < 0) {
return -1;
}
@@ -150,10 +150,10 @@ size_t n = (size_t)len;
### Check for overflow in size calculations
```c
// ? Bad: potential overflow in multiplication
// ❌ Bad: potential overflow in multiplication
size_t bytes = count * sizeof(Item);
// ? Good: check before multiplying
// ✅ Good: check before multiplying
if (count > SIZE_MAX / sizeof(Item)) {
return NULL;
}
@@ -167,10 +167,10 @@ size_t bytes = count * sizeof(Item);
### Always check return values
```c
// ? Bad: ignore errors
// ❌ Bad: ignore errors
fread(buf, 1, size, f);
// ? Good: handle errors
// ✅ Good: handle errors
size_t read = fread(buf, 1, size, f);
if (read != size && ferror(f)) {
return -1;
@@ -190,13 +190,13 @@ if (read != size && ferror(f)) {
### volatile is not synchronization
```c
// ? Bad: data race
// ❌ Bad: data race
volatile int stop = 0;
void worker(void) {
while (!stop) { /* ... */ }
}
// ? Good: C11 atomics
// ✅ Good: C11 atomics
_Atomic int stop = 0;
void worker(void) {
while (!atomic_load(&stop)) { /* ... */ }
@@ -214,11 +214,11 @@ Protect shared data with `pthread_mutex_t` or equivalent. Avoid holding locks wh
### Parenthesize arguments
```c
// ? Bad: macro with side effects
// ❌ Bad: macro with side effects
#define MIN(a, b) ((a) < (b) ? (a) : (b))
int x = MIN(i++, j++);
// ? Good: static inline function
// ✅ Good: static inline function
static inline int min_int(int a, int b) {
return a < b ? a : b;
}
@@ -231,7 +231,7 @@ static inline int min_int(int a, int b) {
### Const-correctness and sizes
```c
// ? Good: explicit size and const input
// ✅ Good: explicit size and const input
int hash_bytes(const uint8_t *data, size_t len, uint8_t *out);
```
+1 -3
View File
@@ -395,9 +395,7 @@ try {
content = Path("log.txt").read_text()
first_line = content.split("\n")[0]
# ✅ 只读需要的内容
first_line = Path("log.txt").read_text().split("\n", 1)[0]
# 或更好的方式:逐行读取
# ✅ 只读第一行,不加载整个文件
with open("log.txt") as f:
first_line = f.readline()
```
+22 -22
View File
@@ -24,7 +24,7 @@
Use RAII to express ownership. Default to `std::unique_ptr`, use `std::shared_ptr` only for shared lifetime.
```cpp
// ? Bad: manual new/delete with early returns
// ❌ Bad: manual new/delete with early returns
Foo* make_foo() {
Foo* foo = new Foo();
if (!foo->Init()) {
@@ -34,7 +34,7 @@ Foo* make_foo() {
return foo;
}
// ? Good: RAII with unique_ptr
// ✅ Good: RAII with unique_ptr
std::unique_ptr<Foo> make_foo() {
auto foo = std::make_unique<Foo>();
if (!foo->Init()) {
@@ -47,7 +47,7 @@ std::unique_ptr<Foo> make_foo() {
### Wrap C resources
```cpp
// ? Good: wrap FILE* with unique_ptr
// ✅ Good: wrap FILE* with unique_ptr
using FilePtr = std::unique_ptr<FILE, decltype(&fclose)>;
FilePtr open_file(const char* path) {
@@ -64,18 +64,18 @@ FilePtr open_file(const char* path) {
`std::string_view` and `std::span` do not own data. Make sure the owner outlives the view.
```cpp
// ? Bad: returning string_view to a temporary
// ❌ Bad: returning string_view to a temporary
std::string_view bad_view() {
std::string s = make_name();
return s; // dangling
}
// ? Good: return owning string
// ✅ Good: return owning string
std::string good_name() {
return make_name();
}
// ? Good: view tied to caller-owned data
// ✅ Good: view tied to caller-owned data
std::string_view good_view(const std::string& s) {
return s;
}
@@ -84,13 +84,13 @@ std::string_view good_view(const std::string& s) {
### Lambda captures
```cpp
// ? Bad: capture reference that escapes
// ❌ Bad: capture reference that escapes
std::function<void()> make_task() {
int value = 42;
return [&]() { use(value); }; // dangling
}
// ? Good: capture by value
// ✅ Good: capture by value
std::function<void()> make_task() {
int value = 42;
return [value]() { use(value); };
@@ -106,7 +106,7 @@ std::function<void()> make_task() {
Prefer the Rule of 0 by using RAII types. If you own a resource, define or delete copy and move operations.
```cpp
// ? Bad: raw ownership with default copy
// ❌ Bad: raw ownership with default copy
struct Buffer {
int* data;
size_t size;
@@ -115,7 +115,7 @@ struct Buffer {
// copy ctor/assign are implicitly generated -> double delete
};
// ? Good: Rule of 0 with std::vector
// ✅ Good: Rule of 0 with std::vector
struct Buffer {
std::vector<int> data;
explicit Buffer(size_t n) : data(n) {}
@@ -164,10 +164,10 @@ struct Millis {
struct Shape { virtual ~Shape() = default; };
struct Circle : Shape { void draw() const; };
// ? Bad: slices Circle into Shape
// ❌ Bad: slices Circle into Shape
void draw(Shape shape);
// ? Good: pass by reference
// ✅ Good: pass by reference
void draw(const Shape& shape);
```
@@ -190,7 +190,7 @@ struct Worker final : Base {
### Prefer RAII for cleanup
```cpp
// ? Good: RAII handles cleanup on exceptions
// ✅ Good: RAII handles cleanup on exceptions
void process() {
std::vector<int> data = load_data(); // safe cleanup
do_work(data);
@@ -209,7 +209,7 @@ struct File {
### Use expected results for normal failures
```cpp
// ? Expected error: use optional or expected
// ✅ Expected error: use optional or expected
std::optional<int> parse_int(const std::string& s) {
try {
return std::stoi(s);
@@ -226,11 +226,11 @@ std::optional<int> parse_int(const std::string& s) {
### Protect shared data
```cpp
// ? Bad: data race
// ❌ Bad: data race
int counter = 0;
void inc() { counter++; }
// ? Good: atomic
// ✅ Good: atomic
std::atomic<int> counter{0};
void inc() { counter.fetch_add(1, std::memory_order_relaxed); }
```
@@ -254,7 +254,7 @@ void add(int v) {
### Avoid repeated allocations
```cpp
// ? Bad: repeated reallocation
// ❌ Bad: repeated reallocation
std::vector<int> build(int n) {
std::vector<int> out;
for (int i = 0; i < n; ++i) {
@@ -263,7 +263,7 @@ std::vector<int> build(int n) {
return out;
}
// ? Good: reserve upfront
// ✅ Good: reserve upfront
std::vector<int> build(int n) {
std::vector<int> out;
out.reserve(static_cast<size_t>(n));
@@ -277,7 +277,7 @@ std::vector<int> build(int n) {
### String concatenation
```cpp
// ? Bad: repeated allocation
// ❌ Bad: repeated allocation
std::string join(const std::vector<std::string>& parts) {
std::string out;
for (const auto& p : parts) {
@@ -286,7 +286,7 @@ std::string join(const std::vector<std::string>& parts) {
return out;
}
// ? Good: reserve total size
// ✅ Good: reserve total size
std::string join(const std::vector<std::string>& parts) {
size_t total = 0;
for (const auto& p : parts) {
@@ -308,13 +308,13 @@ std::string join(const std::vector<std::string>& parts) {
### Prefer constrained templates (C++20)
```cpp
// ? Bad: overly generic
// ❌ Bad: overly generic
template <typename T>
T add(T a, T b) {
return a + b;
}
// ? Good: constrained
// ✅ Good: constrained
template <typename T>
requires std::is_integral_v<T>
T add(T a, T b) {
+2 -2
View File
@@ -247,7 +247,7 @@ var blogs = await context.Blogs
// ❌ 默认跟踪——只读查询也付出跟踪开销
var products = await context.Products.ToListAsync();
// ✅ AsNoTracking——性能提升 ~30%,内存减少 ~40%
// ✅ AsNoTracking——跳过变更跟踪,更快且更省内存
var products = await context.Products
.AsNoTracking()
.ToListAsync();
@@ -334,7 +334,7 @@ var form = await HttpContext.Request.ReadFormAsync();
### 异常用于控制流
```csharp
// ❌ 用异常判断是否存在——比检查慢 10-100 倍
// ❌ 用异常判断是否存在——异常开销大,比直接检查慢得多
try
{
var user = await _db.Users.FirstAsync(u => u.Id == id);
+7 -2
View File
@@ -562,6 +562,8 @@ module.exports = {
### Mixin vs Extend vs 变量
```scss
@use 'sass:color';
/* 变量 - 用于单个值 */
$primary-color: #3b82f6;
@@ -570,7 +572,9 @@ $primary-color: #3b82f6;
background: $bg;
color: $text;
&:hover {
background: darken($bg, 10%);
// Dart Sass 已弃用全局 darken()/lighten(),改用 color 模块
background: color.adjust($bg, $lightness: -10%);
// color.scale($bg, $lightness: -10%) 按比例调整,深浅过渡更自然
}
}
@@ -580,7 +584,8 @@ $primary-color: #3b82f6;
width: 1px;
height: 1px;
overflow: hidden;
clip: rect(0, 0, 0, 0);
clip-path: inset(50%); /* clip: rect() 已弃用,改用 clip-path */
white-space: nowrap; /* 避免内容被挤成一列后撑开布局 */
}
.sr-only {
+30 -28
View File
@@ -223,15 +223,15 @@ for author in authors:
### QuerySet 缓存误用
```python
# ❌ 重复评估同一个 QuerySet
# ❌ count() 后再迭代 —— 两次查询
qs = Book.objects.all()
count = len(qs) # 评估 1: SELECT COUNT(*)
titles = [b.title for b in qs] # 评估 2: SELECT * — 缓存失效!
count = qs.count() # 查询 1: SELECT COUNT(*) — 不填充缓存
titles = [b.title for b in qs] # 查询 2: SELECT * — 重新评估
# ✅ 使用 count() 和一次性迭代
# ✅ 既要对象又要数量时,用 len() 触发一次评估并复用缓存
qs = Book.objects.all()
count = qs.count() # SELECT COUNT(*) — 不填充缓存
titles = [b.title for b in qs] # SELECT * — 唯一一次评估
count = len(qs) # 查询 1: SELECT * — 全部加载并缓存
titles = [b.title for b in qs] # 复用缓存,无新查询
# ✅ 如果需要多次迭代,先转 list
books = list(Book.objects.all()) # 一次查询
@@ -239,18 +239,24 @@ count = len(books)
titles = [b.title for b in books]
```
### 切片不填充缓存
### 切片/索引不填充缓存
```python
# ❌ 切片后迭代触发两次查询
qs = Book.objects.all()[:10] # 切片:不填充缓存
first = list(qs) # 查询 1
second = list(qs) # 查询 2 — 重复!
# ❌ 反复索引未评估的 QuerySet —— 每次都查库
qs = Book.objects.all()
qs[0] # 查询 1: SELECT ... LIMIT 1
qs[0] # 查询 2 — 切片/索引不会填充缓存
# ✅ 切片后立即转 list
books = list(Book.objects.all()[:10]) # 一次查询
first = books
second = list(books) # 使用 Python list,无查询
# ✅ 先整体评估,缓存保存所有行,之后索引走缓存
qs = Book.objects.all()
list(qs) # SELECT * — 评估并缓存全部行
qs[0] # 走缓存,无查询
qs[5] # 走缓存,无查询
# ✅ 只需要前 N 条时,切一次并转 list
books = list(Book.objects.all()[:10]) # 一次查询:SELECT ... LIMIT 10
first = books[0]
rest = books[1:] # 已是 Python list,无查询
```
### len() vs count()
@@ -743,31 +749,28 @@ class TimingMiddleware:
response["X-Elapsed"] = str(elapsed)
return response
# ✅ 同时支持同步和异步的中间件
# ✅ async-capable 中间件:async def __call__,并在 __init__ 里标记实例
import time
from asgiref.sync import iscoroutinefunction, markcoroutinefunction
class TimingMiddleware:
async_capable = True
sync_capable = True
sync_capable = False
def __init__(self, get_response):
self.get_response = get_response
# get_response 是协程函数时标记自己,Django 才会 await 这个实例
if iscoroutinefunction(self.get_response):
markcoroutinefunction(self)
async def __acall__(self, request):
async def __call__(self, request):
start = time.time()
response = await self.get_response(request)
elapsed = time.time() - start
response["X-Elapsed"] = str(elapsed)
return response
def __call__(self, request):
start = time.time()
response = self.get_response(request)
elapsed = time.time() - start
response["X-Elapsed"] = str(elapsed)
return response
# ✅ 或者使用 Django 内置的 async 安全装饰器
# ✅ 要同时兼容同步和异步,用工厂函数 + 内置装饰器
from django.utils.decorators import sync_and_async_middleware
```
@@ -839,9 +842,8 @@ SECURE_HSTS_SECONDS = 31536000 # 1 year HSTS
SECURE_HSTS_INCLUDE_SUBDOMAINS = True
SECURE_HSTS_PRELOAD = True
SECURE_CONTENT_TYPE_NOSNIFF = True # X-Content-Type-Options: nosniff
SECURE_BROWSER_XSS_FILTER = True # X-XSS-Protection: 1; mode=block
X_FRAME_OPTIONS = "DENY" # 防止 clickjacking
REFERRER_POLICY = "strict-origin-when-cross-origin"
SECURE_REFERRER_POLICY = "strict-origin-when-cross-origin"
# --- 密码验证 ---
AUTH_PASSWORD_VALIDATORS = [
+1 -1
View File
@@ -292,7 +292,7 @@ private static final SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd");
// ✅ 使用 DateTimeFormatter (Java 8+)
private static final DateTimeFormatter dtf = DateTimeFormatter.ofPattern("yyyy-MM-dd");
// ❌ HashMap 在多线程环境可能死循环或数据丢失
// ❌ HashMap 在多线程环境会数据丢失(Java 7 及之前 resize 还可能死循环,Java 8 修复了死循环但仍非线程安全)
// ✅ 使用 ConcurrentHashMap
Map<String, String> cache = new ConcurrentHashMap<>();
```
+1 -1
View File
@@ -694,7 +694,7 @@ class MyManager(private val scope: CoroutineScope) {
}
}
// ✅ ViewModel 中使用 closeableScope(Kotlin 2.1+)
// ✅ ViewModel 里直接用内置的 viewModelScope,不用自己管生命周期
class MyViewModel : ViewModel() {
private val scope = viewModelScope + Dispatchers.IO
// Automatically cancelled when ViewModel is cleared
+7 -1
View File
@@ -516,7 +516,13 @@ describe('UsersController (e2e)', () => {
app = moduleFixture.createNestApplication();
// 必须与 main.ts 中相同的全局配置
app.useGlobalPipes(new ValidationPipe({ whitelist: true }));
app.useGlobalPipes(
new ValidationPipe({
whitelist: true,
forbidNonWhitelisted: true,
transform: true,
}),
);
await app.init();
});
+1 -1
View File
@@ -74,7 +74,7 @@ Check logic that might cause infinite signal loops (e.g., `valueChanged` -> `set
```cpp
void MyClass::setValue(int v) {
if (m_value == v) return; // ? Good: Break loop
if (m_value == v) return; // ✅ Good: Break loop
m_value = v;
emit valueChanged(v);
}
+8 -6
View File
@@ -309,10 +309,11 @@ async fn bad_select(stream: &mut TcpStream) {
let mut buffer = vec![0u8; 1024];
loop {
select! {
// 如果 timeout 先完成,read 被取消
// 部分读取的数据可能丢失!
result = stream.read(&mut buffer) => {
handle_data(&buffer[..result?]);
// read_exact 不是取消安全的:timeout 先完成时,
// 已经读进 buffer 的部分字节会随 Future 一起丢弃
result = stream.read_exact(&mut buffer) => {
result?;
handle_data(&buffer);
}
_ = tokio::time::sleep(Duration::from_secs(5)) => {
println!("Timeout");
@@ -326,8 +327,9 @@ async fn good_select(stream: &mut TcpStream) {
let mut buffer = vec![0u8; 1024];
loop {
select! {
// tokio::io::AsyncReadExt::read 是取消安全的
// 取消时,未读取的数据留在流中
// read 是取消安全的:被取消时未读取的数据仍留在流中
// 真的需要按定长读取时,把 read_exact 丢到单独的 task 里,
// 这里 select! 它的 JoinHandle,取消就不会丢字节
result = stream.read(&mut buffer) => {
match result {
Ok(0) => break, // EOF
+4 -3
View File
@@ -96,10 +96,11 @@ 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);
const uploadsDir = path.resolve('./uploads');
const filePath = path.resolve(uploadsDir, safeName);
// Verify it's still within uploads directory
if (!filePath.startsWith(path.resolve('./uploads'))) {
// Verify it's still within uploads directory (both sides absolute)
if (!filePath.startsWith(uploadsDir + path.sep)) {
throw new Error('Invalid path');
}
```
+3 -7
View File
@@ -76,15 +76,11 @@ Svelte 5 / SvelteKit 审查重点:Runes 响应式系统、Server/Client 边界
<!-- ✅ $state.snapshot 获取普通对象副本 -->
<script lang="ts">
import { unstate } from 'svelte';
let state = $state({ x: 0, y: 0 });
onMount(() => {
// $state.snapshot produces a plain object (Svelte 5)
chartLibrary.update($state.snapshot(state));
// or use unstate() for the same purpose
chartLibrary.update(unstate(state));
});
</script>
```
@@ -385,7 +381,7 @@ export async function load() {
return {
stream: fs.createReadStream('data.csv'), // not serializable!
callback: () => console.log('hi'), // functions not serializable!
date: new Date(), // becomes string via devalue
date: new Date(), // OK — devalue serializes Date/Map/Set fine
};
}
@@ -863,7 +859,7 @@ export const actions = {
{/each}
<!-- ✅ 复合 key -->
{#each items as item (item.category, item.id)}
{#each items as item (`${item.category}-${item.id}`)}
<div>{item.name}</div>
{/each}
```
@@ -1006,7 +1002,7 @@ event.cookies.set('session', token, {
- [ ] $state 只用于会变化的值,常量直接声明
- [ ] 大型不可变数据使用 $state.raw
- [ ] 没有解构 $state 对象(会丢失响应性)
- [ ] 外部库使用 $state.snapshot / unstate 传入普通对象
- [ ] 外部库使用 $state.snapshot 传入普通对象
- [ ] $derived 中没有副作用
- [ ] 没有用 $effect 替代 $derived 做状态同步
- [ ] $effect 中不修改被追踪的状态(避免无限循环)
+36 -26
View File
@@ -446,34 +446,44 @@ const routes = createConfig(['home', 'about', 'contact'] as const);
### 推荐的 @typescript-eslint 规则
```javascript
// .eslintrc.js
module.exports = {
extends: [
'eslint:recommended',
'plugin:@typescript-eslint/recommended',
'plugin:@typescript-eslint/recommended-requiring-type-checking',
'plugin:@typescript-eslint/strict'
],
rules: {
// ✅ 类型安全
'@typescript-eslint/no-explicit-any': 'error',
'@typescript-eslint/no-unsafe-assignment': 'error',
'@typescript-eslint/no-unsafe-member-access': 'error',
'@typescript-eslint/no-unsafe-call': 'error',
'@typescript-eslint/no-unsafe-return': 'error',
// eslint.config.js(flat config,typescript-eslint v8)
import eslint from '@eslint/js';
import tseslint from 'typescript-eslint';
// ✅ 最佳实践
'@typescript-eslint/explicit-function-return-type': 'warn',
'@typescript-eslint/no-floating-promises': 'error',
'@typescript-eslint/await-thenable': 'error',
'@typescript-eslint/no-misused-promises': 'error',
export default tseslint.config(
eslint.configs.recommended,
// 需要类型信息的规则集,对应旧的 recommended-requiring-type-checking
tseslint.configs.recommendedTypeChecked,
tseslint.configs.strictTypeChecked,
{
languageOptions: {
parserOptions: {
// 让带类型的规则自动找到对应 tsconfig
projectService: true,
tsconfigRootDir: import.meta.dirname,
},
},
rules: {
// ✅ 类型安全
'@typescript-eslint/no-explicit-any': 'error',
'@typescript-eslint/no-unsafe-assignment': 'error',
'@typescript-eslint/no-unsafe-member-access': 'error',
'@typescript-eslint/no-unsafe-call': 'error',
'@typescript-eslint/no-unsafe-return': 'error',
// ✅ 代码风格
'@typescript-eslint/consistent-type-imports': 'error',
'@typescript-eslint/prefer-nullish-coalescing': 'error',
'@typescript-eslint/prefer-optional-chain': 'error'
}
};
// ✅ 最佳实践
'@typescript-eslint/explicit-function-return-type': 'warn',
'@typescript-eslint/no-floating-promises': 'error',
'@typescript-eslint/await-thenable': 'error',
'@typescript-eslint/no-misused-promises': 'error',
// ✅ 代码风格
'@typescript-eslint/consistent-type-imports': 'error',
'@typescript-eslint/prefer-nullish-coalescing': 'error',
'@typescript-eslint/prefer-optional-chain': 'error',
},
},
);
```
### 常见 ESLint 错误修复
+21 -10
View File
@@ -131,8 +131,13 @@ def parse_diff(diff_content: str) -> List[FileStats]:
if line.startswith('diff --git'):
if current_file:
files.append(current_file)
# Extract filename from "diff --git a/path b/path"
match = re.search(r'b/(.+)$', line)
# "diff --git a/<path> b/<path>" — match the b/ side via a
# backreference so a literal "b/" inside paths like lib/, web/ or
# db/ can't be mistaken for the prefix. Renames have differing
# paths, so fall back to the b/ side after the separating space.
match = re.match(r'diff --git a/(.+?) b/\1', line)
if not match:
match = re.search(r' b/(.+)$', line)
if match:
filename = match.group(1)
current_file = FileStats(
@@ -141,6 +146,8 @@ def parse_diff(diff_content: str) -> List[FileStats]:
is_test=is_test_file(filename),
is_config=is_config_file(filename),
)
else:
current_file = None
elif current_file:
if line.startswith('+') and not line.startswith('+++'):
current_file.additions += 1
@@ -355,14 +362,18 @@ def main():
args = parser.parse_args()
# Read diff from file or stdin
if args.diff_file:
with open(args.diff_file, 'r') as f:
diff_content = f.read()
elif not sys.stdin.isatty():
diff_content = sys.stdin.read()
else:
print("Usage: git diff main...HEAD | python pr-analyzer.py")
print(" python pr-analyzer.py -f diff.txt")
try:
if args.diff_file:
with open(args.diff_file, 'r', encoding='utf-8', errors='replace') as f:
diff_content = f.read()
elif not sys.stdin.isatty():
diff_content = sys.stdin.buffer.read().decode('utf-8', errors='replace')
else:
print("Usage: git diff main...HEAD | python pr-analyzer.py")
print(" python pr-analyzer.py -f diff.txt")
sys.exit(1)
except OSError as e:
print(f"Error reading diff input: {e}", file=sys.stderr)
sys.exit(1)
if not diff_content.strip():
+75
View File
@@ -0,0 +1,75 @@
#!/usr/bin/env python3
"""Tests for pr-analyzer.py diff parsing (stdlib unittest, no extra deps)."""
import importlib.util
import os
import unittest
# The script has a hyphen in its name, so load it by path.
_HERE = os.path.dirname(os.path.abspath(__file__))
_spec = importlib.util.spec_from_file_location(
'pr_analyzer', os.path.join(_HERE, 'pr-analyzer.py')
)
pr_analyzer = importlib.util.module_from_spec(_spec)
_spec.loader.exec_module(pr_analyzer)
class ParseDiffFilenameTest(unittest.TestCase):
def test_lib_prefixed_path(self):
# "lib/" embeds a literal "b/" that the old regex swallowed.
diff = (
"diff --git a/lib/foo.py b/lib/foo.py\n"
"index 1234567..89abcde 100644\n"
"--- a/lib/foo.py\n"
"+++ b/lib/foo.py\n"
"@@ -1,2 +1,3 @@\n"
" unchanged\n"
"+added line\n"
"-removed line\n"
)
files = pr_analyzer.parse_diff(diff)
self.assertEqual(len(files), 1)
self.assertEqual(files[0].filename, 'lib/foo.py')
self.assertEqual(files[0].additions, 1)
self.assertEqual(files[0].deletions, 1)
def test_normal_path(self):
diff = (
"diff --git a/src/main.py b/src/main.py\n"
"index 1111111..2222222 100644\n"
"--- a/src/main.py\n"
"+++ b/src/main.py\n"
"@@ -0,0 +1 @@\n"
"+print('hi')\n"
)
files = pr_analyzer.parse_diff(diff)
self.assertEqual(len(files), 1)
self.assertEqual(files[0].filename, 'src/main.py')
def test_other_embedded_b_slash_prefixes(self):
# web/ and db/ also contain a literal "b/".
diff = (
"diff --git a/web/x.js b/web/x.js\n"
"+++ b/web/x.js\n"
"+console.log(1)\n"
"diff --git a/db/y.sql b/db/y.sql\n"
"+++ b/db/y.sql\n"
"+SELECT 1;\n"
)
files = pr_analyzer.parse_diff(diff)
self.assertEqual([f.filename for f in files], ['web/x.js', 'db/y.sql'])
def test_rename_falls_back_to_b_side(self):
diff = (
"diff --git a/old/name.py b/new/name.py\n"
"similarity index 100%\n"
"rename from old/name.py\n"
"rename to new/name.py\n"
)
files = pr_analyzer.parse_diff(diff)
self.assertEqual(len(files), 1)
self.assertEqual(files[0].filename, 'new/name.py')
if __name__ == '__main__':
unittest.main()