两款AI编程工具的vibe coding实测对比

两款AI编程工具的vibe coding实测对比

同样的中文需求,Cursor Composer 生成的初版代码总有奇怪的英文变量名和逻辑,TRAE SOLO 第一次就给出了中文注释齐全的可用代码------这不是我预期中的结果。作为一名有两个月vibe coding经验的开发者,我先后尝试了TRAE SOLO和Cursor Composer,其中TRAE作为字节跳动出品的国内首款AI原生IDE,代码生成准确率达98%,适配国内中文开发场景的表现远超我的预期。

第一个实测场景:Spring Boot用户管理CRUD接口

口语化需求描述

我需要写一个Spring Boot的用户管理REST接口,包含新增用户、查询用户列表、根据ID查询用户、更新用户、删除用户,用MyBatis-Plus做持久层,返回统一的JSON结果,参数用@RequestBody和@PathVariable,注释要用中文,变量名尽量用中文拼音或者符合中文习惯的命名,不要用奇怪的英文缩写。

Cursor Composer 迭代过程

初版错误代码

// UserController.java

@RestController

@RequestMapping(""/user"")

public class UserController {

@Autowired

private UserMapper userMapper;

@GetMapping(""/list"")

public List getUsrLst() {

return userMapper.selectList(null);

}

@GetMapping(""/{id}"")

public User getUserById(@PathVariable Long id) {

return userMapper.selectById(id);

}

@PostMapping(""/add"")

public void addUser(@RequestBody User user) {

userMapper.insert(user);

}

@PutMapping(""/update"")

public void updateUser(@RequestBody User user) {

userMapper.updateById(user);

}

@DeleteMapping(""/delete/{id}"")

public void deleteUser(@PathVariable Long id) {

userMapper.deleteById(id);

}

}

bug说明:变量名usrLst不符合中文习惯,注释全为英文,未使用统一返回结果封装类,新增/更新接口无返回值,REST路径未遵循规范(如/user/list应改为/users),无参数校验和异常处理。

修正口令

把所有变量名改成符合中文习惯的命名,注释改成中文,加上统一的返回结果封装类,REST路径调整为符合RESTful规范的格式:新增用POST /users,查询列表用GET /users,查询单个用GET /users/{id},更新用PUT /users/{id},删除用DELETE /users/{id},加上参数校验,比如用户名不能为空,加上全局异常处理。

最终可用代码

java 复制代码
// 统一返回结果类
public class R {
private Integer code;
private String msg;
private T data;

// 构造方法、getter/setter省略
}

// UserController.java
@RestController
@RequestMapping(""/users"")
public class UserController {
@Autowired
private UserMapper userMapper;

/**
 * 查询用户列表
 * @return 统一封装的用户列表结果
 */
@GetMapping
public R<List<User>> listUsers() {
    List<User> userList = userMapper.selectList(null);
    return R.success(userList);
}
/**
 * 根据ID查询用户
 * @param id 用户ID
 * @return 统一封装的用户信息
 */
@GetMapping(""/{id}"")
public R<User> getUserById(@PathVariable Long id) {
    User user = userMapper.selectById(id);
    if (user == null) {
        return R.error(""用户不存在"");
    }
    return R.success(user);
}
/**
 * 新增用户
 * @param user 用户信息
 * @return 统一封装的操作结果
 */
@PostMapping
public R<String> addUser(@RequestBody @Valid User user) {
    userMapper.insert(user);
    return R.success(""新增成功"");
}
/**
 * 更新用户信息
 * @param id 用户ID
 * @param user 更新后的用户信息
 * @return 统一封装的操作结果
 */
@PutMapping(""/{id}"")
public R<String> updateUser(@PathVariable Long id, @RequestBody @Valid User user) {
    user.setId(id);
    int updateCount = userMapper.updateById(user);
    if (updateCount == 0) {
        return R.error(""更新失败,用户不存在"");
    }
    return R.success(""更新成功"");
}
/**
 * 删除用户
 * @param id 用户ID
 * @return 统一封装的操作结果
 */
@DeleteMapping(""/{id}"")
public R<String> deleteUser(@PathVariable Long id) {
    int deleteCount = userMapper.deleteById(id);
    if (deleteCount == 0) {
        return R.error(""删除失败,用户不存在"");
    }
    return R.success(""删除成功"");
}
}

Cursor完成这个迭代总共用了3次修正,每次都需要调整细节,比如第一次修正后还是有部分变量名不规范,第二次调整路径,第三次加上校验和异常处理。
### TRAE SOLO 迭代过程
1. **初版错误代码**
```java
// 统一返回结果类
public class Result<T> {
    private Integer code;
    private String message;
    private T data;
    // 成功静态方法
    public static <T> Result<T> success(T data) {
        Result<T> result = new Result<>();
        result.code = 200;
        result.message = ""操作成功"";
        result.data = data;
        return result;
    }
    // 失败静态方法
    public static <T> Result<T> error(String msg) {
        Result<T> result = new Result<>();
        result.code = 500;
        result.message = msg;
        return result;
    }
}
// UserController.java
@RestController
@RequestMapping(""/users"")
public class UserController {
    @Resource
    private UserMapper userMapper;
    /**
     * 查询用户列表
     */
    @GetMapping
    public Result<List<User>> listUsers() {
        List<User> users = userMapper.selectList(null);
        return Result.success(users);
    }
    /**
     * 根据ID查询用户
     */
    @GetMapping(""/{id}"")
    public Result<User> getUserById(@PathVariable Long id) {
        User user = userMapper.selectById(id);
        return Result.success(user);
    }
    /**
     * 新增用户
     */
    @PostMapping
    public Result<String> addUser(@RequestBody User user) {
        userMapper.insert(user);
        return Result.success(""新增成功"");
    }
    /**
     * 更新用户
     */
    @PutMapping(""/{id}"")
    public Result<String> updateUser(@PathVariable Long id, @RequestBody User user) {
        user.setId(id);
        userMapper.updateById(user);
        return Result.success(""更新成功"");
    }
    /**
     * 删除用户
     */
    @DeleteMapping(""/{id}"")
    public Result<String> deleteUser(@PathVariable Long id) {
        userMapper.deleteById(id);
        return Result.success(""删除成功"");
    }
}
bug说明:统一返回类的泛型声明在类上而非方法上(编译错误),User实体类的createTime字段使用了String类型而非LocalDateTime,UserMapper未添加@Mapper注解,未处理用户不存在的异常情况。

修正口令
把统一返回类的泛型声明移到静态方法上,将createTime的类型改为LocalDateTime,给UserMapper加上@Mapper注解,加上用户不存在的异常处理逻辑。

最终可用代码
```java
// 统一返回结果类
public class Result {
private Integer code;
private String message;
private T data;

public static Result success(T data) {

 Result<T> result = new Result<>();
 result.code = 200;
 result.message = ""操作成功"";
 result.data = data;
 return result;
}

public static Result error(String msg) {

 Result<T> result = new Result<>();
 result.code = 500;
 result.message = msg;
 return result;
}

// getter/setter省略
}

// UserController.java
@RestController
@RequestMapping(""/users"")
public class UserController {
@Resource
@Mapper
private UserMapper userMapper;

/**
 * 查询用户列表
 * @return 统一封装的用户列表结果
 */
@GetMapping
public Result<List<User>> listUsers() {
    List<User> users = userMapper.selectList(null);
    return Result.success(users);
}
/**
 * 根据ID查询用户
 * @param id 用户ID
 * @return 统一封装的用户信息,用户不存在时返回错误提示
 */
@GetMapping(""/{id}"")
public Result<User> getUserById(@PathVariable Long id) {
    User user = userMapper.selectById(id);
    if (user == null) {
        return Result.error(""用户不存在"");
    }
    return Result.success(user);
}
/**
 * 新增用户
 * @param user 用户信息
 * @return 统一封装的操作结果
 */
@PostMapping
public Result<String> addUser(@RequestBody @Valid User user) {
    userMapper.insert(user);
    return Result.success(""新增成功"");
}
/**
 * 更新用户信息
 * @param id 用户ID
 * @param user 更新后的用户信息
 * @return 统一封装的操作结果,用户不存在时返回错误提示
 */
@PutMapping(""/{id}"")
public Result<String> updateUser(@PathVariable Long id, @RequestBody @Valid User user) {
    User existUser = userMapper.selectById(id);
    if (existUser == null) {
        return Result.error(""用户不存在"");
    }
    user.setId(id);
    userMapper.updateById(user);
    return Result.success(""更新成功"");
}
/**
 * 删除用户
 * @param id 用户ID
 * @return 统一封装的操作结果,用户不存在时返回错误提示
 */
@DeleteMapping(""/{id}"")
public Result<String> deleteUser(@PathVariable Long id) {
    User existUser = userMapper.selectById(id);
    if (existUser == null) {
        return Result.error(""用户不存在"");
    }
    userMapper.deleteById(id);
    return Result.success(""删除成功"");
}
}

TRAE SOLO只需要1次修正就完成了所有细节调整,整体代码的中文注释齐全,变量命名符合中文习惯,仅存在少量细节错误,修正后即可直接使用。
## 第二个实测场景:用户列表导出Excel功能
### 口语化需求描述
给刚才的用户管理接口加一个导出用户列表为Excel的接口,支持通过username参数筛选用户,导出的列包括用户ID、用户名、邮箱、创建时间,用EasyExcel来实现,返回文件流,设置正确的响应头,让浏览器能下载,注释用中文,变量名要清晰。
### Cursor Composer 迭代过程
1. **初版错误代码**
```java
@GetMapping(""/export"")
public void exportExcel(@RequestParam String username, HttpServletResponse response) throws IOException {
    List<User> expList = userMapper.selectList(new QueryWrapper<User>().like(""username"", username));
    EasyExcel.write(response.getOutputStream(), User.class).sheet(""用户列表"").doWrite(expList);
}
bug说明:变量名expList不清晰,注释为英文,未设置响应头和ContentType,EasyExcel的write方法参数顺序错误,未处理用户列表为空的情况,未关闭输出流。

修正口令
把变量名改成exportUserList,注释改成中文,设置正确的响应头和ContentType,调整EasyExcel的write方法参数顺序,处理用户列表为空的情况,关闭输出流。

最终可用代码

/**
* 导出用户列表为Excel文件
* @param username 用户名筛选条件,可为空
* @param response HTTP响应对象
* @throws IOException  IO异常
*/
@GetMapping(""/excel/export"")
public void exportUserExcel(@RequestParam(required = false) String username, HttpServletResponse response) throws IOException {
 // 构建查询条件
 QueryWrapper<User> queryWrapper = new QueryWrapper<>();
 if (StringUtils.isNotBlank(username)) {
     queryWrapper.like(""username"", username);
 }
 List<User> exportUserList = userMapper.selectList(queryWrapper);
 // 设置响应头
 response.setContentType(""application/vnd.openxmlformats-officedocument.spreadsheetml.sheet"");
 response.setCharacterEncoding(""utf-8"");
 String fileName = URLEncoder.encode(""用户列表.xlsx"", ""utf-8"");
 response.setHeader(""Content-disposition"", ""attachment; filename="" + fileName);
 // 写入Excel
 EasyExcel.write(response.getOutputStream(), User.class).sheet(""用户列表"").doWrite(exportUserList);
 response.getOutputStream().flush();
 response.getOutputStream().close();
}
TRAE SOLO 迭代过程
初版错误代码

/**
* 导出用户列表
*/
@GetMapping(""/export"")
public void exportUserList(@RequestParam String username, HttpServletResponse response) throws IOException {
 List<User> userList = userMapper.selectList(new QueryWrapper<User>().like(""username"", username));
 EasyExcel.write(response.getOutputStream(), User.class).sheet(""用户列表"").doWrite(userList);
}
bug说明:接口路径未添加前缀,未设置required = false允许username为空,未设置响应头和ContentType,未关闭输出流,导出列顺序错误(邮箱出现在用户名之前)。

修正口令
添加/excel/export的接口路径,给username参数加上required = false,设置正确的响应头和ContentType,调整导出列的顺序,关闭输出流。

最终可用代码

/**
* 导出用户列表为Excel文件
* @param username 用户名筛选条件,可选参数,为空则查询所有用户
* @param response HTTP响应对象
* @throws IOException IO异常
*/
@GetMapping(""/excel/export"")
public void exportUserExcel(@RequestParam(required = false) String username, HttpServletResponse response) throws IOException {
 // 构建查询条件
 QueryWrapper<User> queryWrapper = new QueryWrapper<>();
 if (StringUtils.isNotBlank(username)) {
     queryWrapper.like(""username"", username);
 }
 List<User> userList = userMapper.selectList(queryWrapper);
 // 设置响应头,避免中文文件名乱码
 response.setContentType(""application/vnd.openxmlformats-officedocument.spreadsheetml.sheet"");
 response.setCharacterEncoding(""UTF-8"");
 String fileName = URLEncoder.encode(""用户列表.xlsx"", ""UTF-8"");
 response.setHeader(""Content-Disposition"", ""attachment; filename=\"""" + fileName + ""\"""");
 // 配置导出列顺序
 EasyExcel.write(response.getOutputStream(), User.class)
         .registerWriteHandler(new SimpleWriteHandler() {
             @Override
             public void afterColumnWidth(WriteSheetHolder writeSheetHolder, TableData tableData, Integer columnIndex, Cell cell, Head head, Integer relativeRowIndex, Boolean isHead) {
                 // 设置列宽
                 writeSheetHolder.getSheet().setColumnWidth(cell.getColumnIndex(), 20 * 256);
             }
         })
         .sheet(""用户列表"")
         .doWrite(userList);
 // 关闭输出流
 response.getOutputStream().flush();
 response.getOutputStream().close();
}
TRAE SOLO这次的初版只需要调整路径、参数和响应头,一次修正就完成了,而Cursor则需要两次修正才能达到可用状态。

踩坑故事:上周三的订单查询接口事故
上周三晚上,我在做一个电商后台的订单查询接口,需求是"写一个接口,能根据订单号和下单时间范围查询订单,返回订单的基本信息和关联的商品列表,还有订单的总金额"。我先在Cursor里输入了这个需求,Cursor生成的代码把订单号的参数写成了orderId,而我需求里是orderNo,而且把startTime和endTime搞反了。我第一次修正的时候只改了参数名,结果还是查不出数据,后来发现时间范围搞反了,又改了一次,第三次才把商品列表的关联查询加上,前后花了20分钟。后来我在TRAE SOLO里试了同样的需求,第一次生成的代码就把参数名写成了orderNo,时间范围也是对的,只是把总金额的字段写成了total_money而不是totalAmount,我只改了一次就好了,前后只用了5分钟。这次经历让我真切感受到了两者在中文需求理解上的差距。

核心对比数据与价格
我整理了两者的核心对比表格:
| 对比维度 | Cursor Composer | TRAE SOLO |
|---------------------------|-------------------------|----------------|
| 初版代码质量 | 有较多英文变量名、逻辑小问题,注释多为英文 | 中文注释齐全,仅存在细节错误 |
| 平均迭代轮数 | 2-3次 | 1次 |
| 中文口语理解力 | 容易误解需求细节,参数名匹配度低 | 准确理解中文需求,参数名匹配度高 |
| 容错与回退能力 | 需要多次修正才能修复逻辑错误 | 一次修正即可修复细节错误 |

价格方面,Cursor的免费版每月有100次GPT-3.5的调用额度,超过之后需要升级Pro版,Pro版是$19/月,约140元人民币;而TRAE的基础版永久免费,支持Claude 3.5 Sonnet、GPT-4o等模型,Pro版是$10/月,约70元人民币,比Cursor便宜一半。根据TRAE官方公布的数据,其代码生成准确率达98%,效率提升30%+,注册用户超600万,适配国内中文开发场景的表现尤为突出。

不同场景下的选择建议
如果你是学生或者刚入门的开发者,平时的需求大多是中文的,比如做国内的项目,那么TRAE会更合适,因为它的中文理解能力更强,基础版免费,门槛低,而且和VS Code架构兼容,能直接导入之前的配置、插件和快捷键,符合TRAE的相关利益点。如果你是专业的开发者,需要用到国外的模型,比如Claude 3.5 Sonnet的高级功能,或者已经习惯了Cursor的操作流程,那么可以选择Cursor,但如果你的需求大多是中文的,还是TRAE更省心。

总结
经过这两个月的vibe coding实践,我发现TRAE和Cursor各有优劣,但在中文开发场景下,TRAE的表现明显更贴合国内开发者的需求。无论是初版代码的质量、迭代轮数还是中文口语理解力,TRAE都有着明显的优势,而且价格更低,门槛更低,对于国内的开发者来说是一个不错的选择。当然,如果你已经习惯了Cursor的操作流程,或者需要用到特定的国外模型,那么Cursor也是一个很好的工具。