第3篇:5 天完成 24 个任务——我和 Trae 的真实协作 Workflow 📅 2026/7/13 2:04:36 引言架构是图纸流程是施工。上一篇讲清楚了图纸长什么样这一篇回到工地现场看一个真人和一个 AI5 天怎么把 24 个任务推进完毕。这一篇不打算写成逐天流水账。先一张表概览全貌再深入两个剖面TDD 流程怎么在“人AI”模式下落地、分歧和 Bug 怎么发现和修复。最后聊聊那些你不容易感知到的规范执行带来的隐形价值。第一部分5 天 24 个任务1.1 真实进度总览以下是一份逐天真实记录。表格中的数据来源是tasks.md和实际开发节奏天数Phase完成任务任务数Trae 做了什么我做了什么发现的问题Day 1P0 P1项目框架搭建、Swagger 配置、用户管理 CRUD、角色管理、数据权限5生成 Spring Boot 骨架、Security 配置、JWT 认证、BCrypt 加密调整包结构命名、确认版本号兼容性Swagger 的页面请求被拦截Day 2P1 P2员工状态机、岗位管理、部门管理、考勤组与班次、种子数据生成5生成 Entity/Mapper/Service/Controller 全套、5 状态 9 规则状态机、10 部门100 员工种子数据校验状态转换规则是否完备、“已离职→待入职” 的重新入职路径需要单独补充种子数据生成时先插员工再插部门导致外键约束失败员工状态流转路径缺失Day 3P2日考勤计算引擎含打卡与考勤统计、考勤日历、假期额度管理、请假审批、加班管理、月考勤汇总6日考勤判定算法迟到/早退/旷工/缺卡/正常五种状态、假期额度自动生成、请假审批流程补充打卡的边界 case一天多次打卡取首条 IN末条 OUT、调整节假日的计算逻辑月考勤汇总的批量重算存在 N1 查询每个员工单独查一次日结果100 个员工就是 100 次查询Day 4P3薪资科目管理、薪资公式引擎、工资单生成、职位管理、候选人管理、招聘看板6科目模板双层薪资模型、公式引擎、候选人状态机、拖拽式招聘看板调整薪资计算链路的数据联动考勤→薪资、补充公式引擎的 BigDecimal 精度处理公式引擎对“空值”的处理不当某员工当月没有加班记录公式里引用加班字段直接抛 NPEDay 5P4数据看板与移动端适配、性能优化2ECharts 图表组件封装、响应式布局调整看板图表的数据聚合口径、优化首屏加载基础优化后 Lighthouse 仍只有 70Element 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 分支Traegit checkout -b task-4-employee-state-machine2. 编写测试Trae生成EmployeeTransitionServiceTest覆盖 9 条合法转换和 5 条非法转换3. 测试失败RedTrae14 个测试全部标红实体类和 Service 还不存在4. 编写实现Trae生成状态枚举、转换规则 Map、Service、Controller5. 测试通过GreenTrae14 个测试全部绿色6. 审查调整我发现“已离职→待入职”重新入职路径缺失补充转换规则调整异动日志的 JSON 存储格式7. 前端页面Trae生成状态转换按钮、Timeline 组件、确认弹窗8. 联调验证我Swagger 调接口验证完整流转确认前端正确渲染9. Git 提交Traegit commit -m feat: 完成员工状态机功能从启动到提交一个完整功能约 39 分钟。其中 Trae 编码加调整共用了约 20 分钟我审查调整和验证共用了约 19 分钟。接近五五开。这不是特例。在整个 24 个任务中这个比例非常稳定Trae 负责“写出来”我负责“写对了”。第二部分TDD 实战剖面上一节是广度这一节是深度。用一个真实案例还原 Red-Green-Refactor 在“人AI”协作模式下的真实样貌。2.1 Red测试先行任务开始我在执行计划里写道“实现用户管理密码必须 BCrypt 加密存储”。Trae 在执行时先生成了测试Testvoidcreate_ValidUser_PasswordIsBCryptEncoded(){UserDTOdtonewUserDTO();dto.setUsername(testuser);dto.setPassword(123456);dto.setStatus(1);userService.create(dto);SysUsersaveduserMapper.selectOne(newLambdaQueryWrapperSysUser().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 开始写实现OverrideTransactional(rollbackForException.class)publicvoidcreate(UserDTOdto){if(!StringUtils.hasText(dto.getPassword())){thrownewBusinessException(密码不能为空);}LambdaQueryWrapperSysUserquerynewLambdaQueryWrapper();query.eq(SysUser::getUsername,dto.getUsername());if(userMapper.exists(query)){thrownewBusinessException(用户名已存在);}SysUserusernewSysUser();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].*)){thrownewBusinessException(密码长度至少8位需包含字母和数字);}2.4 这个案例说明了什么Trae 严格遵循 TDD 流程。测试→实现→通过没有跳步、没有偷懒。我们有时候会想“这个太简单了不用写测试”但只要你告诉 AI 说我们要按照 TDD 的流程执行开发 AI 就不会偷懒。Trae 不会主动思考“这个功能该不该放在这里”。它按规范写代码但“update 方法该不该改密码”这种设计层面的判断仍然需要人来做。Refactor 阶段的审查是人的核心价值所在。Trae 给你的是一个“功能正确”的版本而你要把它变成“设计合理”的版本。第三部分分歧与纠错如果说 TDD 展示的是“正常流程下的协作模式”那这一节展示的是“出问题的时候怎么办”。3.1 场景一“太复杂了简化它”问题Day 3 做“请假审批流程”时Trae 生成了一个完整的工作流引擎LeaveApprovalWorkflow类包含了多级审批链、条件分支、会签/或签、超时自动处理等逻辑。我跟它说“太复杂。”实际情况是这个系统的请假审批只需要“提交 → 直属上级审批 → 通过/拒绝”三段式。不需要多级审批链不需要会签不需要超时自动处理。我的处理让 Trae 把LeaveApprovalWorkflow整个类删掉审批逻辑简化为一个approve()方法和一个reject()方法放在LeaveService中。// 简化后的审批逻辑只有一个方法publicvoidapprove(LongapplicationId,LongapproverId){LeaveApplicationappgetById(applicationId);if(app.getStatus()!LeaveStatus.PENDING){thrownewBusinessException(该申请已处理);}app.setStatus(LeaveStatus.APPROVED);app.setApproverId(approverId);app.setApprovedAt(LocalDateTime.now());leaveApplicationMapper.updateById(app);leaveQuotaService.deduct(app.getEmployeeId(),app.getLeaveType(),app.getDuration());}教训AI 倾向于“完整方案”。你让它做审批它给你工作流引擎。你让它做权限它给你 RBACABACACL 全套。我们要会掌舵。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的批量计算方法长这样简化版// 问题代码N1 查询for(LongempId:employeeIds){ListAttDailyResultdailyResultsdailyResultMapper.selectByEmployeeAndMonth(empId,year,month);// 对每个员工单独查一次数据库MonthlyAttendancesummarycalculateSummary(dailyResults);monthSummaryMapper.insert(summary);}100 个员工 100 次数据库查询 100 次插入。这就是典型的 N1 问题。修复改为批量查询 批量插入// 批量查询所有员工的日结果ListAttDailyResultallDailyResultsdailyResultMapper.selectByMonth(year,month);// 按员工 ID 分组MapLong,ListAttDailyResultgroupedallDailyResults.stream().collect(Collectors.groupingBy(AttDailyResult::getEmployeeId));// 批量计算ListMonthlyAttendancesummariesgrouped.entrySet().stream().map(e-calculateSummary(e.getValue())).toList();// 批量插入自定义扩展方法BaseMapper 不提供 insertBatchmonthSummaryMapper.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有人用构造器注入有人返回ResultT有人返回ApiResponseT有人异常抛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 过度设计的部分性能逻辑正确优先性能优化N1、缓存、SQL 调优规范执行100% 执行不会偷懒规范本身的设计和迭代AI 写得快但我们在审查、调试和优化中的作用不可替代。这个结论不是感悟是 5 天 24 个任务反复验证的结果。下一篇文章将进入业务和技术的深水区考勤引擎、状态机和公式引擎这三个模块是云山HR 最复杂的业务逻辑也是最能体现 Trae 的能力和短板的地方。