如何偿还技术债务?从小改动到大规模代码重写的实践经验

技术债务是软件开发中长期存在的问题。如何偿还技术债务、如何通过小改动理解代码库,以及什么时候应该进行大规模代码重写,都是工程团队在系统演进过程中绕不开的问题。

本篇主要讨论两个主题。

第一,如何通过偿还技术债务帮助自己进入工作状态。一个看似反直觉的观察是:做一些小的、非功能性改进,反而能帮助你更快理解新代码库,建立信心,并更快推进后续项目。

第二,大规模代码重写为什么需要高层支持。没有管理层支持的大规模重写,往往很难成功。

如何偿还技术债务?从小改动到大规模代码重写的实践经验

下面进入正文。

通过偿还技术债务进入工作状态

在修改旧代码之前,先补充一些注释,一个很好的理由是:它能加快代码审查速度。添加注释也是降低认知负担的有效方式。

当我需要花时间理解一段代码到底在做什么时,把理解过程写下来,可以帮助我记住自己已经弄明白的内容。而试着向别人解释代码,本身也会进一步加深理解。

边做边偿还技术债务,也有助于我进入“心流”状态。

我们都经历过类似场景:盯着一段代码发呆,想弄明白它到底在干什么;一会儿向上翻,一会儿向下翻;最后忍不住切到聊天工具里拖延时间。对我来说,在学习代码的过程中做一些小改动,可以逐步积累信心。很快,我就能从添加测试和注释,过渡到更实质性的修改。

偿还技术债务并不只是为了未来收益,它们在当下就能帮到我。

一旦进入心流状态,保持这种状态就变得非常重要。因此,每当我对正在进行的改动感到阻力时,我都会先做一个很小的代码修改,避免自己从心流中脱离出来。进入状态时有效的技巧,也同样可以用来帮助自己保持状态。

逐步减少技术债务,也是学习新代码库的好方法。

我大学毕业后的第一份工作,是在一家开发外汇期权定价 DOS 软件的公司。也正是在那里,我第一次学到了这一点。刚入职时,我对大量代码的功能几乎一无所知。

但每个程序里,总有一些与具体业务领域无关的代码。20 世纪 90 年代初,像我们公司开发的那类 DOS 程序,通常都有自己的文本用户界面渲染系统。即使在入职第一天,我也能比较容易地理解这个渲染系统。

我们的渲染系统内存效率很低,但这个问题可以解决。于是,在最初几周里,我重写了应用程序中的每一个“窗口”,以提升内存效率。通过这个过程,我熟悉了系统里的每一个界面,也了解了软件的结构、构建方式,以及团队的问题跟踪和版本控制流程。

换句话说,我顺手还掉了一部分技术债务,同时也摸清了整个软件栈的工作方式。

后来,在某海外协作软件公司的看板类产品团队工作时,我又做过类似的事情。我的第一个项目,是为应用支持 i18n,也就是国际化。第一步,是把代码中的每一个字符串整理到字符串文件中,以便后续交给翻译人员处理。

我的直接目标是解决硬编码字符串问题。但在这个过程中,我也更深入地了解了代码库,以及团队的工作流程。

这种方法甚至会在无意中发挥作用。比如,当我被聘用参与一个大规模代码重写项目时,我最终也成了原有代码方面的专家。因为每天为了重新实现旧功能,我都必须反复阅读原有代码。

大规模代码重写需要强有力的支持

我通常会尽量避免大规模重写。原因很简单:我见过太多失败或长期拖延的大规模重写项目,远远多于成功案例。

我个人犯过的最大错误之一,就是尝试用内存安全的 C# 代码重写一个 C/C++ 系统。这听起来似乎是个好主意,但项目缺乏必要支持,最终只能被搁置。

有些工作,只有真正完成之后才能产生价值。因此,它们需要大量时间、资源和组织承诺。

不过,我也参与过一个历时两年、最终取得成功的重写项目。这个项目让我学到了一些重要经验。后来,每当客户想进行类似重写时,我都会把这些经验分享给他们。

2004 年,我受聘于某海外非营利电力系统运营机构。这类机构负责管理区域电网,其中一项重要职责是管理电力市场。电力市场全天候运行,并决定发电价格。如果电力市场系统出现故障,就可能影响电力供应。

当时,该机构的电力价格发布系统由一堆混乱的脚本组成,使用 Bash、Perl、PHP 和 C 编写。这些脚本以出人意料的方式混合了数据库访问、HTML 生成和业务逻辑。有时,一个脚本还会生成另一个脚本。你修复了生成出来的脚本中的一个错误,下一次它又会被覆盖。

简直是一团乱麻。

我受聘的任务,是把它重写为一个全新的 Java 系统。他们选择我,是因为我既有旧语言经验,也熟悉 J2EE。

最初,团队从两个人扩展到四个人,其中包括一位拥有丰富 Java Server Pages 经验的开发人员。后来,团队又扩大到八个人,并引入了一些专门负责新系统的外部人员。他们还聘请了一位曾做过类似项目的经理,并把项目周期设定为接近两年。

我从大型代码重写项目中学到的第一课是:永远不要低估它们的难度。

这个项目的规模从一开始就被认真规划过。坦白说,最初我觉得让这么大的团队花将近两年时间重写系统,并不合理。但最终证明,我们确实需要这么长时间。

我们之所以能获得这些资源,是因为原有系统已经无法继续维持下去:代码脆弱,却又极其关键。而利益相关方把可靠性放在了首位。

代码重写过程中,也要持续改进旧系统

在重写过程中,我们确保原有系统也在持续改进。

一开始,我受聘全职开发新代码。但后来我们意识到,对我来说,最合适的安排是拿出一部分时间来维护旧代码。因为团队里有不少熟悉新系统的外部人员,但他们并不适合维护旧系统。

由于我同时参与两个代码库的开发,每当我对旧代码做任何修改时,我都会把相应变更加入新代码,或者更新规格说明。到了项目后期,所有非外部工程师都以类似比例参与两个代码库的开发。

也就是说,旧系统和新系统并不是完全割裂的。它们是在一段重叠期内并行构建、并行运行的。这种方式,与许多成熟迁移实践中强调的新旧系统并行、逐步切换非常相似。

将代码重写与用户可见的改进结合起来

我们还将代码重写与一个面向用户的改进项目结合了起来。

单纯的代码重写对用户来说通常不可见,因此很难获得持续关注。于是,我们把这次重写与客户关系管理系统,也就是 CRM 系统的迁移结合在一起。我们把这两个项目统一包装成一个 Web 应用重构项目。

当时,我们的内容管理系统也是用类似的自研脚本实现的,并由同一批开发人员维护。后来,我们迁移到了企业级 CRM 系统,并创建了一套全新的设计。

在整个项目过程中,我们主要讨论的是用户能够看到的变化,例如新的设计、更便捷的内容更新方式、内容工作流等。系统运行一段时间后,我们也开始讨论事故减少和可靠性指标改善。由于这些指标在该机构内部一直受到密切监控,因此系统改造后的提升非常明显。

对于这类周期长、参与角色多、既包含技术改造又包含业务改进的项目,团队需要把目标、需求、开发任务、测试验证、发布上线和复盘沉淀串联起来。像 PingCode 这样的智能化研发管理工具,可以帮助团队将这些环节纳入统一流程,并通过 Wiki 沉淀经验,让技术债务偿还不只是一次性的代码改造,而是可追踪、可复盘、可持续改进的研发管理过程。

如果你的系统现状已经无法维持,而且分阶段重写并不可行,那么一定要确保公司内的所有相关人员,包括高管团队,都理解并认可需要采取的措施。

不要低估所需工作量。

这是我从大型重写中学到的最重要一课。多年后,我又在某海外大型软件公司亲眼见证了类似情况:经过多年系统重写,公司从以本地部署为主,逐步转型为以云服务为主。

进行大规模代码重写时,通常需要临时增加开发人员数量。如果公司资源允许,可以通过引入外部人员或临时调配内部人员来实现。同时,也要引入熟悉新技术的专家,以及有类似项目经验的一线管理者。

将重写项目与利益相关方更容易看见的项目结合起来,也会很有帮助。这样做的好处在于,这些项目往往由同一个团队维护,也面临类似问题。只要你能找到合适的结合点,项目完成后就不仅有底层质量提升,也有可以直接展示给业务方和用户的成果。

随后,你还可以继续跟踪质量指标,用数据证明重写和技术债务偿还带来的实际收益。

文章包含AI辅助创作,作者:guo,如若转载,请注明出处:https://docs.pingcode.com/baike/5242365

(0)
guoguo
免费注册
电话联系

4008001024

微信咨询
微信咨询
返回顶部