首页 › tutorials › Claude Code重构五式:坏味道到设计模式

Claude Code代码重构实战:五种坏味道到设计模式升级

学习目标:掌握使用Claude Code识别和修复五种常见代码坏味道的完整工作流,学会从"发现臭味"到"设计模式升级"的渐进式重构方法,最终能在真实项目中用Claude Code安全地完成跨文件重构。

适用人群:有1年以上开发经验、熟悉TypeScript/Python、正在使用或准备使用Claude Code提升代码质量的开发者。


前置条件

条件 说明
Claude Code CLI 已安装并完成认证(claude --version ≥ 1.0)
项目语言 本教程示例基于TypeScript,方法通用
Git 重构前必须能创建检查点
测试框架 Jest/Vitest/pytest,用于行为保护
CLAUDE.md 项目根目录已有基本配置

为什么Claude Code是重构的最佳搭档

传统重构有三个痛点:识别慢(人眼扫描数百行找坏味道)、修改险(改一处动全局)、验证难(手动跑测试覆盖不全)。Claude Code的独特优势在于它能同时持有整个模块的上下文——100K+ token的上下文窗口意味着它可以"看清"跨文件的依赖关系,而不仅仅是当前文件。

根据Ralphable对12个生产项目的统计,使用Claude Code进行重构的典型收益:

关键数据点:使用Claude Code重构的团队报告生产力提升55-80%,一个210行、圈复杂度16的Python函数被重构为30行以下、复杂度3-6的多个函数。


重构安全网:开始前的准备工作

在执行任何重构之前,必须建立安全网。这是非可选步骤。

第一步:创建Git检查点

# 重构前创建检查点
git add -A && git commit -m "checkpoint: before refactoring session"

第二步:确保测试通过

# 运行现有测试确认基线
pnpm test -- --coverage

# 如果没有测试,让Claude Code先生成特征测试
claude "为 src/utils/permissions.ts 编写特征测试(characterization tests),
覆盖所有分支和边界情况。不要修改源代码,只记录当前行为。"

第三步:配置CLAUDE.md重构规则

在项目根目录的CLAUDE.md中添加重构相关规则:

## 重构规则
- 重构前必须确认所有测试通过
- 每次重构只做一件事,不要混合多种重构手法
- 保持行为不变:重构后所有现有测试必须仍然通过
- 优先使用项目已有的工具库(如Zod用于验证)
- 不要引入新的依赖,除非必要且经过讨论
- 重构完成后运行完整测试套件验证

重构工作流全景

flowchart TD
    A[发现代码坏味道] --> B{有测试保护?}
    B -->|有| D[启动Claude Code重构]
    B -->|没有| C[生成特征测试]
    C --> C1[claude: 编写characterization tests]
    C1 --> C2[运行测试确认基线通过]
    C2 --> D
    D --> E[选择重构手法]
    E --> F1[手法1: 提取组件/函数]
    E --> F2[手法2: 简化条件逻辑]
    E --> F3[手法3: 消除重复代码]
    E --> F4[手法4: 类型安全加固]
    E --> F5[手法5: 设计模式升级]
    F1 --> G[运行测试验证]
    F2 --> G
    F3 --> G
    F4 --> G
    F5 --> G
    G --> H{测试全部通过?}
    H -->|是| I[Git提交重构结果]
    H -->|否| J[回滚到检查点]
    J --> K[分析失败原因]
    K --> D
    I --> L[下一种坏味道]
    L --> A

    style A fill:#ff6b6b,color:#fff
    style D fill:#4ecdc4,color:#fff
    style G fill:#45b7d1,color:#fff
    style I fill:#96ceb4,color:#fff
    style J fill:#ff6b6b,color:#fff

手法一:提取组件/函数——拆解"上帝函数"

识别坏味道

一个函数超过100行、做了超过一件事、包含多个层级的UI或逻辑,就是典型的"上帝函数"(God Function)。

Claude Code提示词模板

提取 [文件路径] 中的 [功能区块] 为独立组件 [组件名]。
要求:
1. Props只传递真正需要的数据,不要prop drilling
2. 保持现有行为完全一致
3. 状态归属于最合适的组件
4. 重构后运行测试验证

实战案例:拆分Dashboard页面

重构前(pages/dashboard.tsx,400行):

export default function Dashboard() {
  const [notifications, setNotifications] = useState<Notification[]>([]);
  const [filter, setFilter] = useState<"all" | "unread">("all");
  // ...200行其他Dashboard逻辑...

  const filtered = notifications.filter(n =>
    filter === "all" ? true : !n.readAt
  );

  return (
    <div className="grid grid-cols-12 gap-6">
      {/* 200行业务内容 */}
      <aside className="col-span-3">
        {/* 80行通知面板逻辑 */}
        <div className="flex items-center justify-between mb-4">
          <h2>Notifications</h2>
          <select value={filter} onChange={e =>
            setFilter(e.target.value as "all" | "unread")
          }>
            <option value="all">All</option>
            <option value="unread">Unread</option>
          </select>
        </div>
        <ul>
          {filtered.map(n => (
            <li key={n.id} className={n.readAt ? "opacity-60" : ""}>
              <p>{n.message}</p>
              <time>{n.createdAt}</time>
            </li>
          ))}
        </ul>
      </aside>
    </div>
  );
}

执行重构:

claude "提取 pages/dashboard.tsx 中的通知面板为独立组件 NotificationPanel。
Props只传notifications数组。过滤状态留在组件内部。
保持行为一致,重构后运行 pnpm test 验证。"

重构后——components/NotificationPanel.tsx:

interface NotificationPanelProps {
  notifications: Notification[];
}

export function NotificationPanel({ notifications }: NotificationPanelProps) {
  const [filter, setFilter] = useState<"all" | "unread">("all");
  const filtered = notifications.filter(n =>
    filter === "all" ? true : !n.readAt
  );

  return (
    <aside className="col-span-3">
      <div className="flex items-center justify-between mb-4">
        <h2 className="text-lg font-semibold">Notifications</h2>
        <select value={filter} onChange={e =>
          setFilter(e.target.value as "all" | "unread")
        }>
          <option value="all">All</option>
          <option value="unread">Unread</option>
        </select>
      </div>
      <ul>
        {filtered.map(n => (
          <li key={n.id} className={n.readAt ? "opacity-60" : ""}>
            <p>{n.message}</p>
            <time>{n.createdAt}</time>
          </li>
        ))}
      </ul>
    </aside>
  );
}

关键决策:Claude Code正确判断了filter状态属于NotificationPanel而非Dashboard——因为过滤只影响通知面板。这种"所有权边界"的判断是手动重构容易遗漏的。

常见问题

问题 解决方案
提取后类型报错 确保新文件有正确的import,类型已导出
测试失败 检查是否有测试直接引用了内部实现细节
Props过多 检查是否需要引入Context而非逐层传递

手法二:简化条件逻辑——消灭"箭头代码"

识别坏味道

函数中有超过3层嵌套的if/else,形成"箭头形"代码(向右缩进越来越深),阅读时需要同时记住多个条件分支。

Claude Code提示词模板

简化 [文件/函数] 中的条件逻辑。
要求:
1. 用early return(卫语句)替代嵌套if/else
2. 提取复杂条件为有意义的命名变量
3. 保持每个分支的行为不变
4. 如果某个分支看起来是bug,加注释说明但不要修改行为

实战案例:权限检查函数

重构前(圈复杂度=6,最大嵌套=4层):

function canUserEdit(user: User, document: Document): boolean {
  if (user.role === "admin") {
    return true;
  } else {
    if (document.ownerId === user.id) {
      if (document.status !== "archived") {
        return true;
      } else {
        if (user.permissions.includes("edit_archived")) {
          return true;
        } else {
          return false;
        }
      }
    } else {
      if (document.collaborators.includes(user.id)) {
        if (document.status === "draft" || document.status === "review") {
          return true;
        } else {
          return false;
        }
      } else {
        return false;
      }
    }
  }
}

执行重构:

claude "简化 src/utils/permissions.ts 中 canUserEdit 函数的条件逻辑。
用early return和卫语句替代嵌套。提取条件为语义化变量。
运行测试确认行为不变。"

重构后(圈复杂度=4,最大嵌套=1层):

function canUserEdit(user: User, document: Document): boolean {
  if (user.role === "admin") return true;

  const isOwner = document.ownerId === user.id;
  const isCollaborator = document.collaborators.includes(user.id);
  const isArchived = document.status === "archived";
  const editableStatuses = ["draft", "review"];

  if (isOwner && !isArchived) return true;
  if (isOwner && isArchived) return user.permissions.includes("edit_archived");
  if (isCollaborator) return editableStatuses.includes(document.status);

  return false;
}

改进量化:

指标 重构前 重构后
代码行数 27行 12行
最大嵌套深度 4层 1层
圈复杂度 6 4
可读性评分 差 优

手法三:消除重复代码——DRY到Schema化

识别坏味道

两个或多个文件中存在几乎相同的逻辑块,通常是复制粘贴后做了微调。修改时需要同步更新多处,容易遗漏。

Claude Code提示词模板

搜索 [目录] 中所有重复的验证逻辑。
提取共享逻辑到 [目标路径] 的工具函数/Schema中。
用Zod schema(如果项目已有)替代手动验证。
更新所有调用点,运行测试。

实战案例:API路由验证去重

重构前——两个路由文件中几乎相同的验证:

// routes/create-project.ts
const body = await req.json();
if (!body.name || typeof body.name !== "string" || body.name.length > 100) {
  return Response.json({ error: "Invalid name" }, { status: 400 });
}
if (!body.teamId || typeof body.teamId !== "string") {
  return Response.json({ error: "Invalid team" }, { status: 400 });
}

// routes/update-project.ts(几乎一样的验证代码)
const body = await req.json();
if (!body.name || typeof body.name !== "string" || body.name.length > 100) {
  return Response.json({ error: "Invalid name" }, { status: 400 });
}
if (!body.teamId || typeof body.teamId !== "string") {
  return Response.json({ error: "Invalid team" }, { status: 400 });
}

执行重构:

claude "找出 routes/ 目录中所有重复的请求验证逻辑。
提取为 lib/validation/project.ts 中的Zod schema。
更新所有路由文件使用schema验证。运行 pnpm test 验证。"

重构后:

// lib/validation/project.ts
import { z } from "zod";

export const projectSchema = z.object({
  name: z.string().min(1).max(100),
  teamId: z.string().min(1),
});
export type ProjectInput = z.infer<typeof projectSchema>;

// routes/create-project.ts
import { projectSchema } from "@/lib/validation/project";

const parsed = projectSchema.safeParse(await req.json());
if (!parsed.success) {
  return Response.json(
    { error: parsed.error.flatten() },
    { status: 400 }
  );
}

Claude Code的智能升级:它不只是"搬代码",而是自动将手动验证升级为Zod schema——更安全、更易维护,还自动生成了类型定义。新增字段只需改一个文件。


手法四:类型安全加固——消灭any类型

识别坏味道

代码中存在大量any类型标注,或完全没有类型标注的函数。这是TypeScript项目中最常见的技术债。

Claude Code提示词模板

为 [文件/模块] 添加严格的TypeScript类型。
要求:
1. 替换所有any为正确的接口定义
2. 从运行时用法推断类型(如果没有现成定义)
3. 不要使用as类型断言——修复底层类型
4. 为泛型参数添加约束

实战案例:API工具函数

重构前:

export async function fetchData(endpoint: string, options?: any): Promise<any> {
  const res = await fetch(`/api/${endpoint}`, options);
  const data: any = await res.json();
  return data;
}

// 30+组件中的用法
const users: any = await fetchData("users");
const projects: any = await fetchData("projects", {
  method: "POST",
  body: JSON.stringify({ name: "New Project" })
});

执行重构:

claude "为 lib/api.ts 添加严格TypeScript类型。
替换所有any。使用泛型让调用方指定返回类型。
不要用as断言。修复底层类型问题。运行 pnpm test 验证。"

重构后:

interface FetchOptions extends Omit<RequestInit, "body"> {
  body?: Record<string, unknown>;
}

export async function fetchData<T>(
  endpoint: string,
  options?: FetchOptions
): Promise<T> {
  const { body, ...rest } = options ?? {};
  const res = await fetch(`/api/${endpoint}`, {
    ...rest,
    body: body ? JSON.stringify(body) : undefined,
    headers: {
      "Content-Type": "application/json",
      ...rest.headers,
    },
  });

  if (!res.ok) {
    throw new ApiError(res.status, await res.text());
  }

  return res.json() as Promise<T>;
}

// 用法——类型安全
const users = await fetchData<User[]>("users");
const project = await fetchData<Project>("projects", {
  method: "POST",
  body: { name: "New Project" }
});

改进量化:


手法五:设计模式升级——从过程式到面向对象

识别坏味道

大量switch/case或if/else链条根据类型做不同处理,每次新增类型都要修改已有代码(违反开闭原则)。

Claude Code提示词模板

将 [函数/模块] 中的switch/case重构为 [策略模式/工厂模式/状态模式]。
要求:
1. 定义统一的接口/抽象类
2. 每种情况实现为独立类
3. 用注册表或工厂方法管理实例
4. 确保新增类型只需添加新类,不修改已有代码
5. 运行测试验证行为不变

实战案例:通知系统重构

重构前(违反开闭原则):

async function sendNotification(type: string, payload: any): Promise<void> {
  switch (type) {
    case "email":
      await emailClient.send({
        to: payload.to,
        subject: payload.subject,
        body: payload.body,
      });
      break;
    case "sms":
      await smsClient.send({
        phone: payload.phone,
        message: payload.message,
      });
      break;
    case "push":
      await pushClient.send({
        userId: payload.userId,
        title: payload.title,
        body: payload.body,
      });
      break;
    case "slack":
      await slackClient.postMessage({
        channel: payload.channel,
        text: payload.text,
      });
      break;
    default:
      throw new Error(`Unknown notification type: ${type}`);
  }
}

执行重构:

claude "将 src/notifications/send.ts 中的sendNotification函数重构为策略模式。
定义Notifier接口,每种通知类型实现为独立类。
用Map注册表替代switch。新增通知类型只需添加新类。
运行测试确认行为不变。"

重构后:

// types.ts
interface Notifier<T = unknown> {
  type: string;
  send(payload: T): Promise<void>;
}

// notifiers/email.ts
interface EmailPayload {
  to: string;
  subject: string;
  body: string;
}

class EmailNotifier implements Notifier<EmailPayload> {
  type = "email";
  async send(payload: EmailPayload): Promise<void> {
    await emailClient.send(payload);
  }
}

// notifiers/sms.ts
interface SmsPayload {
  phone: string;
  message: string;
}

class SmsNotifier implements Notifier<SmsPayload> {
  type = "sms";
  async send(payload: SmsPayload): Promise<void> {
    await smsClient.send(payload);
  }
}

// registry.ts
const notifierRegistry = new Map<string, Notifier>();

export function registerNotifier(notifier: Notifier): void {
  notifierRegistry.set(notifier.type, notifier);
}

export async function sendNotification(
  type: string,
  payload: unknown
): Promise<void> {
  const notifier = notifierRegistry.get(type);
  if (!notifier) throw new Error(`Unknown notification type: ${type}`);
  await notifier.send(payload);
}

// 初始化注册
registerNotifier(new EmailNotifier());
registerNotifier(new SmsNotifier());
registerNotifier(new PushNotifier());
registerNotifier(new SlackNotifier());

开闭原则达成:新增Discord通知只需要创建DiscordNotifier类并注册,无需修改sendNotification函数。


跨文件大规模重构实战

当重构涉及10个以上文件时,需要一套系统化的工作流:

步骤一:让Claude Code先分析影响范围

claude "我要将整个项目中的Plan类型重命名为SubscriptionTier,
因为与Stripe的Plan类型冲突。
请先分析影响范围:列出所有需要修改的文件和位置。"

步骤二:审查计划并确认

Claude Code会返回类似的分析:

影响范围分析:
- 类型定义:types/subscription.ts(1处)
- 组件引用:14个React组件
- API路由:8个路由处理程序
- 测试文件:12个测试文件
- 配置文件:3个(constants、seed、migration)
- 字符串字面量:5处UI文本

总计:83个文件,预计修改~120处

建议分三阶段执行:
Phase 1: 重命名类型定义和导出
Phase 2: 更新所有import和类型引用
Phase 3: 更新字符串字面量和UI文本(需人工确认)

步骤三:分阶段执行

# Phase 1: 核心类型重命名
claude "执行Phase 1:重命名 types/subscription.ts 中的Plan为SubscriptionTier。
不要修改其他文件,只改类型定义和导出。运行测试。"

# 确认通过后
git add -A && git commit -m "refactor: rename Plan to SubscriptionTier (phase 1)"

# Phase 2: 更新所有引用
claude "执行Phase 2:更新所有14个组件、8个API路由、12个测试文件中的
Plan类型引用为SubscriptionTier。更新import语句。
运行 pnpm test 验证。"

git add -A && git commit -m "refactor: update all references (phase 2)"

# Phase 3: 字符串字面量(需要人工判断)
claude "执行Phase 3:列出所有包含'Plan'字符串的UI文本和字符串字面量。
不要自动修改——输出列表让我决定哪些需要改。"

步骤四:最终验证

# 运行完整测试套件
pnpm test

# TypeScript类型检查
pnpm tsc --noEmit

# 代码搜索确认无遗漏
claude "搜索整个项目,确认没有遗漏的Plan引用
(排除node_modules和.lock文件)。只报告类型相关的引用,
不报告注释中的。"

构建重构专用CLAUDE.md命令

将常用的重构流程封装为自定义命令,避免每次重复输入提示词:

创建.claude/commands/refactor-extract.md:

提取 $ARGUMENTS 中的指定功能区块为独立模块。

执行步骤:
1. 读取目标文件,理解当前结构
2. 识别要提取的代码边界
3. 创建新文件,将代码移入
4. 更新原文件的import和引用
5. 运行 pnpm test 验证行为不变
6. 报告修改的文件列表和测试结果

约束:
- Props/参数只传递必要数据
- 状态归属于最合适的模块
- 不改变任何外部可见行为

使用方式:

claude "/refactor-extract pages/dashboard.tsx 中的通知面板"

重构后的量化验证

每次重构完成后,用以下指标量化改进:

claude "分析以下文件的重构改进,输出量化报告:
- 圈复杂度变化
- 代码行数变化
- 函数/组件数量变化
- 类型安全覆盖率变化
- 重复代码比例变化

对比文件:[重构前文件] 和 [重构后文件]"

典型输出:

重构量化报告:
┌─────────────────┬──────────┬──────────┬──────────┐
│ 指标             │ 重构前   │ 重构后   │ 变化     │
├─────────────────┼──────────┼──────────┼──────────┤
│ 圈复杂度(最大)   │ 16       │ 4        │ -75%     │
│ 代码行数         │ 210      │ 85       │ -60%     │
│ any类型数量      │ 12       │ 0        │ -100%    │
│ 重复代码块       │ 8        │ 0        │ -100%    │
│ 函数平均长度     │ 45行     │ 12行     │ -73%     │
│ 测试覆盖率       │ 34%      │ 89%      │ +162%    │
└─────────────────┴──────────┴──────────┴──────────┘

常见陷阱与避坑指南

陷阱一:一次性重构太多

# 错误做法:一个提示词要求重构整个模块
claude "重构整个 auth/ 模块,让它更干净"  # ❌ 结果不可控

# 正确做法:一次只做一种重构
claude "只用'提取函数'手法重构 auth/validate.ts,
不要做其他修改"  # ✅ 行为可预测

陷阱二:跳过测试直接重构

# 错误做法:直接改代码
claude "把这个switch改成策略模式"  # ❌ 没有安全网

# 正确做法:先确保测试覆盖
claude "先为 sendNotification 写特征测试覆盖所有分支。
测试通过后,再将switch重构为策略模式。"  # ✅ 安全重构

陷阱三:不审查直接接受

Claude Code的重构结果应该像初级开发者的PR一样审查:

  1. 检查边界条件:Claude Code可能遗漏null/undefined检查
  2. 检查性能:提取函数可能引入不必要的闭包或重复计算
  3. 检查命名:自动生成的变量名可能不够语义化
  4. 检查import:跨文件重构时容易遗漏或多余import

延伸阅读


训练营适用性评估

维度 说明
适合训练营类型 企业内训、高级开发者训练营、代码质量提升工作坊
学员基础要求 1年以上TypeScript/Python经验,了解基本设计模式
预计教学时长 4-6小时(含实操练习)
实操占比 70%(每种手法配一个现场练习)
课后作业 选择自己项目中的真实代码坏味道,用五步法重构并提交量化报告
难度评级 中高级

参考来源

  1. Claude Code Prompts for Refactoring: 12 Battle-Tested Patterns — 12种经过实战验证的Claude Code重构提示词模式,含完整before/after代码示例
  2. Claude Code Skills Complete Guide (2026) — Duet发布的Claude Code Skills完整指南,涵盖SKILL.md格式、MCP对比、团队共享
  3. Code Refactoring With Claude AI: Ultimate Guide — Claude AI重构完整工作流,含特征测试、安全网、量化指标
  4. Claude Code Best Practices: 15 Tips from 6 Projects — 来自6个生产项目的15条最佳实践,CLAUDE.md配置到工作流全覆盖
  5. SKILL.md Files: What They Are, How to Write One — SKILL.md文件格式详解,覆盖上下文、指令、约束三段式结构

[!tip] 51学通信知识星球 星主:爱卫生(微信:gprshome201101)——公众号「51学通信」创办者,19 年通信行业运维与讲师经验,著有《GPRS网络信令实例详解》《EPC原理与实践》《5G核心网原理与实践》三本纸质书(各大电商平台有售)。 星球定位:深耕 5G 核心网、IMS、NFV/AI、云原生等 ICT 领域,主打运维岗位与信令分析。 当前规模:5200 篇主题 / 990 个回答,198 元/年。 加入福利:加入星球后请加爱卫生微信,拉您进知识星球年度陪伴与答疑群——不限量回复您的各种问题,另有群内分享。 AI 加持:51学通信知识星球已作为 skill 接入 WorkBuddy,使用 deepseek-4-flash 在海量星球帖子中精准找贴、找视频、找 FAQ 和案例,做总结、找文档和 PPT 等场景全覆盖。

51学通信知识星球海报-扫码加入