如何让 Copilot 帮你写出符合团队工程规范的 使用AI进行旧代码重构的提效路线 核心逻辑

信息图

前言

我是大山哥。

上周帮客户做旧代码重构时,开发工程师小陈抱怨:"大山哥,这堆 jQuery 代码简直是屎山,重构起来太痛苦了!"

我笑了笑:"别急,咱们让 Copilot 来帮忙!"

结果呢?我们用 Copilot 把 10 万行的旧代码重构任务缩短到了一周,而且代码完全符合团队规范!

兄弟,现在重构旧代码,不会用 AI 就落伍了!

今天,我就来分享如何让 Copilot 帮你写出符合团队工程规范的 AI 旧代码重构提效路线。


一、重构流程设计

1.1 重构工作流

flowchart TD
    A[分析旧代码] --> B[制定重构计划]
    B --> C[配置 Copilot 规范]
    C --> D[AI 辅助重构]
    D --> E[代码审查]
    E --> F{符合规范?}
    F -->|否| G[反馈给 AI]
    G --> D
    F -->|是| H[单元测试]
    H --> I{测试通过?}
    I -->|否| J[修复问题]
    J --> H
    I -->|是| K[集成测试]
    K --> L[部署上线]

1.2 规范配置架构

graph LR
    A[团队规范] --> B[代码风格]
    A --> C[命名规范]
    A --> D[设计模式]
    A --> E[架构原则]
    B --> F[ESLint 配置]
    C --> G[命名约定]
    D --> H[组件模式]
    E --> I[目录结构]
    F --> J[Copilot 提示词]
    G --> J
    H --> J
    I --> J

二、团队规范配置

2.1 代码风格配置

{
  "extends": [
    "eslint:recommended",
    "plugin:[用户名]-eslint/recommended",
    "plugin:react/recommended"
  ],
  "rules": {
    "indent": ["error", 2],
    "quotes": ["error", "single"],
    "semi": ["error", "always"],
    "[用户名]-eslint/no-unused-vars": "error",
    "[用户名]-eslint/explicit-function-return-type": "warn",
    "react/react-in-jsx-scope": "off"
  },
  "parser": "[用户名]-eslint/parser",
  "plugins": ["[用户名]-eslint", "react"]
}

2.2 命名规范

const namingRules = {
  components: 'PascalCase',
  functions: 'camelCase',
  classes: 'PascalCase',
  variables: 'camelCase',
  constants: 'UPPER_CASE',
  files: 'kebab-case',
  interfaces: 'PascalCase with I prefix',
  types: 'PascalCase',
  hooks: 'use prefix with camelCase',
};

const directoryStructure = {
  src: [
    'components/',
    'hooks/',
    'utils/',
    'types/',
    'api/',
    'pages/',
    'styles/',
    'tests/',
  ],
};

三、Copilot 提示词工程

3.1 重构提示词模板

const refactorPromptTemplate = `
你是一位资深前端工程师,正在协助进行代码重构。请按照以下规范进行重构:

## 四、团队规范
### 4.1 代码风格
- 使用 TypeScript
- 使用 React 函数组件
- 使用 ESLint 规则(见配置)
- 使用 TailwindCSS 进行样式开发

### 4.2 命名规范
- 组件名:PascalCase(如 UserProfile)
- 函数名:camelCase(如 getUserData)
- 变量名:camelCase(如 userName)
- 常量名:UPPER_CASE(如 API_BASE_URL)
- 文件命名:kebab-case(如 user-profile.tsx)

### 4.3 架构原则
- 组件职责单一
- 使用自定义 Hooks 管理状态逻辑
- 使用 TailwindCSS 工具类
- 类型定义清晰

### 4.4 目录结构
\`\`\`
src/
├── components/     # UI 组件
├── hooks/          # 自定义 Hooks
├── utils/          # 工具函数
├── types/          # 类型定义
├── api/            # API 调用
└── pages/          # 页面组件
\`\`\`

## 五、重构任务
将以下旧代码重构为符合上述规范的现代 React + TypeScript 代码:

--- OLD CODE START ---
{oldCode}
--- OLD CODE END ---

## 六、输出要求
1. 提供完整的重构后代码
2. 包含必要的类型定义
3. 确保代码可以直接运行
4. 添加适当的注释
5. 输出格式为 TypeScript/JSX

## 七、检查清单
- [ ] 类型定义完整
- [ ] 组件职责单一
- [ ] 命名符合规范
- [ ] 代码可运行
- [ ] 包含必要注释
`;

7.1 提示词生成器

interface RefactorOptions {
  oldCode: string;
  targetFramework?: 'react' | 'vue' | 'angular';
  includeTests?: boolean;
  styleGuide?: 'default' | 'airbnb' | 'google';
}

class RefactorPromptGenerator {
  generate(options: RefactorOptions): string {
    const frameworkConfig = this.getFrameworkConfig(options.targetFramework || 'react');
    const styleConfig = this.getStyleConfig(options.styleGuide || 'default');
    
    return `
你是一位资深前端工程师,正在协助进行代码重构。请按照以下规范进行重构:

## 八、目标框架
${frameworkConfig}

## 九、代码风格
${styleConfig}

## 十、重构任务
将以下旧代码重构为符合上述规范的现代代码:

--- OLD CODE START ---
${options.oldCode}
--- OLD CODE END ---

## 十一、输出要求
1. 提供完整的重构后代码
2. 包含必要的类型定义
3. 确保代码可以直接运行
4. 添加适当的注释
${options.includeTests ? '5. 提供单元测试用例' : ''}

## 十二、检查清单
- [ ] 类型定义完整
- [ ] 组件职责单一
- [ ] 命名符合规范
- [ ] 代码可运行
- [ ] 包含必要注释
    `.trim();
  }

  private getFrameworkConfig(framework: string): string {
    const configs: Record<string, string> = {
      react: `
- 使用 React 18 + TypeScript
- 使用函数组件和 Hooks
- 使用 TailwindCSS 进行样式开发
- 使用 React Testing Library 进行测试
      `.trim(),
      vue: `
- 使用 Vue 3 + TypeScript
- 使用 Composition API
- 使用 TailwindCSS 进行样式开发
- 使用 Vue Test Utils 进行测试
      `.trim(),
      angular: `
- 使用 Angular 17 + TypeScript
- 使用组件架构
- 使用 Angular Material 进行样式开发
- 使用 Jasmine 进行测试
      `.trim(),
    };
    return configs[framework] || configs.react;
  }

  private getStyleConfig(style: string): string {
    const configs: Record<string, string> = {
      default: `
- 缩进:2 空格
- 引号:单引号
- 分号:必须
- 变量命名:camelCase
- 常量命名:UPPER_CASE
- 组件命名:PascalCase
      `.trim(),
      airbnb: `
- 缩进:2 空格
- 引号:单引号
- 分号:必须
- 变量命名:camelCase
- 常量命名:UPPER_CASE
- 组件命名:PascalCase
- 函数参数超过 3 个使用对象解构
- 使用箭头函数代替匿名函数
      `.trim(),
      google: `
- 缩进:2 空格
- 引号:双引号
- 分号:必须
- 变量命名:camelCase
- 常量命名:CONSTANT_CASE
- 组件命名:PascalCase
- 文件命名:lowerCamelCase
      `.trim(),
    };
    return configs[style] || configs.default;
  }
}

十三、实战:jQuery 到 React 重构

13.1 旧代码示例

// 旧的 jQuery 代码
$(document).ready(function() {
  const $loginForm = $('#login-form');
  const $username = $('#username');
  const $password = $('#password');
  const $error = $('#error-message');
  
  $loginForm.submit(function(e) {
    e.preventDefault();
    
    const username = $username.val();
    const password = $password.val();
    
    if (!username) {
      $error.text('请输入用户名').show();
      return;
    }
    
    if (!password) {
      $error.text('请输入密码').show();
      return;
    }
    
    $.ajax({
      url: '/api/login',
      method: 'POST',
      data: { username, password },
      success: function(response) {
        if (response.success) {
          window.location.href = '/dashboard';
        } else {
          $error.text(response.message).show();
        }
      },
      error: function() {
        $error.text('网络错误').show();
      }
    });
  });
});

13.2 重构后代码

// ✅ 重构后的 React + TypeScript 代码
import React, { useState } from 'react';

interface LoginFormProps {
  onLoginSuccess?: () => void;
}

interface LoginData {
  username: string;
  password: string;
}

interface LoginResponse {
  success: boolean;
  message?: string;
}

const LoginForm: React.FC<LoginFormProps> = ({ onLoginSuccess }) => {
  const [formData, setFormData] = useState<LoginData>({
    username: '',
    password: '',
  });
  const [error, setError] = useState<string>('');
  const [isLoading, setIsLoading] = useState<boolean>(false);

  const handleInputChange = (
    e: React.ChangeEvent<HTMLInputElement>
  ): void => {
    const { name, value } = e.target;
    setFormData((prev) => ({
      ...prev,
      [name]: value,
    }));
    setError('');
  };

  const validateForm = (): boolean => {
    if (!formData.username.trim()) {
      setError('请输入用户名');
      return false;
    }
    
    if (!formData.password.trim()) {
      setError('请输入密码');
      return false;
    }
    
    return true;
  };

  const handleSubmit = async (e: React.FormEvent): Promise<void> => {
    e.preventDefault();
    
    if (!validateForm()) {
      return;
    }
    
    setIsLoading(true);
    setError('');

    try {
      const response = await fetch('/api/login', {
        method: 'POST',
        headers: {
          'Content-Type': 'application/json',
        },
        body: JSON.stringify(formData),
      });

      const data: LoginResponse = await response.json();

      if (data.success) {
        onLoginSuccess?.();
      } else {
        setError(data.message || '登录失败');
      }
    } catch {
      setError('网络错误,请稍后重试');
    } finally {
      setIsLoading(false);
    }
  };

  return (
    <form onSubmit={handleSubmit} className="space-y-4">
      <div>
        <label htmlFor="username" className="block text-sm font-medium text-gray-700">
          用户名
        </label>
        <input
          type="text"
          id="username"
          name="username"
          value={formData.username}
          onChange={handleInputChange}
          disabled={isLoading}
          className="mt-1 block w-full px-4 py-2 border border-gray-300 rounded-md shadow-sm focus:ring-2 focus:ring-blue-500 focus:border-blue-500"
        />
      </div>

      <div>
        <label htmlFor="password" className="block text-sm font-medium text-gray-700">
          密码
        </label>
        <input
          type="password"
          id="password"
          name="password"
          value={formData.password}
          onChange={handleInputChange}
          disabled={isLoading}
          className="mt-1 block w-full px-4 py-2 border border-gray-300 rounded-md shadow-sm focus:ring-2 focus:ring-blue-500 focus:border-blue-500"
        />
      </div>

      {error && (
        <div className="text-red-500 text-sm">
          {error}
        </div>
      )}

      <button
        type="submit"
        disabled={isLoading}
        className="w-full flex justify-center py-2 px-4 border border-transparent rounded-md shadow-sm text-sm font-medium text-white bg-blue-600 hover:bg-blue-700 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-blue-500 disabled:opacity-50 disabled:cursor-not-allowed"
      >
        {isLoading ? '登录中...' : '登录'}
      </button>
    </form>
  );
};

export default LoginForm;

十四、重构质量保障

14.1 代码审查清单

interface ReviewChecklist {
  naming: boolean;
  types: boolean;
  components: boolean;
  hooks: boolean;
  tests: boolean;
  comments: boolean;
}

class CodeReviewer {
  private rules: Record<string, RegExp> = {
    componentName: /^[A-Z][a-zA-Z0-9]*$/,
    functionName: /^[a-z][a-zA-Z0-9]*$/,
    constantName: /^[A-Z_]+$/,
    hookName: /^use[A-Z][a-zA-Z0-9]*$/,
  };

  review(code: string): ReviewChecklist {
    return {
      naming: this.checkNaming(code),
      types: this.checkTypes(code),
      components: this.checkComponents(code),
      hooks: this.checkHooks(code),
      tests: this.checkTests(code),
      comments: this.checkComments(code),
    };
  }

  private checkNaming(code: string): boolean {
    const componentNames = code.match(/export\s+default\s+function\s+(\w+)/g) || [];
    return componentNames.every(name => {
      const match = name.match(/function\s+(\w+)/);
      return match ? this.rules.componentName.test(match[1]) : true;
    });
  }

  private checkTypes(code: string): boolean {
    return code.includes('interface') || code.includes('type ');
  }

  private checkComponents(code: string): boolean {
    return code.includes('React.FC') || code.includes('React.Component');
  }

  private checkHooks(code: string): boolean {
    const hookCalls = code.match(/use\w+\(/g) || [];
    const validHooks = ['useState', 'useEffect', 'useCallback', 'useMemo', 'useRef'];
    return hookCalls.every(call => 
      validHooks.some(hook => call.startsWith(hook))
    );
  }

  private checkTests(code: string): boolean {
    return code.includes('.test.') || code.includes('.spec.');
  }

  private checkComments(code: string): boolean {
    return code.includes('//') || code.includes('/*');
  }
}

14.2 单元测试

import { render, screen, fireEvent, waitFor } from '@testing-library/react';
import LoginForm from './LoginForm';

describe('LoginForm', () => {
  const mockOnLoginSuccess = jest.fn();

  beforeEach(() => {
    jest.clearAllMocks();
  });

  it('should render form fields', () => {
    render(<LoginForm />);
    
    expect(screen.getByLabelText('用户名')).toBeInTheDocument();
    expect(screen.getByLabelText('密码')).toBeInTheDocument();
    expect(screen.getByRole('button', { name: '登录' })).toBeInTheDocument();
  });

  it('should show error for empty username', async () => {
    render(<LoginForm />);
    
    fireEvent.change(screen.getByLabelText('密码'), { target: { value: 'password123' } });
    fireEvent.submit(screen.getByRole('form'));
    
    await waitFor(() => {
      expect(screen.getByText('请输入用户名')).toBeInTheDocument();
    });
  });

  it('should show error for empty password', async () => {
    render(<LoginForm />);
    
    fireEvent.change(screen.getByLabelText('用户名'), { target: { value: 'user' } });
    fireEvent.submit(screen.getByRole('form'));
    
    await waitFor(() => {
      expect(screen.getByText('请输入密码')).toBeInTheDocument();
    });
  });

  it('should call onLoginSuccess when login succeeds', async () => {
    const mockFetch = jest.spyOn(global, 'fetch').mockResolvedValueOnce({
      json: () => Promise.resolve({ success: true }),
      ok: true,
    } as Response);
    
    render(<LoginForm onLoginSuccess={mockOnLoginSuccess} />);
    
    fireEvent.change(screen.getByLabelText('用户名'), { target: { value: 'user' } });
    fireEvent.change(screen.getByLabelText('密码'), { target: { value: 'password123' } });
    fireEvent.submit(screen.getByRole('form'));
    
    await waitFor(() => {
      expect(mockOnLoginSuccess).toHaveBeenCalled();
    });
    
    mockFetch.mockRestore();
  });
});

十五、避坑指南

  1. 💡 规范先行:在重构前明确团队规范,并配置好提示词
  2. ⚠️ 逐块重构:不要一次性重构所有代码,分模块进行
  3. 测试覆盖:重构后必须运行单元测试
  4. 代码审查:AI 生成的代码必须经过人工审查
  5. 📝 文档更新:更新相关文档和注释

十六、总结

Copilot 是重构旧代码的得力助手,但关键在于如何正确引导它。通过配置清晰的团队规范和高质量的提示词,我们可以让 AI 生成符合要求的代码,大幅提升重构效率。

记住:AI 是工具,规范是灵魂!

更多推荐