Files
knowledge-base/4 - Resources/Claude-Code/Claude Code 新项目实操教程 (贯穿示例).md

448 lines
14 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

---
created: "2026-06-27"
type: resource
tags: [claude-code, tutorial, hands-on, new-project, walkthrough, example]
source: "Anthropic 官方最佳实践 + 实操演练 · 2026-06-27"
---
# Claude Code 新项目实操教程 (贯穿示例)
> 配套理论篇:[[Claude Code 新项目开发最佳实践 (2026 调研)]]。
> 本篇是**手把手教程**:用一个真实小项目「短链接 API(url-shortener)」从**空文件夹 → 第一个 PR 合并**,
> 每一步都给出**真实命令、完整文件内容、与 Claude 的真实对话**。技术栈用 Node + TypeScript + Vitest(换成你的栈思路一样)。
---
## 目标产物
一个能 `POST /shorten {url}` 返回短码、`GET /:code` 302 跳转的 HTTP 服务。我们只做**第一个垂直切片**:`POST /shorten`,走完整 TDD + 四阶段闭环。
整个过程的目录最终长这样:
```
url-shortener/
├── .claude/
│ ├── settings.json # hooks:改完自动跑测试
│ ├── rules/
│ │ └── typescript.md # path-scoped 规则(只在改 .ts 时加载)
│ └── agents/
│ └── code-reviewer.md # 自定义子代理(也可用内置)
├── CLAUDE.md # 项目记忆:命令 + 约定
├── src/
│ ├── shorten.ts
│ └── server.ts
├── test/
│ └── shorten.test.ts
├── package.json
└── tsconfig.json
```
---
## 步骤 0 — 先建 git,再开 Claude
git 是回滚安全网。**先 init 再让 Claude 动手**,这样任何一步都能 `git checkout .` 退回。
```bash
mkdir url-shortener && cd url-shortener
git init
npm init -y
npm i -D typescript vitest @types/node tsx
npx tsc --init
git add -A && git commit -m "chore: 初始化空项目"
```
然后在该目录启动 Claude Code:
```bash
claude
```
---
## 步骤 1 — 用 `/init` 生成起始 CLAUDE.md,再亲手补全
在 Claude 里输入:
```
/init
```
它会扫描目录、检测到 TypeScript + Vitest,生成一份起始 `CLAUDE.md`。**起始版通常太泛**,你要立刻补上「**怎么跑测试 / lint / build**」——这是验证循环能跑起来的前提。手动把 `CLAUDE.md` 改成:
```markdown
# CLAUDE.md
This file provides guidance to Claude Code when working in this repository.
## 命令
- 安装依赖:`npm install`
- 跑全部测试:`npm test` # 即 vitest run
- 跑单个测试文件:`npx vitest run test/shorten.test.ts`
- 跑单个用例:`npx vitest run -t "rejects an invalid url"`
- 类型检查:`npx tsc --noEmit`
- 启动服务:`npx tsx src/server.ts`
## 约定
- 纯函数放 src/,每个函数配同名 test/*.test.ts
- 不可变:函数返回新对象,绝不原地修改入参
- 短码用 base62、长度 7,生成逻辑必须可单测(不依赖网络/随机时不可测的部分要可注入)
- 错误处理:非法输入抛 `ValidationError`,不静默吞掉
## 测试策略(TDD)
先写失败测试 → 最小实现 → 重构。覆盖率目标 80%+。
```
> ⚠️ 在 `package.json` 里把 `"test": "vitest run"` 配好,否则 `npm test` 会报错。
把它提交:
```bash
git add CLAUDE.md package.json && git commit -m "docs: 添加 CLAUDE.md 与测试命令"
```
**为什么重要**:下次 Claude 想跑测试时,会直接读到 `npx vitest run -t "..."` 而不是瞎猜 `jest`、瞎猜路径。这一步省掉大量来回。
---
## 步骤 2 — 大规则拆进 `.claude/rules/`(path-scoped)
`CLAUDE.md`< 200 TypeScript 细则不该塞进主文件,而是放 `.claude/rules/typescript.md`,**只在你改 `.ts` 文件时才按需加载**,省上下文
`.claude/rules/typescript.md`:
```markdown
---
paths: ["**/*.ts"]
---
# TypeScript 规则(改 .ts 时加载)
- 开启 strict;不写 `any`,用 `unknown` + 收窄
- 导出函数必须有显式返回类型
-`type` 而非 `interface` 定义数据形状(本项目约定)
- 校验在边界做:HTTP handler 入口校验 body,核心纯函数假设输入已校验
- 命名:函数 camelCase,类型 PascalCase,常量 UPPER_SNAKE_CASE
```
`paths` frontmatter 是关键:没有它就变成全局常驻白占上下文
---
## 步骤 3 — Explore:进 plan mode 只读探索(不写一行码)
**Shift+Tab** 循环模式,切到 **plan mode**(界面底部会显示 `plan mode on`)。plan mode Claude **只能读、不能改**,逼它先看懂再动手
你输入:
```
我们要做第一个切片:POST /shorten {url} -> {code}。
先只读探索:看现有 src/、CLAUDE.md、.claude/rules/typescript.md,
告诉我你打算怎么拆分文件、纯函数边界在哪、需要哪些测试用例。先别写代码。
```
Claude plan mode 会回类似(读完文件后):
```
读完了。现状:src/ 为空,约定要求纯函数可单测、base62 短码长度 7。
拆分建议:
- src/shorten.ts:纯函数 shorten(url, idgen) —— 校验 url、用注入的 idgen 生成 code
- src/server.ts:HTTP 层,调用 shorten,负责 302/JSON 响应(薄)
纯函数边界:把"生成 id"作为参数注入,使 shorten 完全确定、可单测。
建议测试用例:
1. 合法 url -> 返回 7 位 base62 code
2. 非法 url(无 scheme)-> 抛 ValidationError
3. 同一 idgen 下相同输入 -> 相同输出(确定性)
4. code 字符集只含 [0-9a-zA-Z]
```
**关键点**:探索阶段产出的是文件拆分 + 测试清单」,不是代码idgen 作为参数注入,正是为了让纯函数可测——这种设计决策在探索阶段定下来,远比写完再返工省事
---
## 步骤 4 — Plan:生成详细计划并亲手改
继续在 plan mode,让它把上面落成一份**有序计划**:
```
按 TDD 给我一份分步计划:先写哪个测试、再写哪段实现,顺序排好。
```
Claude 给出计划后,** Ctrl+G 可在编辑器里直接改这份计划**(比如你想调整用例顺序加一条"超长 url 截断"的用例)。改完确认一份典型计划:
```
1. 写 test/shorten.test.ts:用例 1-4(此时无实现,测试会失败)
2. 跑 npx vitest run test/shorten.test.ts → 确认 RED
3. 写 src/shorten.ts 最小实现:base62 编码 + url 校验
4. 跑测试 → GREEN
5. 重构:抽出 isValidUrl、BASE62 常量
6. 写 src/server.ts 薄 HTTP 层
7. tsc --noEmit + 全量测试
8. commit + PR
```
> 计划本身就是给「实现阶段」的 prompt —— 见 [[20260320100300 Plans as Prompts设计模式]]。
---
## 步骤 5 — Implement:TDD 红→绿→重构(看真实代码)
**退出 plan mode**(Shift+Tab 切回普通模式), Claude 按计划执行下面是真实产物
### 5a. 先写失败测试(RED)
`test/shorten.test.ts`:
```typescript
import { describe, it, expect } from "vitest";
import { shorten, ValidationError } from "../src/shorten";
// 注入一个确定性 idgen,使测试可重复
const fixedIdgen = () => 123456789;
describe("shorten", () => {
it("returns a 7-char base62 code for a valid url", () => {
const { code } = shorten("https://example.com", fixedIdgen);
expect(code).toHaveLength(7);
expect(code).toMatch(/^[0-9a-zA-Z]+$/);
});
it("rejects an invalid url", () => {
expect(() => shorten("not-a-url", fixedIdgen)).toThrow(ValidationError);
});
it("is deterministic for the same idgen", () => {
const a = shorten("https://example.com", fixedIdgen).code;
const b = shorten("https://example.com", fixedIdgen).code;
expect(a).toBe(b);
});
});
```
跑一下,**确认它失败**(这一步不能跳——你要看到 RED):
```bash
npx vitest run test/shorten.test.ts
# ❌ FAIL Cannot find module '../src/shorten' —— 正是预期的 RED
```
### 5b. 最小实现到通过(GREEN)
`src/shorten.ts`:
```typescript
export class ValidationError extends Error {}
const BASE62 = "0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ";
const CODE_LEN = 7;
function isValidUrl(url: string): boolean {
try {
const u = new URL(url);
return u.protocol === "http:" || u.protocol === "https:";
} catch {
return false;
}
}
function toBase62(n: number): string {
let code = "";
let x = n;
while (code.length < CODE_LEN) {
code = BASE62[x % 62] + code;
x = Math.floor(x / 62);
}
return code.slice(-CODE_LEN);
}
export function shorten(
url: string,
idgen: () => number,
): { url: string; code: string } {
if (!isValidUrl(url)) {
throw new ValidationError(`Invalid url: ${url}`);
}
return { url, code: toBase62(idgen()) }; // 返回新对象,不改入参
}
```
再跑:
```bash
npx vitest run test/shorten.test.ts
# ✅ PASS 3 passed —— GREEN
```
### 5c. 薄 HTTP 层
`src/server.ts`:
```typescript
import { createServer } from "node:http";
import { shorten, ValidationError } from "./shorten";
const idgen = () => Date.now() + Math.floor(Math.random() * 1000);
createServer((req, res) => {
if (req.method === "POST" && req.url === "/shorten") {
let body = "";
req.on("data", (c) => (body += c));
req.on("end", () => {
try {
const { url } = JSON.parse(body); // 边界校验输入
const result = shorten(url, idgen);
res.writeHead(200, { "content-type": "application/json" });
res.end(JSON.stringify(result));
} catch (e) {
const status = e instanceof ValidationError ? 400 : 500;
res.writeHead(status, { "content-type": "application/json" });
res.end(JSON.stringify({ error: (e as Error).message }));
}
});
return;
}
res.writeHead(404).end();
}).listen(3000, () => console.log("listening on :3000"));
```
手动验一下:
```bash
npx tsx src/server.ts &
curl -s -XPOST localhost:3000/shorten -d '{"url":"https://example.com"}'
# {"url":"https://example.com","code":"0bygMqo"}
curl -s -XPOST localhost:3000/shorten -d '{"url":"bad"}'
# {"error":"Invalid url: bad"}
kill %1
```
---
## 步骤 6 — 把验证循环变成 Hook(确定性自动跑测试)
别依赖"记得跑测试"。配一个 **PostToolUse hook**:每次 Claude 改完 `.ts`,**自动跑测试**,结果回灌给它自我纠正
`.claude/settings.json`:
```json
{
"hooks": {
"PostToolUse": [
{
"matcher": "Edit|Write",
"hooks": [
{
"type": "command",
"command": "if echo \"$CLAUDE_FILE_PATHS\" | grep -q '\\.ts$'; then npx vitest run 2>&1 | tail -20; fi"
}
]
}
]
}
}
```
效果:Claude 一改 `.ts`,测试自动执行,失败输出直接进它的上下文,它会主动修这就是"自给自足循环"—— [[20260627062504 自给自足验证循环配合 TDD]]、[[20260319120100 Hook驱动优于提示词驱动]]。
> 想更严:再加一个 `PreToolUse` 在 git commit 前跑 `tsc --noEmit`,不过则拦截。
---
## 步骤 7 — 用子代理做独立代码审查(隔离上下文)
实现完,派一个**子代理**用全新上下文只看 diff 做审查——它不被你的实现思路"污染",更容易发现问题
两种方式:
**A. 直接用内置/已装的(最简单)**——在主对话里说:
```
用 code-reviewer 子代理审查刚写的 src/shorten.ts 和 src/server.ts,
重点:不可变性、错误处理、base62 边界(n 极大时是否溢出)。
```
**B. 自定义一个项目子代理** `.claude/agents/code-reviewer.md`:
```markdown
---
name: code-reviewer
description: 审查本项目 TS 代码的质量与安全,改完代码后用。聚焦不可变、错误处理、边界。
tools: Read, Grep, Glob, Bash
---
你是资深 TS 评审。只读不改。按严重度(CRITICAL/HIGH/MEDIUM/LOW)输出问题,
每条给文件:行号 + 具体修法。重点:
- 是否原地修改了入参(违反不可变约定)
- 错误是否被静默吞掉
- 数值边界(base62 对超大整数是否丢精度)
- 输入是否在边界校验
最后给一句"可合并 / 需修改"结论。
```
> 何时用子代理 vs 主对话、为什么隔离上下文有效:[[20260320100100 上下文腐烂与全新窗口隔离]]、[[Claude Code 官方多Agent编排最佳实践 (2026)]]。
> 注意:子代理产生大量审查输出但只回摘要,不占用你主线上下文。
---
## 步骤 8 — Commit & PR
测试绿审查过,收尾:
```bash
npx tsc --noEmit && npm test # 最后一道闸
git add -A
git commit -m "feat: 实现 POST /shorten 短链接生成
- shorten() 纯函数 + 注入式 idgen,完全可单测
- base62 7 位短码,http/https 校验,非法输入抛 ValidationError
- 薄 HTTP 层,400/500 区分
- 3 个用例覆盖:合法/非法/确定性"
git push -u origin HEAD
gh pr create --fill
```
Claude 起草 commit/PR 正文时,它会读 `git diff` 总结改动——这也是四阶段的最后一阶段 Commit
---
## 回看:八步对应四阶段
| 步骤 | 四阶段 | 核心动作 |
|---|---|---|
| 02 | (准备) | git init`/init` CLAUDE.md 命令 path-scoped rules |
| 3 | **Explore** | plan mode 只读,定文件拆分 + 测试清单 |
| 4 | **Plan** | TDD 分步计划,Ctrl+G 手改 |
| 5 | **Implement** | RED GREEN 重构,看真实代码 |
| 6 | (Implement 加固) | PostToolUse hook 自动验证 |
| 7 | (Implement 把关) | 子代理隔离审查 |
| 8 | **Commit** | tsc+test 闸门描述性 commitPR |
---
## 常见坑(实操踩过的)
- **跳过 RED**:不看测试失败就写实现,等于没验证测试本身有效务必先跑出红
- **CLAUDE.md 不写命令**:Claude 反复猜测试命令/框架,浪费大量轮次命令是 CLAUDE.md 的第一优先级内容
- **rules 不加 `paths`**:细则全局常驻,白烧上下文 scope scope
- **plan mode 忘了退**:停在 plan mode Claude 改不了文件,会一直只给建议Implement Shift+Tab 切回
- **hook 命令没过滤文件类型**:改个 README 也触发全量测试 `grep '\.ts$'` 限定
- **把所有事塞一个 agent**:复杂流程拆"一任务一代理"更好调试但多数日常编码留主对话即可,别为了用而用(多代理约 15x token)。
---
## Related
- [[Claude Code 新项目开发最佳实践 (2026 调研)]] —— 理论篇 / 出处
- [[Claude Code 多Agent编排 MOC]] —— 编排总入口
- [[20260627062503 Explore-Plan-Implement-Commit 四阶段工作流]]
- [[20260627062504 自给自足验证循环配合 TDD]]
- [[20260627062505 CLAUDE.md 是目录树拼接加载而非覆盖]]
- [[20260308223000 Claude Code Memory 日常最佳实践]]
## Source
- https://code.claude.com/docs/en/best-practices
- https://code.claude.com/docs/en/memory
- https://code.claude.com/docs/en/sub-agents
- https://code.claude.com/docs/en/hooks