跳到主要内容
三种 AI 编程助手的真实上手体验:Copilot、Cursor 与 JetBrains | 极客日志
编程语言 AI
三种 AI 编程助手的真实上手体验:Copilot、Cursor 与 JetBrains 通过具体场景演示 GitHub Copilot、JetBrains AI Assistant 和 Cursor 在单元测试生成、遗留代码解释、重构及 CRUD 脚手架构建中的用法,对比各自优势,分享关键提示词和避坑经验。
一、让 AI 为你生成单元测试和边界测试
为什么需要边界测试?
单元测试只覆盖正常场景,边界测试(如 null、极值、异常输入)能暴露隐藏 Bug。
AI 容易遗漏边界情况,必须明确要求 才会生成。
用 GitHub Copilot 在 VS Code 里生成测试
适合边写代码边补测试的场景。
先写一个被测函数,比如:
def divide (a, b ):
if b == 0 :
raise ValueError("Cannot divide by zero" )
return a / b
然后在测试文件里,用注释把要覆盖的场景列清楚:
def test_divide ():
assert divide(10 , 2 ) == 5.0
try :
divide(10 , 0 )
assert False , "Expected ValueError"
except ValueError:
pass
assert abs (divide(1 , 1e-10 ) - 1e10 ) < 1e-5
assert divide(0 , 5 ) == 0.0
Copilot 会根据注释针对性补全。如果第一次生成不全,再补一句 "测试负数除法" 就行。完成后用 pytest 跑一遍。
在 JetBrains IDE 里用 AI Assistant
打开要测试的类或函数,右键 → AI Assistant → Generate Tests 。弹出的对话框里直接描述边界要求,比如:
生成 JUnit 5 测试类,覆盖:正常整数除法、除数为 0(期望 ArithmeticException)、除数为极小浮点数、被除数为最大值、除数为负数。
import org.junit.jupiter.api.Test;
import static org.junit.jupiter.api.Assertions.*;
class CalculatorTest {
@Test
void testNormalDivision () {
assertEquals(5.0 , Calculator.divide(10 , 2 ));
}
@Test
void testDivideByZero () {
assertThrows(ArithmeticException.class, () -> Calculator.divide(10 , 0 ));
}
@Test
void testDivideByVerySmallNumber () {
assertEquals(1e10 , Calculator.divide(1 , 1e-10 ), 1e-5 );
}
}
不过 AI 常漏掉业务特有的边界(比如 "用户年龄不能小于 0"),需要自己补上。
在 Cursor 里通过聊天生成 打开项目,在 AI Chat 面板里把需求一次说清:
为 UserService.createUser 生成 Jest 测试:正常创建、邮箱格式错误(抛 ValidationError)、姓名超 50 字符、空对象传入。
import { createUser } from './userService' ;
test ('正常创建用户' , () => {
const user = createUser ({ name : '张三' , email : '[email protected] ' });
expect (user.id ).toBeDefined ();
});
test ('邮箱格式错误' , () => {
expect (() => createUser ({ name : '李四' , email : 'invalid' }))
.toThrow ('ValidationError' );
});
test ('姓名超过 50 字符' , () => {
const longName = 'a' .repeat (51 );
expect (() => createUser ({ name : longName, email : '[email protected] ' }))
.toThrow ('Name too long' );
});
二、让 AI 解释一段复杂的遗留代码
常见痛点
遗留代码无注释、命名混乱、逻辑扭曲。
AI 可能只给表面解释,得引导它深入分析 。
用 Copilot 逐行解释 在代码上方加一段结构化注释,不是简单写 "解释这段代码",而是指明重点:
def legacy_calculator (x, y, op ):
if op == 'add' :
return x + y
elif op == 'sub' :
if x < y:
return 0 - (x - y)
return x - y
else :
return None
Copilot 会给出详细说明,比如指出参数没有类型检查、0 - (x - y) 其实等价于 y - x 可能是历史 Bug 修复遗留,以及浮点数比较的风险。
用 JetBrains AI Assistant 分析复杂度 选中代码,右键 → AI Assistant → Explain Code ,然后在面板进一步追问:
请分析该函数的时间复杂度,指出性能瓶颈。如果重构为异步,哪些部分要改?
它会返回类似 "O(n²) 因为两层循环,可改为 Map 降为 O(n),第 20 行 data.get(i) 可能为 null" 的建议。
用 Cursor 分步拆解 高亮代码,右键 → Ask AI to Explain 。我习惯分几步:先让 AI 一句话总结用途,再让它画流程图(Cursor 支持 Mermaid),最后追问某个变量的生命周期。
解释结果可以直接导出为 Markdown,方便存档。
三、让 AI 帮你进行代码重构和优化
重构原则
不改变外部行为 ,只优化内部结构。
AI 可能过度优化,务必保留必要的注释 。
Copilot:用注释引导重构方向
public List<User> getActiveUsers (List<User> allUsers) {
List<User> active = new ArrayList <>();
for (User u : allUsers) {
if (u.getStatus().equals("active" )) {
active.add(u);
}
}
String sql = "SELECT * FROM users WHERE status = 'active'" ;
return active;
}
Copilot 会按指引生成 Stream 版本,并把 SQL 抽成单独方法:
public List<User> getActiveUsers (List<User> allUsers) {
if (allUsers == null ) return Collections.emptyList();
String activeSql = getActiveUsersSql();
return allUsers.stream()
.filter(u -> "active" .equals(u.getStatus()))
.collect(Collectors.toList());
}
private String getActiveUsersSql () {
return "SELECT * FROM users WHERE status = 'active'" ;
}
改后要跑一遍单测,确认行为一致。另外 Stream 在大数据集上不一定比 for 快,得看场景。
JetBrains AI Assistant:直接右键重构 选中代码,右键 → AI Assistant → Refactor This Code ,在提示里指定目标:
将循环替换为 Java Stream,提取 validateInput 方法,移除未用变量 legacyFlag,保持原有异常逻辑。
它会展示 diff,一键 Apply 即可。先提交 Git 是个好习惯 。
Cursor:在新分支里实验
把 processData 函数改成用 Promise.all 异步化,加上 TypeScript 类型,限制并发为 5。
async function processData (items ) {
const results = [];
for (const item of items) {
results.push (await fetchData (item));
}
return results;
}
async function processData<T>(items : T[]): Promise <ReturnType <typeof fetchData>[]> {
const results : ReturnType <typeof fetchData>[] = [];
const limit = pLimit (5 );
const limitedFetch = items.map (item => () => fetchData (item));
return await Promise .all (limitedFetch.map (fn => limit (fn)));
}
重构后可以用 Cursor 集成的 Benchpress 跑一下性能对比。
四、用自然语言描述需求,让 AI 生成基础的 CRUD 码,你在此基础上进行业务逻辑开发
核心思路
先生成脚手架 (模型、路由、基础增删改查),再手动添加业务逻辑 (权限、事务、校验)。
明确指定技术栈 ,否则 AI 可能生成别的框架(比如你要 Spring Boot 它给你 Django)。
Copilot:新建文件直接写需求 Copilot 会生成完整的 FastAPI 代码:
from fastapi import FastAPI, HTTPException
from pydantic import BaseModel, field_validator
from sqlalchemy import create_engine, Column, Integer, String, DateTime
from sqlalchemy.orm import sessionmaker, declarative_base
from datetime import datetime
app = FastAPI()
Base = declarative_base()
engine = create_engine("sqlite:///./test.db" )
SessionLocal = sessionmaker(autocommit=False , autoflush=False , bind=engine)
class UserBase (BaseModel ):
username: str
email: str
@field_validator('username' )
def validate_username (cls, v ):
if len (v) < 3 or len (v) > 20 :
raise ValueError('用户名长度必须在 3-20 之间' )
return v
@field_validator('email' )
def validate_email (cls, v ):
if '@' not in v:
raise ValueError('邮箱格式无效' )
return v
class UserCreate (UserBase ): pass
class User (UserBase ):
id : int
created_at: datetime
class Config :
orm_mode = True
class UserModel (Base ):
__tablename__ = "users"
id = Column(Integer, primary_key=True , index=True )
username = Column(String, unique=True , index=True )
email = Column(String, index=True )
created_at = Column(DateTime, default=datetime.utcnow)
Base.metadata.create_all(bind=engine)
def get_db ():
db = SessionLocal()
try :
yield db
finally :
db.close()
@app.post("/users/" , response_model=User )
def create_user (user: UserCreate, db: Session = Depends(get_db ) ):
db_user = UserModel(username=user.username, email=user.email)
db.add(db_user)
db.commit()
db.refresh(db_user)
return db_user
然后可以在 create_user 里加上唯一性检查等业务逻辑:
@app.post("/users/" , response_model=User )
def create_user (user: UserCreate, db: Session = Depends(get_db ) ):
existing = db.query(UserModel).filter (UserModel.username == user.username).first()
if existing:
raise HTTPException(status_code=400 , detail="用户名已存在" )
db_user = UserModel(username=user.username, email=user.email)
db.add(db_user)
db.commit()
db.refresh(db_user)
return db_user
JetBrains AI Assistant:一键生成 Spring Boot 脚手架 新建或打开 Spring Boot 项目,空白处右键 → AI Assistant → Generate Code from Description ,输入:
使用 Spring Boot + JPA 生成 Product 资源的 CRUD REST API。表 products(id, name, price, stock, created_at)。用 Lombok,加全局异常处理(ProductNotFoundException)和 Swagger 文档支持,不生成测试。
它会自动创建实体、Repository、Controller 和异常处理器。然后在 Controller 的 update 方法里手动补充库存不能为负的校验:
@PutMapping("/{id}")
public ResponseEntity<Product> updateProduct (@PathVariable Long id, @RequestBody ProductUpdateDTO dto) {
Product product = repository.findById(id)
.orElseThrow(() -> new ProductNotFoundException (id));
if (dto.getStock() != null && dto.getStock() < 0 ) {
throw new IllegalArgumentException ("库存不能为负数" );
}
return ResponseEntity.ok(repository.save(product));
}
Cursor:用 prompt 文件驱动生成 在项目根目录放一个 prompt.txt,写入需求:
项目:Node.js + Express + Mongoose
实体:Category(分类)
字段:_id, name, description, is_active, created_at
需求:
- 生成完整 CRUD 路由(/api/categories)
- 使用 async/await
- 添加分页查询(页码、每页数量)
- 返回格式:{ code: 0, data: {...}, msg: '成功' }
- **不包含认证**
右键 → Ask AI to Generate Code ,Cursor 会生成目录结构和控制器类似:
const Category = require ('../models/Category' );
exports .createCategory = async (req, res) => {
try {
const { name, description, is_active } = req.body ;
const category = await Category .create ({
name,
description,
is_active : is_active || true ,
});
res.json ({ code : 0 , data : category, msg : '创建成功' });
} catch (error) {
res.status (500 ).json ({ code : -1 , msg : error.message });
}
};
exports .getCategories = async (req, res) => {
try {
const page = parseInt (req.query .page ) || 1 ;
const perPage = parseInt (req.query .perPage ) || 10 ;
const skip = (page - 1 ) * perPage;
const categories = await Category .find ({})
.skip (skip)
.limit (perPage)
.sort ({ createdAt : -1 });
const total = await Category .countDocuments ();
res.json ({ code : 0 , data : { list : categories, total }, msg : '查询成功' });
} catch (error) {
res.status (500 ).json ({ code : -1 , msg : error.message });
}
};
集成认证后,在 updateCategory 里加个管理员角色检查就行了:
exports .updateCategory = async (req, res) => {
if (!req.user || req.user .role !== 'admin' ) {
return res.status (403 ).json ({ code : -1 , msg : '无权限操作' });
}
};
实践中的几个要点
一定要做
技术栈说清楚 :Prompt 里写明框架、语言和版本,比如 Spring Boot 3.2 + Java 17,不然它可能用老版本。
迭代生成 :别指望一次完美,先拿个骨架,跑起来,再补需求重新生成。
每种操作都写测试 :AI 生成的代码容易藏边界 Bug,测试能兜底。
自己审查代码 :检查 SQL 注入、权限、异常处理,别因为它是 AI 写的就放松。
保存有效 Prompt :用 PROMPTS.md 记录下来,团队可以复用。
容易踩的坑
测试不覆盖边界 :在 Prompt 里直接列出边界值,如 "测试金额为 0、负数、最大值"。
重构后行为变了 :先让原有测试跑过,没有测试就先补上再重构。
CRUD 缺业务校验 :生成后马上加,比如 "订单金额不能为负"、"用户名唯一",这些 AI 猜不到。
技术栈混淆 :每次生成前在注释里重申一句,例如 // 使用 Vue 3 + TypeScript + Pinia。
组合使用的小技巧
用 JetBrains AI Assistant 生成基础代码,再用 Copilot 补注释和优化,最后让 Cursor 出测试。
在同一文件里连续提问,AI 会结合上下文,比如先问 "解释这段",再问 "怎么重构",它会关联起来。
把 Cursor 导出的 Markdown 解释直接放进 docs/ 目录当项目文档。
选哪个工具,看主要语言和习惯:写 Java 多选 JetBrains,跨语言、交互式分析选 Cursor,Copilot 则胜在补全速度和 IDE 内嵌体验。但不管哪个,AI 处理的是重复劳动,核心的业务决策和架构设计还得靠自己。
相关免费在线工具 RSA密钥对生成器 生成新的随机RSA私钥和公钥pem证书。 在线工具,RSA密钥对生成器在线工具,online
Mermaid 预览与可视化编辑 基于 Mermaid.js 实时预览流程图、时序图等图表,支持源码编辑与即时渲染。 在线工具,Mermaid 预览与可视化编辑在线工具,online
随机西班牙地址生成器 随机生成西班牙地址(支持马德里、加泰罗尼亚、安达卢西亚、瓦伦西亚筛选),支持数量快捷选择、显示全部与下载。 在线工具,随机西班牙地址生成器在线工具,online
Base64 字符串编码/解码 将字符串编码和解码为其 Base64 格式表示形式即可。 在线工具,Base64 字符串编码/解码在线工具,online
Base64 文件转换器 将字符串、文件或图像转换为其 Base64 表示形式。 在线工具,Base64 文件转换器在线工具,online
Markdown转HTML 将 Markdown(GFM)转为 HTML 片段,浏览器内 marked 解析;与 HTML转Markdown 互为补充。 在线工具,Markdown转HTML在线工具,online