写点什么

十年老代码库的生存指南:如何在“屎山”中优雅地工作?

  • 2025-02-12
    北京
  • 本文字数:2895 字

    阅读完需:约 9 分钟

大小:1.40M时长:08:10
十年老代码库的生存指南:如何在“屎山”中优雅地工作?

身为一名软件工程师,在大型现有代码库中工作是最让人头痛的情况之一。首先我们没法事先演练(任何开源项目在参与体验上都跟这不一样),个人项目也因为体量不大、从头开始而很难体现这种“历史包袱”的沉重感。顺带一提,我这里所说的大型现有代码库,是指那些:

 

  • 拥有几百万行代码(比如说 500 万行);

  • 有约 100 到 1000 名工程师在同一套代码库上工作;

  • 代码库的最早工作版本至少有十年历史的项目。

 

我自己在这类代码库上已经工作了十年。下面聊聊那些我真希望年轻时自己能够知晓的经验和心得。

最大的问题来自不一致性

 

我最深看到的一个致命问题,就是忽略掉代码库的其余部分,只考虑以最合理的方式实现当前功能。换句话说,刻意限制与现有代码库的接触点,以此保证自己的干净代码不受遗留垃圾的污染。对于主要在小型代码库上工作的工程师们来说,这是种很难改掉的习惯——但大家必须努力克服!事实上,为了保持一致性,我们不仅要拥抱遗留代码,还得尽量深入地研究遗留代码库。

 

为什么一致性在大型代码库中如此重要?因为它能保护我们免受令人讨厌的意外影响、减缓代码库陷入混乱的速度,并允许各位充分运用未来可能出现的改进功能。

 

假设我们正在为特定类型的用户构建 API 端点,大家可以在端点中旋转一些“如果当前用户不属于此类型,则返回 403 错误”的逻辑。但在动手之前,我们应该先查看代码库中的其他 API 端点在身份验证中发挥何种作用。如果他们使用到一些特定的帮助程序集,那我们也应该使用该帮助程序(哪怕它笨拙、难以集成甚至对于当前用例来说有点用力过猛)。总之,我们必须得控制住自己想要让代码库中的某个小角落变得更加简洁优雅的冲动。

 

这样做的主要原因,在于大型代码库中往往埋有很多暗雷。例如,你可能不知道代码库中其实存在“机器人”的概念,它跟普通用户类似但又不完全一样,需要特殊处理才能进行身份验证。我们可能不知道代码库中的内部支持工具允许工程师偶尔以用户的角色进行身份验证,而这需要特殊处理才能顺利通过。


总之,大型代码库里肯定还有无数我们难以、甚至根本不可能知晓的情况。现有功能则代表了一条能够穿越雷区的安全路径。如果已经有某个长期存在的 API 端点在以某种方式进行身份验证,那请务必按照同样的路径进行操作,这样我们至少能保证不会被那些暗雷给误伤到。

 

最重要的是,缺乏一致性正是长期以来困扰大型代码库的核心威胁,因为其导致项目无法进行任何普适性的改进。仍然以之前提到的身份验证为例,如果我们想要引入一种新的用户类型,高度一致的代码库使我们能够直接更新现有身份验证助手集以适应这种需求。


但对于不一致的代码库,不同 API 端点所执行的操作各不相同,我们必须去亲自更新并测试每个实现。实际上,这通常足以把变化扼杀在摇篮当中,或者至少会把更新难度最大的那 5%功能剔除出应用范围——这反过来又会进一步降低一致性,因为现在我们又多了一种只适用于大多数、但并非支持所有 API 端点的用户类型。

 

所以说,当我们决定认真在大型代码库中搞开发时,应当先深入研究现有技术、并尽可能遵循其基本逻辑。

除了一致性,还有什么重要问题?


一致性就是最重要的,但除此之外,我们也可以快速过一下其他要点:

 

我们需要对服务在实践中的使用方式(即用户用法)拥有深入了解。比如哪些端点的访问频率最高?哪些端点的重要性最高(即由付费客户使用且无法优雅降级的端点)?服务必须遵循哪些延迟保证,而哪些代码运行在热路径之中?大型代码库中的另一个常见错误,就是进行“微调”——因为这类调整往往会意外触及关注流程的热路径,进而引发大麻烦。

 

我们绝不能像在小型项目中那样过度依赖自己在开发中测试代码的能力。任何大型项目都会随时间推移而积累下诸多状态(例如,您觉得 Gmail 需要支持多少种用户?)。到了特定的时间点,即使借助自动化手段,我们也不可能测试每一种状态组合。相反,我们能测的就只有关键路径,因此请谨慎编码并领先缓慢发布和持续监控来不断发现问题。

 

另外,请尽量不要引入新的依赖项。在大型代码库中,新增代码往往会永远存在。依赖项会带来持续的安全漏洞与更高的软件包更新成本,而这些成本几乎肯定会超过特定人员在公司内的任期。即使确有必要,也请确保选择广泛使用且稳定可靠的依赖项,或者是那些在必要时易于分叉的依赖项。

 

出于同样的理由,一旦有机会可以删减一部分代码,也请务必牢牢把握。这是大型代码库中最危险的工作之一,所以切忌半途而废:首先对代码进行梳理检测以识别生产中的调用者,并将其降至零,这样才能确保可以安全删除。但正如减重对于肥胖人群特别重要,在大型代码库中也没有什么能比安全删除代码更具现实价值。

 

将工作拆分成多个小型 PR 提交,并通过预加载引导其他团队的代码变更。这一点在小型项目中也有体现,而在大型代码库这边则可谓至关重要。这是因为我们往往会依赖于其他团队中不同领域专家做出的问题预测(毕竟大型项目太过复杂,没有单独哪方能够准确预测所有情况)。如果能够将风险区域的变更保持在较小且易于理解的范围之内,那么这些领域专家就更有可能注意到问题并避免引发事故。

为什么非得接手历史包袱?

 

最后,我想花点时间强调这些遗留代码库的意义。相信大家都听说过这样一个常见的观点:

 

为什么非要接手遗留下来的一团乱麻?花时间深入研究错综复杂的代码结构和业务逻辑根本就没有价值。面对庞大的现有代码库,我们的思路应该是拆分出一个个更小、更优雅的服务来进行精简,而不是身陷其中进一步令混乱变得更乱。

 

我认为这种说法完全错误。主要原因在于,一般来讲大型现有代码库会产生 90%的价值,任何大型科技企业、大部分创收活动(即实际产生经济收益以支付工程开发投入的工作)都来自大型现有代码库。虽然偶有例外,但多数科技巨头的主要业务价值仍然由这些大型现有代码库负责承载和实现。


我也见过那些小巧而优雅的服务能够为某些高收入产品的核心功能提供支持,但实际产品化代码(包括设置、用户管理、计费、企业报告等)仍然离不开大型现有代码库的功能范畴。

 

所以出于对现有业务运转方式的尊重,我们也不应该因为嫌弃而远离所谓“遗留的混乱”。毕竟这就是我们的职责所在,也是我们必须啃下的工程硬骨头。

 

另一个原因在于,如果缺少对大型现有代码库的充分理解,我们根本就不可能进行有效拆分。我见过大型代码库的成功拆分案例,但从未见过不擅长在大型代码库上交付功能的团队可以做到这一点。复杂的现实世界告诉我们,人是无法单靠第一性原理就重新设计出任何足够复杂的项目的(即真正能赚钱的项目)。正是无数极其偶然的细节,支撑着巨头们那年均数千万美元的收益。

总结

  • 大型代码库值得我们费心费力,因为我们的工资往往就从它们中来。

  • 到目前为止,最重要的就是保持一致性。

  • 永远不要在未对代码库中现有技术进行深入研究的情况下,盲目启动任何功能。

  • 如果不遵循现有模式,最好找个足够充分且有说服力的理由。

  • 务必理解代码库的生产足迹。

  • 不要指望测试能够覆盖到每一个用例——相反,要充分发挥监控的作用。

  • 把握所有机会、尽量删除代码,但在操作过程中要极度小心。

  • 尽量让其他领域专家能够轻松发现我们的错误。

 

原文链接:

https://www.seangoedecke.com/large-established-codebases/


2025-02-12 16:0711244
用户头像
李冬梅 加V:busulishang4668

发布了 1178 篇内容, 共 799.3 次阅读, 收获喜欢 1298 次。

关注

评论 2 条评论

发布
用户头像
这翻译的啥呀
2025-02-14 17:36 · 北京
回复
用户头像
这翻译一言难尽,随便找家机器翻译也不至于这样吧 You could put some “return 403 if current user isn’t of that type” logic in your endpoint -> 大家可以在端点中旋转一些“如果当前用户不属于此类型,则返回 403 错误”的逻辑 :-)
2025-02-12 17:44 · 江苏
回复
没有更多了

透过华为军团看科技之变(四):互动媒体(音乐)

脑极体

小红书严打买卖账号及刷量作弊行为:必须维护平台的公信力

石头IT视角

服务治理的工作内容

阿泽🧸

微服务 6月月更

数据库每日一题---第18天:每天的领导和合伙人

知心宝贝

数据库 大数据 前端 后端 6月月更

Discourse 新用户可插入媒体的数量

HoneyMoose

Java Core 「13」ReentrantReadWriteLock 再探析

Samson

学习笔记 Java core 6月月更

新课上线 | 每次 5 分钟,轻松玩转阿里云容器服务!

阿里巴巴云原生

阿里云 云原生 容器服务

JVM调优简要思想及简单案例-新生代回收算法

zarmnosaj

6月月更

在线JSON转YAML工具

入门小站

工具

浅谈融云即时通讯服务「日志优化」

融云 RongCloud

Larix真正的去中心化借贷平台,并开启double Mining活动

鳄鱼视界

AWS的运营管理类服务

冯亮

云计算 AWS

uni-app进阶之创建组件/原生渲染【day9】

恒山其若陋兮

6月月更

客户案例|观测云助力合思信息升级新一代可观测平台

观测云

数据资产管理

奔向架构师

数据资产 数据管理 6月月更

百度安全再次亮相高性能计算国际顶会SC 2022 — 采用Fuzzing技术防护高性能计算静默数据损坏安全风险

百度安全

百度安全 百度安全实验室 高性能计算国际顶会 SC 2022 Fuzzing技术防护

统一日志

卢卡多多

日志 6月月更

Android 修改系统音量及监听

yechaoa

android 6月月更 AudioManager

leetcode 221. Maximal Square 最大正方形(中等)

okokabcd

LeetCode 动态规划 数据结构与算法

在线文本列表批量添加行号工具

入门小站

工具

喜讯!云效度量能力获信通院先进级评估

阿里云云效

云计算 阿里云 DevOps 研发效能 研发

攻防演练中红队的内网横向扩展

穿过生命散发芬芳

6月月更 攻防演练

稳住了,别抖!—— 看GetX 的Worker如何防抖

岛上码农

flutter ios 前端 安卓开发 6月月更

vue导航路由

小恺

6月月更

一问带你彻底了解JVM-Java虚拟机内存区域详解

派大星

JVM

linux之我常用的20条命令( 之三)

入门小站

Linux

携手腾竞体育后,英特尔IMC如何加速电竞生态正循环?

科技之家

flutter系列之:flutter中的builder

程序那些事

flutter 程序那些事 6月月更

Any to Any 实时变声的实现与落地丨RTC Dev Meetup

声网

音频 RTC Dev Meetup 生态专栏 语音处理

接口测试(apipost、jmeter和python脚本)——测试工具

Xd

Java 后端 接口测试

军体拳代码

工程师日月

6月月更

十年老代码库的生存指南:如何在“屎山”中优雅地工作?_技术选型_Sean Goedecke_InfoQ精选文章