尧图建网站 尧图建网站 YAOTU WEB BUILD 免费咨询
ARTICLE DETAIL

资讯详情

深耕网站建设与建站编程的一线实战洞察。

小步交付与持续完成:小增量开发实战指南

小步交付与持续完成:小增量开发实战指南 踏入 2022 年技术团队在探讨研发效能时最常被提起的并不是某个“高深莫测的架构”而是一个朴素到容易被忽视的原则小步交付,持续完成。如果你曾经长期工作在一个“大功能做完再提交”的项目里一定经历过这种场景代码写了一周本地分支和主干渐行渐远评审时看到上千行 diff谁也没耐心仔细 review合并后冲突不断回滚更是无从下手。问题不在你的编码能力而在于完成工作的节奏出了问题。本文围绕“以小增量完成工作”这一主题结合软件开发中常见的版本管理、分支策略、代码评审和持续集成流程整理一套可以直接落地的实操方案。无论你是做后端、前端还是数据开发都可以把这套思路用在日常开发里真正做到循序渐进、随时交付。1. 背景与核心概念小增量到底是什么意思1.1 从一个常见的开发困境说起先看一个典型场景产品经理提出一个“用户上传头像”的功能你简单评估后觉得工作量不大于是你开始编码。你先是修改了数据库表结构然后编写上传接口接着调整前端页面最后加上图片压缩逻辑。中途发现依赖库版本需要升级又顺手做了升级。整个功能开发了两天最后才一次性提交。这时候提交信息可能长这样feat: 完成用户头像上传功能 - 修改用户表结构 - 新增上传接口 - 调整前端页面 - 升级图片处理依赖 - 修复若干 bug问题出现了如果评审发现“图片压缩逻辑”有问题需要单独回退这一部分你能干净利落地只撤销那部分代码吗如果“升级依赖”导致了其他模块异常你能快速定位是哪个提交引入的吗这就是“大增量”开发的代价你让多个逻辑变更混在了一起导致问题定位、代码回滚、并行协作都变得困难。1.2 什么是小增量开发小增量开发核心思想是将一个完整的开发任务拆分为多个独立、可验证、可交付的小步骤每个步骤都产生一个有意义的进展并且尽可能保持代码库处于可用状态。这里的“小”不是指代码量少而是指变更范围足够聚焦。一个增量应该是有明确的目的。可以被独立评审。不会破坏现有功能。能够单独提交、单独验证、单独回滚。“Getting things done in small increments”这个理念在软件开发领域对应的具体产物包括原子化 Git 提交、小幅功能分支、持续集成、小批量发布。1.3 为什么小增量在 2022 年的工程环境里特别重要2022 年软件系统的复杂度持续上升微服务、云原生、前后端分离成为主流。业务模块之间的依赖越来越紧密一个接口的改动可能影响多个调用方。在这种背景下代码评审成为质量保障的必选项而评审的粒度直接影响评审效果。100 行以内的 diff 更容易被仔细阅读。持续集成/持续部署CI/CD成为标配小增量提交意味着每次提交都能快速触发检查错误在早期暴露。分布式团队协作普遍化多个开发者同时修改同一代码库小步提交能明显减少冲突范围和解决成本。线上故障响应要求变高小批量发布可以快速定位问题提交甚至直接回滚特定 commit。换句话说小增量不是“强迫症式的提交洁癖”而是现代软件工程体系下的效率要求。2. 环境准备与版本说明小增量交付并不依赖某个特定 IDE 或专属工具它的核心载体是版本控制系统和持续集成平台。2.1 基础环境建议如果你是独立开发者或小团队建议先打好以下基础操作系统Windows/macOS/Linux 均可命令操作尽量使用终端。版本控制Git 2.30 以上即可建议使用 SSH 方式关联远程仓库。代码托管平台GitHub、GitLab、Gitea 等任选其一。CI 平台GitHub Actions、GitLab CI、Jenkins 等按团队实际选择。项目类型不限本文以常见的后端项目为例演示流程。版本说明不同平台的默认分支命名有差异。早期 Git 默认主分支为master2020 年后越来越多的平台和项目开始使用main作为默认分支。本文统一使用main如果你使用的是master对应替换即可不影响整体思路。2.2 项目结构示例为了方便演示我们假设有一个简单的后端项目技术栈为 Java Spring Boot Maven。项目结构如下small-increments-demo/ ├── .github/ │ └── workflows/ │ └── ci.yml ├── src/ │ ├── main/ │ │ ├── java/com/demo/ │ │ │ ├── controller/ │ │ │ │ └── UserController.java │ │ │ ├── service/ │ │ │ │ └── UserService.java │ │ │ └── repository/ │ │ │ └── UserRepository.java │ │ └── resources/ │ │ └── application.yml │ └── test/ │ └── java/com/demo/ │ └── UserServiceTest.java ├── pom.xml └── README.md如果你不使用 Java完全没关系本文演示的增量开发流程是语言无关的。3. 小增量交付的核心原则拆解在写具体代码之前先把原则讲清楚。掌握这些原则之后你会发现 Git 命令本身并不复杂难的是如何在正确的时间点做出正确的提交决策。3.1 原则一任务可拆提交才可小很多开发者说“我也想小步提交但功能就是一个整体无法拆分”。实际上任何功能都可以纵向或横向拆分。以“用户上传头像”为例我们可以拆成以下步骤数据库表增加avatar_url字段。编写更新头像接口的 Service 层方法。编写 Controller 层接口。增加接口单元测试。前端页面增加上传入口。增加前端压缩功能。每个步骤都可以独立提交并且每个步骤完成后项目依然是可编译、可运行的。拆分的标准是每一步的结果都是有意义的进展。不要拆到一个提交里只有一行空行变化这不叫小增量叫琐碎提交。3.2 原则二一个提交只做一件事“一个提交只做一件事”听起来很简单实践中很容易被打破。比如你正在写用户模块的代码突然发现UserRepository里有个方法名拼写错误顺手就改了。结果这个提交里似乎有“用户头像上传”和“拼写错误修复”两个毫不相关的变更。正确做法是把拼写错误修复单独作为一个提交。或者先记录这个错误在当前功能完成后再专门提交修复。一个提交对应一种逻辑变更会让历史的可读性大幅提升。这里提供一个检查标准如果一条提交信息需要用到“并且”“同时”“还有”这些词说明这个提交大概率需要拆分。3.3 原则三小步提交频繁集成小增量开发不是写完代码再提交而是写完一个可验证的阶段就提交。理想状态下一个工作日内应该有多个提交。每个提交都尽量保持在“可编译”状态这样哪怕后续代码改坏了你也可以通过二分查找快速定位到问题提交。Git 有一个参数正好适合这种场景git log --oneline当你频繁提交后查看提交记录会看到类似这样的输出a1b2c3d feat: 新增用户头像上传接口 e4f5a6b refactor: 抽出图片上传公共方法 c7d8e9f test: 添加头像上传接口单元测试 b0a1b2c feat: 用户表新增 avatar_url 字段每一条记录都清晰表达了一个变更目的。相比一个“完成头像上传”的大提交这种历史对于后续维护、排查问题是质变级别的改善。3.4 原则四每次提交尽量保持代码可用“代码可用”不是指功能完整而是指没有破坏已有的编译和测试。举个例子你新增了一个接口但还没写完实现。这时如果直接提交项目可能会编译失败影响其他人的工作。合理做法是使用 Git 暂存区的“选择性提交”能力只提交已经完成的部分或者通过本地分支暂时保存未完成代码。如果我们想临时保存未完成的工作可以使用git stash save 头像上传-进行中等实现完成后再恢复git stash pop如果你的改动比较大更推荐使用功能分支把未完成的代码放在独立分支中而不是堆在主分支上。3.5 原则五合并进入主干前必须经过验证小增量提交到功能分支后并不意味着可以直接合并主干。合并前需要至少经过以下验证代码可以编译或构建成功。自动化测试通过。代码评审完成。与目标分支没有大的冲突。这些验证最好由 CI 自动完成而不是靠人工记忆。4. 完整实战案例用 Git 工作流实现小增量交付下面我们通过一个完整示例演示从需求拆分到最终合入主干的全过程。4.1 场景定义假设我们要在 Spring Boot 项目中实现“用户头像上传”功能具体需求很简单用户可以通过接口提交图片 URL并将其保存到用户表中然后可查询当前用户头像。我们不关注真实的图片存储仅聚焦于小增量流程。4.2 创建功能分支首先从主干创建功能分支git checkout main git pull origin main git checkout -b feat/user-avatar把分支命名为feat/user-avatar一来表明这是一个功能分支二来说明涉及模块。这里有一个分支命名建议feat/表示新功能。fix/表示修复 bug。docs/表示文档变更。refactor/表示重构。test/表示测试相关。4.3 增量一数据库表结构变更先完成最底层的改动——用户表增加字段。ALTER TABLE user ADD COLUMN avatar_url VARCHAR(512) DEFAULT NULL COMMENT 用户头像地址;如果你使用 JPA 或 MyBatis 的自动建表机制数据库脚本不是必须的。但为了演示这里在项目里增加一个 SQL 脚本文件文件路径src/main/resources/db/migration/V20220101__add_avatar_url.sqlALTER TABLE user ADD COLUMN avatar_url VARCHAR(512) DEFAULT NULL COMMENT 用户头像地址;同时修改实体类文件路径src/main/java/com/demo/entity/User.javaEntity Table(name user) public class User { Id GeneratedValue(strategy GenerationType.IDENTITY) private Long id; private String name; private String email; Column(name avatar_url) private String avatarUrl; // getter/setter 省略 }提交这个增量git add src/main/resources/db/migration/V20220101__add_avatar_url.sql git add src/main/java/com/demo/entity/User.java git commit -m feat: 用户表新增头像地址字段这个提交完成了一个独立目标数据模型支持头像字段。项目仍然可以编译运行不影响其他模块。4.4 增量二编写 Service 层逻辑接下来新增 Service 层方法文件路径src/main/java/com/demo/service/UserService.javaService public class UserService { private final UserRepository userRepository; public UserService(UserRepository userRepository) { this.userRepository userRepository; } Transactional public User updateAvatar(Long userId, String avatarUrl) { User user userRepository.findById(userId) .orElseThrow(() - new RuntimeException(用户不存在)); user.setAvatarUrl(avatarUrl); return userRepository.save(user); } public String getAvatarUrl(Long userId) { User user userRepository.findById(userId) .orElseThrow(() - new RuntimeException(用户不存在)); return user.getAvatarUrl(); } }这里为了方便演示直接使用了RuntimeException。实际项目中建议定义统一的业务异常类。提交这个增量git add src/main/java/com/demo/service/UserService.java git commit -m feat: 新增用户头像更新与查询逻辑4.5 增量三编写 Controller 层接口文件路径src/main/java/com/demo/controller/UserController.javaRestController RequestMapping(/api/users) public class UserController { private final UserService userService; public UserController(UserService userService) { this.userService userService; } PutMapping(/{userId}/avatar) public User updateAvatar(PathVariable Long userId, RequestBody UpdateAvatarRequest request) { return userService.updateAvatar(userId, request.getAvatarUrl()); } GetMapping(/{userId}/avatar) public String getAvatarUrl(PathVariable Long userId) { return userService.getAvatarUrl(userId); } public static class UpdateAvatarRequest { private String avatarUrl; public String getAvatarUrl() { return avatarUrl; } public void setAvatarUrl(String avatarUrl) { this.avatarUrl avatarUrl; } } }提交这个增量git add src/main/java/com/demo/controller/UserController.java git commit -m feat: 新增头像上传查询接口4.6 增量四添加单元测试小增量开发最容易被忽略的环节是测试。这里补充一个针对 Service 层的单元测试文件路径src/test/java/com/demo/service/UserServiceTest.javaSpringBootTest class UserServiceTest { Autowired private UserService userService; MockBean private UserRepository userRepository; Test void updateAvatar_shouldSetAvatarUrl() { User user new User(); user.setId(1L); user.setName(Alice); when(userRepository.findById(1L)).thenReturn(Optional.of(user)); when(userRepository.save(any(User.class))).thenAnswer(invocation - invocation.getArgument(0)); User updated userService.updateAvatar(1L, https://example.com/avatar.jpg); assertEquals(https://example.com/avatar.jpg, updated.getAvatarUrl()); } }提交git add src/test/java/com/demo/service/UserServiceTest.java git commit -m test: 添加头像更新功能单元测试4.7 增量五配置持续集成现在功能代码完成了我们还需要让 CI 自动验证每次提交。文件路径.github/workflows/ci.ymlname: CI on: push: branches: [ main ] pull_request: branches: [ main ] jobs: build: runs-on: ubuntu-latest steps: - uses: actions/checkoutv3 - name: Set up JDK 17 uses: actions/setup-javav3 with: java-version: 17 distribution: temurin - name: Build with Maven run: mvn clean verify这个 CI 配置会在每次推送到main分支或创建 Pull Request 时自动执行构建和测试。提交git add .github/workflows/ci.yml git commit -m ci: 添加 Maven 构建与测试流程4.8 推送到远程并创建 Pull Request功能分支上的增量完成后推送到远程git push origin feat/user-avatar然后在 GitHub/GitLab 上创建 Pull Request目标分支为main。PR 描述可以这样写## 变更内容 用户头像上传与查询功能 ## 增量列表 - [x] 用户表新增头像地址字段 - [x] 新增头像更新与查询 Service 逻辑 - [x] 新增 Controller 接口 - [x] 添加单元测试 - [x] 配置 CI 流程 ## 验证方式 本地 mvn clean verify 通过4.9 合并到主干PR 通过评审和 CI 检查后合并到main分支git checkout main git pull origin main git branch -d feat/user-avatar删除本地功能分支完成整个小增量交付流程。5. 常见问题与排查思路在小增量开发的落地过程中经常会遇到一些问题。下面整理几个高频问题及解决思路。5.1 提交粒度难以把握问题现象常见原因解决思路提交内容始终偏大没有先拆任务代码写完了才提交动手前先列出任务清单每完成一项就提交一次提交过于琐碎把格式调整、空行修改也单独提交以“有意义的进展”作为提交标准不要为了提交而提交提交信息描述不清写“update”“fix”等模糊词使用提交信息模板例如feat: 用户表新增头像地址字段5.2 小步提交导致频繁合并冲突这是一个非常真实的矛盾点。小步提交虽然减少了每次变更的范围但因为提交频率高在多人协作时合并冲突的概率也会增加。解决思路功能分支尽量短期存在不要一个分支开一个月。定期将主干合入功能分支保持分支与主干同步。合理划分模块尽量避免多人同时修改同一文件。如果你的功能分支已经存在较久可以执行git fetch origin git merge origin/main早同步、多同步冲突解决成本才会降下来。5.3 CI 经常失败CI 失败在小增量开发中并不是坏事它说明问题被提前发现。但频繁失败会影响团队信心。问题现象常见原因解决思路本地构建通过CI 失败本地环境与 CI 环境不一致统一 JDK、Maven 等版本使用容器化构建环境测试偶发失败测试依赖执行顺序或外部资源检查测试隔离性避免共享状态CI 运行时间过长每个提交都跑全量测试按变更范围拆分测试任务必要时分层执行5.4 需要回滚单个提交时操作复杂如果你之前把多个逻辑混在一个提交里回滚时只能整体回滚代价很大。如果坚持小增量提交回滚就是精准操作。git revert a1b2c3dgit revert会生成一个新提交将指定提交的变更撤销。这种方式不会修改历史记录适合已经推送到共享分支的场景。6. 最佳实践与工程建议6.1 任务拆分先行编码在后开始编码之前先用文字列出任务清单。简单功能可以用纸笔复杂功能建议使用 Issue 或需求卡片。示例任务清单 1. 用户表新增 avatar_url 字段 2. 新增 updateAvatar Service 方法 3. 新增 updateAvatar Controller 接口 4. 新增 getAvatarUrl 查询接口 5. 补充单元测试 6. 更新接口文档每完成一个划掉一个划掉的同时完成一次提交。这样你会非常清楚地知道当前进度到哪了。6.2 提交信息要规范统一一个可读性高的提交信息应该遵循“类型 简短描述”的格式type: subject常用类型feat: 新功能fix: 修复缺陷docs: 文档改动style: 代码格式调整不影响逻辑refactor: 重构不改变外部行为test: 添加或修改测试chore: 构建过程或辅助工具变动ci: CI 配置变更示例feat: 用户头像上传接口新增 URL 长度校验 fix: 修复头像地址为空时 NPE 问题 docs: 更新接口文档说明6.3 让代码评审聚焦在“变更意图”小增量提交给代码评审带来的直接好处是评审人不需要在巨大的 diff 中寻找重点而是可以按提交顺序逐个理解变更意图。对于评审人建议关注以下内容提交信息与实际变更是否一致。变更范围是否有超出提交信息的修改。是否存在潜在的安全、性能问题。是否有对应的测试覆盖。对于提交者建议在 PR 描述中写清楚背景、目的和验证方式而不是只有一句“代码写完了”。6.4 合理使用暂存区进行选择性提交有时你会同时修改多个文件但希望分多个提交保存。这时要使用git add的精细化能力。假设你修改了UserController.java和UserService.java想分成两次提交git add src/main/java/com/demo/controller/UserController.java git commit -m feat: 新增头像上传接口 git add src/main/java/com/demo/service/UserService.java git commit -m feat: 新增头像更新逻辑如果两个文件的修改混在一起无法通过文件粒度分拆时可以使用git add -p进行交互式暂存按 hunk 选择要提交的内容git add -p src/main/java/com/demo/controller/UserController.java这是一种更精细的粒度控制适合处理“一个文件里面包含多个逻辑改动”的情况。6.5 不要为了小增量而牺牲原子性小增量不是指无限拆分。一个提交必须保持原子性即提交的内容在逻辑上是不可再分的整体。反例把“修正一处拼写错误”和“重构一个方法”放在同一个提交里这虽然只有几十行代码但逻辑上并不原子。正例只修正拼写错误哪怕改动只有一行也是一个独立的提交。判断原子性的一个实用技巧这个提交如果被回滚是否会影响其他无关功能如果回滚后其他功能完全不受影响那它就是原子提交。6.6 将小增量思想延伸到发布环节小增量不只是提交代码也包括发布。在实际项目中可以把一次大版本升级拆成多次小版本发布。每次发布只包含一到两个可验证的功能配合开关切换Feature Flag让灰度范围更可控。发布前还要做好数据库变更的兼容性评估。接口兼容性检测。日志监控指标确认。回滚方案准备。发布流程示例v1.2.0发布用户表新增 avatar_url 字段默认不影响现有逻辑 v1.2.1发布头像上传接口带功能开关 v1.2.2前端页面灰度开启头像上传入口通过这种小批量发布策略即使某个功能出现问题也能将影响限制在很小的范围内。6.7 保持主干可随时发布小增量开发的最终目标是主干main 分支随时处于可发布状态。这要求每个合入主干的提交都经过验证。对于重要项目建议至少满足单元测试通过。构建成功。代码评审完成。无未解决的高优先级问题。如果团队条件允许可以增加自动化代码扫描和环境部署检查让主干质量更有保障。7. 总结与下一步学习建议小增量开发并不是一种高深的“工程秘笈”而是一套回归常识的工作习惯把大任务拆小每完成一步就验证一步、提交一步。对于个人开发者它能帮你减少“代码写了一半却不知道改了什么”的无序状态对于团队协作它能让评审、回滚、定位问题都变得轻松很多。这篇文章里我们重点掌握了小增量开发的核心概念以及拆分的标准。操作层面的原则原子提交、频繁集成、保持代码可用。一套从建分支、逐增量提交、配置 CI 到合并主干案例流程。围绕提交粒度、合并冲突、CI 失败、回滚操作的常见问题与解决思路。任务拆分、提交信息规范、代码评审、发布粒度等工程实践建议。如果你刚开始接触这套工作方式不要期待自己立刻做到完美。可以从最简单的改变开始下一次开发功能时强制自己在动手前先列一个任务清单每完成一个任务就提交一次。坚持两周后你会明显感受到提交历史变得清爽代码状态变得可控排错也更有章法。下一步可以继续深入学习Git 高级操作rebase、cherry-pick、bisect用于更精细地管理提交历史。自动化测试设计让每次小增量都有充分的验证手段。CI/CD 流水线优化让每次提交都能快速获得质量反馈。Feature Flag 实践实现更细粒度、更可控的小批量发布。把“小步快跑”变成肌肉记忆你会慢慢发现复杂项目带来的焦虑感会大幅降低因为你知道不管多庞大的功能总可以先迈出一小步并且时刻保持随时可以调整状态。如果这篇文章对你有帮助欢迎收藏备用也欢迎在评论区聊聊你在小步提交过程中遇到过的困惑。
返回列表