摘要

1) 一句话总结 本文总结了通过严格的代码审查、系统设计前置、逐步重构老项目以及定期偿还技术债务等最佳实践,来有效提升团队代码质量与开发效率的实战经验。

2) 关键要点

  • 代码审查与自动化:坚持在代码合并前进行审查,并引入 CI、Lint 工具和自动化测试(单元与集成测试)来辅助检查命名、格式及功能破坏问题。
  • PR 管理与紧急处理:要求拆分小 PR 以降低审查难度;对于因工期紧急上线的低质量代码,必须创建 Ticket 跟踪改进,其优先级等同于正式功能。
  • 系统设计前置:编码前需输出简单的设计文档和结构图,并通过设计评审会议对齐团队思路,避免后期代码偏离规范或被推翻重写。
  • 老项目渐进式重构:拒绝一刀切推翻重写,采取先补充自动化测试(尤其是集成测试),再逐个模块替换的策略(如建立新旧项目共用的组件库以实现平滑过渡)。
  • 技术债务常态化管理:将技术债务转化为 Ticket,并在每个 Sprint 中固定分配约 20% 的时间用于偿还。
  • Sprint 周期分配创新:采用两周迭代模式,第一周专注产品需求开发与测试部署,第二周集中处理 Bug 修复、技术债务、新技术测试及公共组件开发。
  • 确立并执行最佳实践:架构设计完成后,由设计者先实现基础模块作为团队参考标准;在代码审查阶段果断拒绝不符合最佳实践的代码。

3) 风险与缺口

  • 重构执行阻力:重构往往面临工期不足、容易复发以及个人能力受限等现实挑战。
  • 人工审查局限性:纯靠肉眼审查难以发现格式规范问题,且面对大 PR 或缺乏自动化测试覆盖的代码时,审查压力大且合并风险高。
  • 重写引发抵触:在缺乏前期设计的情况下,直接要求开发者推翻重写已实现的功能,容易引发强烈的抵触情绪。
  • 老项目迁移风险:老项目整体替换风险极高,若无自动化测试兜底,任何修改都可能引入严重问题。

正文

Image 1

烂代码是一个有趣而值得深思的话题。在 Martin Fowler 的经典著作《重构:改善既有代码的设计》中,他专门提到了代码异味(code smells),并指出:“代码异味是一种表象,通常对应系统中更深层次的问题。”对此我深有体会。

如果你的系统充斥着烂代码,就要反思其中的深层次原因:需求过于频繁变动?项目进度压力大?团队技能不过关?缺乏设计?或许这些因素都有所贡献。Martin Fowler 给出的解决方案是重构,这无疑是很好的建议,但执行起来却充满挑战:

  • 我想重构,但没有工期怎么办?

  • 重构完成后,会不会没多久又回到老样子?

  • 我个人没有足够的能力去重构!

这些问题不仅仅是技术层面,还涉及软件工程和项目管理。我刚开始写代码时,也写过很多烂代码。后来见识过好的代码风格,逐渐提升了自己代码的质量,直到带团队时才真正意识到如何解决烂代码问题。我现在带领的团队代码质量非常出色,即便有新手程序员,也能确保烂代码不混入,产出效率显著高于其他团队,一个功能实现速度往往是其他团队的 1.5 到 2 倍,甚至更快。

代码审查:严格执行与自动化工具的结合

最初,我尝试通过严格的代码审查来提升团队代码质量。所有更新必须经过代码审查才能合并到主干。要注意的是,审查应在合并前,而不是事后,这样才能避免修改的优先级被降低,影响质量。

严格的代码审查确实有效,但也面临不少挑战:

  1. 代码命名和格式不符合规范,靠肉眼很难完全审查到。

  2. PR(Pull Request)太大,审查压力很大。

  3. 工期紧急时,质量不高的代码如何处理?

  4. 自动化测试覆盖不足,合并时心里没底。

  5. 代码结构混乱,但功能实现没问题,该不该批准?

  6. 原本的项目就是“屎山”代码,新代码难以彻底改善。

为此,我逐步引入了工具化和自动化手段来辅助代码审查:

  • 源代码管理和代码审查工具,清楚展示代码改动。

  • CI(持续集成),每次提交代码都通过 CI 运行自动化测试。

  • Lint 工具,自动检查命名、格式等问题。

  • 自动化测试,包括单元测试和集成测试,确保每次更新不会破坏现有功能。

此外,拆小 PR 也是关键,越小的 PR 越容易审查,质量越有保障。而对于那些急着上线的低质量 PR,我要求必须创建 Ticket 跟踪后续改进,这些改进任务的优先级等同于其他正式功能,以确保问题尽快被解决。

系统设计:先磨刀再砍柴

我还遇到过这样的情况:开发很快实现了某个功能,代码质量也不算太差,但由于没有遵循最佳实践,导致整体可读性和可维护性差。推翻重写往往会让开发者产生强烈抵触情绪,简单修改也意义不大。

后来,我找到了一个更好的方法:在写代码前,先做系统设计。通过简单的设计文档和结构图,在设计评审会议中对齐团队思路,这样在代码实现时就不会出现大偏差,也避免了重写的困境。这种做法不仅是“磨刀不误砍柴工”,还让团队成员在设计评审中学习如何做好设计和遵循最佳实践。

技术债务与老项目的重构

对于那些需要长期维护的老项目,我的策略是逐步重构而非一刀切推翻重写。首先补充自动化测试,尤其是集成测试,以确保后续的任何修改都不会引入严重问题。然后逐个模块替换,降低整体迁移的风险。例如,我最近用 NextJS 重构一个重要的前端项目,不是先写完新项目再整体替换,而是先建立组件库,让新旧项目共用组件,一步步逐渐替换和测试,最终实现平滑过渡。

技术债务也需定期偿还

技术债务并不完全是坏事,但必须有计划地偿还。我的策略是将技术债务任务创建成 Ticket,并在每个 Sprint 中分配约 20% 的时间去处理。这样可以有效地避免技术债务积累到必须大规模“清仓”的地步。

我们最近还探索了一种新的开发模式:在两周的 Sprint 中,第一周专注于产品需求开发并部署到测试环境,第二周主要修复新功能 Bug,并有足够的时间进行技术任务,例如偿还技术债务、测试新技术栈和开发公共组件。这种模式既能持续交付产品功能,也能保证技术任务的推进,得到了产品经理的认可。

最佳实践:为新手提供清晰指引

新项目或已有项目要有明确的最佳实践供新手参考,并确保团队成员共同遵守。如果是我自己设计的架构,我会在架构设计后先实现一些基础模块,形成良好的开发实践,这样其他开发者可以“照葫芦画瓢”。对于不符合最佳实践的代码,必须在代码审查阶段果断拒绝。

总结

  • 代码审查:一定要先审查再合并。

  • 系统设计:坚持先做系统设计再进行评审。

  • 自动化测试:代码必须有自动化测试覆盖。

  • 技术债务:定期偿还,保持在良性水平。

  • 老项目重构:逐步替换,降低风险。

  • 最佳实践:为团队提供清晰的开发标准,共同遵守。

这些方法帮助我的团队有效摆脱了烂代码的困扰,提升了整体开发效率和代码质量。

注:本文是2年多前在微博 https://www.weibo.com/1727858283/OAlQiEHhr 首发的,用 Canvas 帮忙润色了一下重发的,效果挺不错的。

关联主题