Meta降本增效大招之:自动清理死代码

Meta降本增效大招之:自动清理死代码_第1张图片

这是一个系列博客。介绍的是Meta如何通过弃用产品、清理代码、删除数据以实现降本增效。这对于效能平台的建设非常具有指导意义。

上一篇介绍的是如何自动弃用产品,本篇介绍的是Meta是如何实现自动清理死代码。

请关注我,我会连载这个系列。下一篇将介绍如何自动化删除数据。

文章最后有原文链接和我个人的总结。懒的同学,可以翻到文章最后。

译文:

在我们关于自动弃用产品的最后一篇博客中,我们谈到了弃用产品的复杂性,以及Meta构建的名为“系统代码和资产清理框架(SCARF)”的解决方案。以“Moments”为例,这是Meta于2015年推出的照片分享应用程序,最终于2019年关闭。我们探讨了SCARF如何通过其工作流管理功能在弃用过程中提供帮助。我们讨论了SCARF如何通过识别清理产品的正确任务顺序来节省工程时间,以及在存在跨系统依赖关系时如何阻止自动清理。这自然引出了一个问题:当存在引用资产的代码时,我们如何自动解除SCARF的阻塞?

SCARF中的死代码清理系统

SCARF 包含一个子系统,可通过静态、运行时和应用分析组合自动识别死代码。它利用这种分析来提交更改请求(Change Request),以便从我们的系统中删除这些代码。这种自动删除无效代码的方法提高了系统的质量,当无效代码中包含的数据资产引用阻碍了自动数据清理时,还能解除 SCARF 中未使用数据删除的阻塞。

代码分析

SCARF的代码分析子系统从各种来源收集信息。首先,通过Glean从我们的编译器中提取每种语言的代码依赖图。然后,通过操作日志获得的API端点的使用情况对其进行了增强,以确定端点是否在运行时被使用。比如以下特定领域的代码分析的案例:

  • • 内部开发工具和系统管理命令的脚本调用。

  • • 用于在Instagram Django后台动态呈现页面的模板钩子以及URI处理程序和路由。

  • • Async的动态引用调度方法(Meta的延迟作业执行服务)。

除了静态依赖关系图之外,SCARF 还必须能够检测任何和所有类型的动态使用情况,以便准确判断一段代码是否真的可以安全删除。SCARF 将这些信息结合起来,形成一个增强的依赖关系图。

Meta降本增效大招之:自动清理死代码_第2张图片

SCARF支持多种编程语言。这一点非常重要,因为Meta产品的客户端代码可能是用Java、Objective-C和JavaScript编写的,服务器代码是用Hack编写的,还有一些后端基础架构是用 Python 编写的。所有这些代码都应删除,因为它们通过API和其他已知形式的动态和跨语言引用关联在一起,从而形成了相同的依赖关系图。

SCARF在符号级而非文件级运行,因此可以进行更精细的分析和清理。例如,函数中未使用的单个变量将有自己的完全限定符号,这样就可以进行比文件级更精细的清理。

垃圾回收

SCARF会分析增强的依赖关系图,找出无法到达的节点和可以删除的子图,并自动生成代码更改请求,每天删除相应的代码。分析完整图的一个主要好处是,我们可以检测并删除代码库不同部分相互依赖的循环。删除整个子图可以加快删除死代码的速度,并为利用这种自动化进行废弃处理的工程师提供更好的体验。

图形包含增强信息非常重要,因为仅靠静态分析可能无法揭示通过动态引用或运行时语言特性创建的组件之间的联系。但这需要权衡利弊,因为用动态使用信息来增强图表需要对索引代码进行全面处理,还需要后续的数据分析管道来提供指标。这就增加了整个过程的端到端持续时间,从而使新特性或功能的原型开发变得更加困难。

SCARF 的早期版本通过采取不同的方法避免了这种前期成本。它单独分析每个可发现的符号,并在运行时运行查询静态和动态引用的分类器,以便找到死根节点 - 没有入站依赖项的代码片段。这不需要预先构建完整的依赖关系图,并简化了在代码库的小子集上运行系统的过程。因此,在不需要耗时的索引或数据分析的情况下,构建新的分类器原型来识别潜在的动态引用是微不足道的。

然而,更长的端到端开发周期导致覆盖范围显着提高。从分析单个符号到分析整个图表的转变导致从 Meta 最大的代码库之一中删除的死代码增加了近 50%。新方法提高了对代码库状态的可见性:有多少是活着的,有多少是死的,以及我们在 SCARF 的任何给定通道中删除了多少。

调依赖关系图

我们使用 Glean 索引的许多依赖关系都是代码调用模式,并不一定会阻止代码的删除。例如,假设我们有一个 PhotoRenderer 类,它的唯一依赖关系在如下代码中:

if isinstance(renderer, PhotoRenderer):
    return renderer.render_photo()
else:
    return renderer.render_generic()

在这种情况下,可以删除对PhotoRenderer和render_photo()的引用,并将代码更改为:

return renderer.render_generic()

在这个例子中,PhotoRenderer 类是根据 Python 语义中的一条规则内联的:如果没有任何地方实例化了 PhotoRenderer 类,我们就可以确信这段代码不可能使用第一个分支,因此它是死的。

在某些情况下,我们根据应用程序语义而不是语言语义来推导这些规则。想象一下这段代码

uri_dispatch = {
  '/home/': HomeController,
  '/photos/': PhotosController,
  ...
}

如果我们只分析语言级的依赖关系图,就不可能确定 PhotosController 是否被引用过,因为它可以通过 URI 调度机制调用。但是,如果我们从应用程序分析中得知"/photos/"端点在生产中从未收到过任何请求,那么我们就可以从字典中删除相应的条目。

鉴于 Python 的语言语义,我们没有固有的方法来推断这一点,但我们的特定领域日志和图增强允许我们告知 SCARF 此操作是安全的。

自动代码更改

在Meta,我们大量自动化代码更改。我们构建了一个内部服务,称为CodemodService,它使工程师能够部署配置以自动批量更改代码。SCARF是Meta首次在全公司范围内实现全自动代码更改的实例,它是与CodemodService携手打造的。如今,CodemodService还为Meta的数百种其他类型的自动代码更改提供了支持,包括自动格式化代码、自动删除已完成的实验、支持大规模API迁移,以及提高Python和Hack等部分类型语言的强类型覆盖率。

大规模死代码清理

SCARF 使用CodemodService创建代码变更请求,供工程师审查。这些更改请求包含人类可读的描述,告知工程师确定目标代码已证明无效的分析结果。

Meta降本增效大招之:自动清理死代码_第3张图片

SCARF已经发展到分析数亿行代码;五年来,它已经自动删除了超过1亿行代码,涉及超过37万个更改请求。在代码审查中由工程师捕获的假阳性被分类并用于改进SCARF的分析,通常反映了我们的增强图必须考虑的新的动态使用来源。有时,这些被误解的动态引用可能导致代码错误的删除,并且这些删除可能会进入生产环境。Meta还有其他机制来捕获这些问题,我们非常重视这类事件。

在某些语言中,我们对我们的分析有如此高的信心,以至于可以自动接受和合并更改请求,而无需人工干预,以更好地利用工程师宝贵的时间。

死代码清理足够吗?

SCARF的自动死代码清理加速了关闭和删除废弃产品的代码和数据的过程,但并没有完全解决。除了由互连性引起的问题外,我们不断改进我们跨所有语言、系统和框架的整合能力。准确覆盖启用系统确定什么是真正的死亡的每一种代码和数据的用途是困难的。

我们的系统也倾向于慎重行事,通过我们的BigGrep系统搜索对代码和数据的文本引用,而不仅仅依赖于Glean和我们的动态使用增强产生的精心策划的图。这是一个后备安全机制,有助于避免在其他语言中以名称引用的MySQL表被意外删除,并防止在Hack、Python和JavaScript等语言中调用代码通过字符串引用或使用eval。这种方法可能导致假阴性,但避免了假阳性。在自动删除死代码时,这些是一个更严重的问题。

正如我们在本系列的第一篇博客中提到的,SCARF提供与死代码子系统共同运作的工作流管理功能,为完全弃用产品和功能提供了一致的体验。至关重要的是,我们的工程师可以比我们的自动化更快地迭代代码更改!如果工程师了解更改已使代码的一个分支(因此整个子图)不可访问,他们可以轻松地将该删除合并到其更改中,而无需等待我们的基础设施索引新代码,分析它,最终提交其自动更改。有时,工程师发现手动删除东西比等待自动系统稍后清理更有效率。

在本系列的下一篇也是最后一篇博客中,我们将讨论SCARF的未使用数据类型子系统,该子系统与死代码子系统一起,通过自动删除死和未使用的资产,增强了Meta的数据最小化能力。

译文完

原文链接:https://engineering.fb.com/2023/10/24/data-infrastructure/automating-dead-code-cleanup/

个人总结

  • • 截止2023年,SCARF已经发展5年,自动清理了超过1亿行代码,涉及超过37万个Change Request。

  • • SCARF的核心是代码依赖图。

  • • 死代码的判断依据:

    • • 没有到达的节点或子图;

    • • 一段时间(没有规定)内没有流量的代码。

  • • 代码依赖图需要

    • • 支持多语言及跨语言引用;

    • • 不仅要支持文件级别,还要支持符号级别级别;

    • • 支持从API端点到函数级别

    • • 依赖图是可以被工程师进行微调的

  • • 构建代码依赖图的方式:

    • • 静态分析:通过Glean(https://glean.software/)

    • • 动态分析:通过运行时日志与代码进行关联;也就是说分析该API是否有外部请求。

  • • 工程方面:

    • • 在Meta,CodemodService服务负责大量自动化代码更改、格式化、删除已经完成的实验等。它是与SCARF一同构建的。

    • • SCARF每天分析依赖图,识别死代码,并使用CodemodService发起Change Request。

    • • 工程师可以对Change Request进行“假阳性”误删判断,并将其反馈给SCARF,以改进SCARF分析。

    • • 通过BigGrep系统对代码和数据的文本引用,排除一些特殊的case,如在Glean不支持的语言中引用了某个MySQL表名。

本文完

往期好文推荐:

  • Meta降本增效大招之:弃用产品

  • 我是如何进行日志降本的

你可能感兴趣的:(Meta降本增效大招之:自动清理死代码)