写点什么

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

  • 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:0710528
用户头像
李冬梅 加V:busulishang4668

发布了 1005 篇内容, 共 618.1 次阅读, 收获喜欢 1180 次。

关注

评论 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 · 江苏
回复
没有更多了

Test

bobcatzoo

UC生态系统APP开发详情

35K成功入职:蚂蚁金服面试Java后端经历!「含面试题+答案」

Java 编程 程序员 架构 面试

圆梦腾讯之路!6面阿里、5面字节、4面腾讯,终斩腾讯Offer

Java 编程 架构 面试

react native实践总结与思考

碗盆

android 跨平台 React Native

【全球软件大会】华为前端工程师分享:华为云官网的智能化实践

华为云开发者联盟

算法 智能化 华为云官网 全球软件大会 内容分发

我看 JAVA 之 线程同步(下)

awen

Java synchronized JOL 锁升级

AI框架中图层IR的分析

华为云开发者联盟

mindspore IR

Dapr:我不是Service Mesh!我只是长得很像

中原银行

云原生 Service Mesh istio Multi-Architecture dapr

扩展ADO.net实现对象化CRUD(.net core/framework)

Spook

.net ORM ado

泪目!跳槽太不容易,蚂蚁金服三轮面试,四个小时灵魂拷问

Java 面试

7月日更,红心向党,党员入驻,即送马克杯~

InfoQ写作社区官方

7月日更 热门活动

anyRTC视频连麦demo上线啦!

anyRTC开发者

音视频 WebRTC 直播 视频直播 直播连麦

2021Android高级进阶学习资料,已拿意向书!

欢喜学安卓

android 程序员 面试 移动开发

在C++中,你真的会用new吗?

华为云开发者联盟

c++ 内存 new new operator operator new

百度工程师手把手教你实现代码规范检测工具

百度开发者中心

百度 代码规范

区块链技术如何赋能医学成像?

CECBC

ONLYOFFICE-基本组成及工作原理

一个需求

onlyoffice

百度关于EMP的探索:落地生产可用的微前端架构

百度Geek说

浪潮云说丨浪潮云智能对话,想你所想,无限畅聊

基于 BDD 理论的 Nebula 集成测试框架重构(下篇)

NebulaGraph

分布式数据库 测试 图数据库 BDD

2021Android高级面试题及答案,Android篇

欢喜学安卓

android 程序员 面试 移动开发

千亿级数据迁移 mongodb 成本节省及性能优化实践(附性能对比质疑解答)

杨亚洲(专注MongoDB及高性能中间件)

MySQL 数据库 mongodb 架构 分布式数据库mongodb

GaussDB(for Redis)揭秘:Redis存算分离架构最全解析

华为云开发者联盟

redis 华为云 GaussDB(for Redis) 存算分离架构 中国系统架构师大会SACC

测量电压调节器输出纹波和开关瞬变的方法

不脱发的程序猿

硬件研发 输出纹波测量 开关瞬变测量 电源测试 测量电压调节器

智安小区建设,智慧安防小区改造建设方案

秀出天际的SpringBoot笔记,让开发像搭积木一样简单

推荐一个MySQL宝藏网站

Simon

MySQL 网站

趣谈Java类加载器

程序猿阿星

Java ClassLoader 类加载器

项目案例--吃货联盟

加百利

Java 项目 案例 6月日更

内蒙古公安重点人员管控研判平台建设方案

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