第3篇:5 天完成 24 个任务——我和 Trae 的真实协作 Workflow
引言
架构是图纸,流程是施工。上一篇讲清楚了图纸长什么样,这一篇回到工地现场:看一个真人和一个 AI,5 天怎么把 24 个任务推进完毕。
这一篇不打算写成逐天流水账。先一张表概览全貌,再深入两个剖面:TDD 流程怎么在“人+AI”模式下落地、分歧和 Bug 怎么发现和修复。最后聊聊那些你不容易感知到的:规范执行带来的隐形价值。
第一部分:5 天 24 个任务
1.1 真实进度总览
以下是一份逐天真实记录。表格中的数据来源是 tasks.md 和实际开发节奏:
| 天数 | Phase | 完成任务 | 任务数 | Trae 做了什么 | 我做了什么 | 发现的问题 |
|---|---|---|---|---|---|---|
| Day 1 | P0 + P1 | 项目框架搭建、Swagger 配置、用户管理 CRUD、角色管理、数据权限 | 5 | 生成 Spring Boot 骨架、Security 配置、JWT 认证、BCrypt 加密 | 调整包结构命名、确认版本号兼容性 | Swagger 的页面请求被拦截 |
| Day 2 | P1 + P2 | 员工状态机、岗位管理、部门管理、考勤组与班次、种子数据生成 | 5 | 生成 Entity/Mapper/Service/Controller 全套、5 状态 9 规则状态机、10 部门+100 员工种子数据 | 校验状态转换规则是否完备、“已离职→待入职” 的重新入职路径需要单独补充 | 种子数据生成时,先插员工再插部门导致外键约束失败 员工状态流转路径缺失 |
| Day 3 | P2 | 日考勤计算引擎(含打卡与考勤统计)、考勤日历、假期额度管理、请假审批、加班管理、月考勤汇总 | 6 | 日考勤判定算法(迟到/早退/旷工/缺卡/正常五种状态)、假期额度自动生成、请假审批流程 | 补充打卡的边界 case(一天多次打卡取首条 IN+末条 OUT)、调整节假日的计算逻辑 | 月考勤汇总的批量重算存在 N+1 查询:每个员工单独查一次日结果,100 个员工就是 100 次查询 |
| Day 4 | P3 | 薪资科目管理、薪资公式引擎、工资单生成、职位管理、候选人管理、招聘看板 | 6 | 科目+模板双层薪资模型、公式引擎、候选人状态机、拖拽式招聘看板 | 调整薪资计算链路的数据联动(考勤→薪资)、补充公式引擎的 BigDecimal 精度处理 | 公式引擎对“空值”的处理不当,某员工当月没有加班记录,公式里引用加班字段直接抛 NPE |
| Day 5 | P4 | 数据看板与移动端适配、性能优化 | 2 | ECharts 图表组件封装、响应式布局 | 调整看板图表的数据聚合口径、优化首屏加载 | 基础优化后 Lighthouse 仍只有 70,Element Plus 全量引入导致首屏 JS 体积过大,常规优化手段无效 |
1.2 节奏分析
如果仔细看上表的任务数分布,我们会发现:
- 前 2 天(10 个任务):搭骨架。Phase 1 的 10 个 P0 任务是整个系统的地基。这个阶段的特点是“代码量大但处理逻辑相对少”:CRUD 为主,Trae 生成速度极快,我的工作主要是审查和微调。
- 第 3 天(6 个任务):跑业务闭环。Phase 2 的任务数量多但每个都是“小闭环”——日考勤引擎、请假审批流、月考勤汇总,每个任务写完就能独立验证。这个阶段是出 Bug 最多的一天。
- 第 4 天(6 个任务):打硬仗。薪资公式引擎和候选人状态机是技术密度最高的模块,任务数少了但每个任务的复杂度翻倍。
- 第 5 天(2 个任务):收尾打磨。图表、适配、优化,属于“锦上添花”类任务。如果时间不够,这几个任务是可裁减的。
1.3 一个典型任务的完整时间线
以 Day 2 的“员工状态机”任务为例,拆解从启动到验收的全过程:
| 步骤 | 谁主导 | 具体动作 |
|---|---|---|
| 1. 创建 Git 分支 | Trae | git checkout -b task-4-employee-state-machine |
| 2. 编写测试 | Trae | 生成 EmployeeTransitionServiceTest,覆盖 9 条合法转换和 5 条非法转换 |
| 3. 测试失败(Red) | Trae | 14 个测试全部标红:实体类和 Service 还不存在 |
| 4. 编写实现 | Trae | 生成状态枚举、转换规则 Map、Service、Controller |
| 5. 测试通过(Green) | Trae | 14 个测试全部绿色 |
| 6. 审查调整 | 我 | 发现“已离职→待入职”重新入职路径缺失,补充转换规则;调整异动日志的 JSON 存储格式 |
| 7. 前端页面 | Trae | 生成状态转换按钮、Timeline 组件、确认弹窗 |
| 8. 联调验证 | 我 | Swagger 调接口验证完整流转,确认前端正确渲染 |
| 9. Git 提交 | Trae | git commit -m "feat: 完成员工状态机功能" |
从启动到提交,一个完整功能约 39 分钟。 其中 Trae 编码加调整共用了约 20 分钟,我审查调整和验证共用了约 19 分钟。接近五五开。
这不是特例。在整个 24 个任务中,这个比例非常稳定:Trae 负责“写出来”,我负责“写对了”。
第二部分:TDD 实战剖面
上一节是广度,这一节是深度。用一个真实案例,还原 Red-Green-Refactor 在“人+AI”协作模式下的真实样貌。

2.1 Red:测试先行
任务开始,我在执行计划里写道:“实现用户管理,密码必须 BCrypt 加密存储”。
Trae 在执行时先生成了测试:
@Test
void create_ValidUser_PasswordIsBCryptEncoded() {
UserDTO dto = new UserDTO();
dto.setUsername("testuser");
dto.setPassword("123456");
dto.setStatus(1);
userService.create(dto);
SysUser saved = userMapper.selectOne(
new LambdaQueryWrapper<SysUser>().eq(SysUser::getUsername, "testuser")
);
assertNotEquals("123456", saved.getPassword());
assertTrue(saved.getPassword().startsWith("$2a$"));
assertNotNull(saved);
}
测试有三个断言:
- 密码不能是明文
"123456" - 密码必须是 BCrypt 格式(以
$2a$开头) - 用户能被正常查询到
此时 UserService 还不存在,测试自然是红的。这个阶段只花了不到 2 分钟,Trae 写测试比人快得多,因为在设计的时候已经定义了测试场景“应该覆盖什么”,它会自动填充边界条件和异常情况。
2.2 Green:最小实现
测试通过后,Trae 开始写实现:
@Override
@Transactional(rollbackFor = Exception.class)
public void create(UserDTO dto) {
if (!StringUtils.hasText(dto.getPassword())) {
throw new BusinessException("密码不能为空");
}
LambdaQueryWrapper<SysUser> query = new LambdaQueryWrapper<>();
query.eq(SysUser::getUsername, dto.getUsername());
if (userMapper.exists(query)) {
throw new BusinessException("用户名已存在");
}
SysUser user = new SysUser();
user.setUsername(dto.getUsername());
user.setPassword(passwordEncoder.encode(dto.getPassword()));
user.setEmployeeId(dto.getEmployeeId());
user.setStatus(dto.getStatus());
user.setPwdReset(0);
userMapper.insert(user);
}
这段代码有几个值得注意的点:
密码加密在 Service 层,而非 Controller 层。 这是一个关键点,Service 层是业务逻辑的边界,密码加密是业务行为而非 HTTP 行为。Trae 在这一点上判断正确。
用户名唯一性校验在插入前完成。 没有依赖数据库唯一索引报错来兜底,而是主动查询+抛业务异常。这意味着前端能拿到明确的错误信息("用户名已存在"),而不是一个 SQL 异常。
pwdReset 字段初始化为 0。 标记该密码未被强制重置,这点在实际开发的时候有可能被忽略,但 Trae 没忘。因为它读了项目的实体类定义,知道有这个字段。
2.3 Refactor:我发现的问题
测试全部通过后,我开始审查。发现了两个问题:
问题一:更新用户时,密码不应该被静默修改。
Trae 最初生成的 update() 方法中包含了密码设置逻辑,如果 DTO 传了 password 字段就更新密码。这是不合理的:用户信息的编辑页面不应该同时承担“改密码”的职责。密码修改应该有独立的接口(resetPassword),有独立的审计日志。
我的调整:从 update() 中移除密码字段的处理,让 update 只修改基本信息,如用户名、关联员工和状态。密码修改走独立的 changePassword 流程。
问题二:密码强度没有服务端校验。
Trae 的测试密码都是 "123456",测试通过不代表实际上线没问题。我要求增加密码复杂度校验: 8 位、包含字母和数字。这个校验逻辑放在 create() 和 resetPassword() 两个入口中。
if (dto.getPassword().length() < 8 ||
!dto.getPassword().matches(".*[a-zA-Z].*") ||
!dto.getPassword().matches(".*[0-9].*")) {
throw new BusinessException("密码长度至少8位,需包含字母和数字");
}
2.4 这个案例说明了什么?
- Trae 严格遵循 TDD 流程。 测试→实现→通过,没有跳步、没有偷懒。我们有时候会想“这个太简单了不用写测试”,但只要你告诉 AI 说我们要按照 TDD 的流程执行开发, AI 就不会偷懒。
- Trae 不会主动思考“这个功能该不该放在这里”。 它按规范写代码,但“update 方法该不该改密码”这种设计层面的判断,仍然需要人来做。
- Refactor 阶段的审查,是人的核心价值所在。 Trae 给你的是一个“功能正确”的版本,而你要把它变成“设计合理”的版本。
第三部分:分歧与纠错
如果说 TDD 展示的是“正常流程下的协作模式”,那这一节展示的是“出问题的时候怎么办”。
3.1 场景一:“太复杂了,简化它”
问题:Day 3 做“请假审批流程”时,Trae 生成了一个完整的工作流引擎:LeaveApprovalWorkflow 类包含了多级审批链、条件分支、会签/或签、超时自动处理等逻辑。
我跟它说:“太复杂。”
实际情况是:这个系统的请假审批只需要“提交 → 直属上级审批 → 通过/拒绝”三段式。不需要多级审批链,不需要会签,不需要超时自动处理。
我的处理:让 Trae 把 LeaveApprovalWorkflow 整个类删掉,审批逻辑简化为一个 approve() 方法和一个 reject() 方法,放在 LeaveService 中。
// 简化后的审批逻辑,只有一个方法
public void approve(Long applicationId, Long approverId) {
LeaveApplication app = getById(applicationId);
if (app.getStatus() != LeaveStatus.PENDING) {
throw new BusinessException("该申请已处理");
}
app.setStatus(LeaveStatus.APPROVED);
app.setApproverId(approverId);
app.setApprovedAt(LocalDateTime.now());
leaveApplicationMapper.updateById(app);
leaveQuotaService.deduct(app.getEmployeeId(), app.getLeaveType(), app.getDuration());
}
教训:AI 倾向于“完整方案”。你让它做审批,它给你工作流引擎。你让它做权限,它给你 RBAC+ABAC+ACL 全套。我们要会掌舵。
3.2 场景二:“跑不通,一起 debug”
问题:Day 4 做薪资公式引擎时,公式 "基本工资 + 绩效 - 缺勤扣款" 在界面操作正常,在单元测试中也正常,但批量计算 100 个员工时,有 3 个员工的结果是 null。
排查过程:
- 先看数据库,这 3 个员工的
att_month_summary记录存在,但late_count和early_count字段都是NULL而非0。 - 再看种子数据生成逻辑,Trae 生成的 SQL 中对于“全勤员工”的
late_count和early_count设为了NULL,而不是0。 - 公式引擎在解析时,
NULL被原样代入 SpEL 表达式,导致整个计算结果变成null。
修复:两处改动:种子数据 SQL 中加 COALESCE(late_count, 0),公式引擎中加空值保护:
// 公式引擎中加空值保护
variables.put("lateCount", Objects.requireNonNullElse(monthly.getLateCount(), 0));
variables.put("earlyCount", Objects.requireNonNullElse(monthly.getEarlyCount(), 0));
variables.put("absentDays", Objects.requireNonNullElse(monthly.getAbsentDays(), 0));
教训:AI 生成代码时对“缺失数据”的处理有时候会假设数据是干净的。生产环境数据很有可能存在异常,NULL、空字符串、负数、超长文本,什么都有。边界条件的防护,需要我们来提醒它要注意。
3.3 场景三:“为什么不用批量操作?”
问题:Day 3 做“月考勤汇总”时,批量重算 100 个员工的月度考勤耗时 5 秒,超过了我的预期。
排查发现,MonthlyAttendanceService 的批量计算方法长这样(简化版):
// 问题代码:N+1 查询
for (Long empId : employeeIds) {
List<AttDailyResult> dailyResults = dailyResultMapper.selectByEmployeeAndMonth(empId, year, month);
// 对每个员工单独查一次数据库
MonthlyAttendance summary = calculateSummary(dailyResults);
monthSummaryMapper.insert(summary);
}
100 个员工 = 100 次数据库查询 + 100 次插入。这就是典型的 N+1 问题。
修复:改为批量查询 + 批量插入:
// 批量查询所有员工的日结果
List<AttDailyResult> allDailyResults = dailyResultMapper.selectByMonth(year, month);
// 按员工 ID 分组
Map<Long, List<AttDailyResult>> grouped = allDailyResults.stream()
.collect(Collectors.groupingBy(AttDailyResult::getEmployeeId));
// 批量计算
List<MonthlyAttendance> summaries = grouped.entrySet().stream()
.map(e -> calculateSummary(e.getValue()))
.toList();
// 批量插入(自定义扩展方法,BaseMapper 不提供 insertBatch)
monthSummaryMapper.insertBatch(summaries);
改完后,100 个员工的批量计算从 5 秒降到 1 秒。
教训:Trae 写业务逻辑时,有时候没有“性能意识”。它的关注点是“逻辑正确”,至于这个循环体里是一次 SQL 还是两次 SQL,它不关心。我们要发现问题,然后让它改正。
第四部分:规范执行的“隐形价值”
前三部分讲的是“看得见的协作”,任务怎么推、TDD 怎么走、Bug 怎么修。这一节想聊聊那些“你不容易感觉到,但一旦没有就会很痛苦”的事。
4.1 为什么你不需要检查 Swagger 注解漏没漏?
在以往的团队开发中,Code Review 有一项固定动作:“这个接口的 Swagger 注解写了吗?”
云山HR 有 100+ 个 API 接口,按照传统开发模式,总有那么几个接口会漏掉 @Operation 注解。然后测试跑来跟你说“这个接口文档里怎么没有?”,你翻开代码一看,确实忘了,然后再补上。
但在 Trae 开发模式下,这个问题不存在。项目规范中写了:
“所有 Controller 类必须添加 @Tag 注解,所有 API 方法必须添加 @Operation 注解”
Trae 每次生成 Controller 时,都会自动补齐这些注解。不是“大部分时候补齐”,每次都补齐。因为规范对 AI 来说是“指令”而不是“建议”。
4.2 为什么代码风格始终一致?
一个 24 个任务的项目,如果分给 3 个开发人员写,大概率会出现三种不同的代码风格:有人用 @Autowired 有人用构造器注入,有人返回 Result<T> 有人返回 ApiResponse<T>,有人异常抛 RuntimeException 有人抛 BusinessException。
但云山HR 的代码风格从头到尾完全一致,因为只有“一个开发人员”在写:Trae。它每次写代码前都加载同一份规范,都使用同一个模板。这种一致性不是靠 Code Review 追回来的,是从源头上就保证了的。
以上其实是一些被忽略的隐形价值。开发结束后,你可能会觉得“这代码写得很符合我制定的规范”,但你可能意识不到,这些规范是因为 Trae 强制执行才没成为后来的“技术债”。
4.3 500 行检查清单不是摆设
开发过程中,我每天都会对照一次 checklist.md。不是人工逐条核对,而是让 Trae 扫描一遍,报告哪些项还没完成。
举几个印象最深的检查项:
“密码 BCrypt 加密实现完成,确认数据库中不存储明文密码”
“分页接口:确认入参 page 从 1 开始,size 默认 10 最大 100”
“日考勤计算引擎 TDD 流程完成:包含正常打卡、迟到早退、旷工缺卡、多打卡记录、节假日五种场景”
如果没有这个清单,后两条大概率会被遗漏,人在高强度开发中,总会忘记某些边界条件。但清单在那里,对着逐一扫描一遍就知道缺了什么。

结语
总结一下这 5 天的核心感受:
| 维度 | Trae 擅长的 | 我们必须做的 |
|---|---|---|
| 速度 | 代码生成极快,CRUD 出码迅速 | 设计决策:功能该不该放在这里 |
| 一致性 | 严格按规范写,风格统一 | 边界条件:NULL、空值、异常输入怎么办 |
| 完整性 | 不会忘记注解、不会漏测试步骤 | 简化:去掉 AI 过度设计的部分 |
| 性能 | 逻辑正确优先 | 性能优化:N+1、缓存、SQL 调优 |
| 规范执行 | 100% 执行,不会偷懒 | 规范本身的设计和迭代 |
AI 写得快,但我们在审查、调试和优化中的作用不可替代。 这个结论不是感悟,是 5 天 24 个任务反复验证的结果。
下一篇文章将进入业务和技术的深水区:考勤引擎、状态机和公式引擎,这三个模块是云山HR 最复杂的业务逻辑,也是最能体现 Trae 的能力和短板的地方。
更多推荐



所有评论(0)