来 DTDS 全球数字人才发展峰会,与刘润、叶军、快刀青衣畅聊成长>> 了解详情
写点什么

Uber 开源自动删除旧代码工具 Piranha,支持 Java、Swift 和 Objective-C 三种语言

2020 年 3 月 18 日

Uber开源自动删除旧代码工具Piranha,支持Java、Swift和Objective-C三种语言

近日,Uber 通过官方博客宣布开源了一款可自动删除旧代码的工具 Piranha,该工具已经支持 Objective-C、Swift 和 Java 三种编程语言。


开源 Piranha:自动删除旧代码

长期以来,Uber 会为不同用户提供不同的功能选择,也就是定制化开发。但是,如果某项功能经过验证后证明并不成功,这些代码放在代码库里就造成了技术负担,可能使应用程序变得臃肿、冗杂。一些陈旧的代码还可能会带来不必要的风险,影响终端用户体验,对工程师而言,这些技术债的消除耗时耗力,还会影响他们开发新功能。


为了使删除旧代码的过程自动化,Uber 开源了一款可以扫描源代码并删除其中过时旧代码的工具 Piranha(食人鱼)。Piranha 可以在 Uber Android 和 iOS 代码库中运行,Uber 已使用它删除了大约 2000 个过时代码。据介绍,该工具目前支持 Objective-C、Swift 和 Java 三种编程语言。


项目地址:https://github.com/uber/piranha


如何定位需要删除的代码?

为了方便定位,Uber 开发的程序中存在各种各样的标志,开发人员在 Uber 的标志管理系统中创建一个条目,并输入属性,例如标志的名称、类型、目标推出百分比、目标平台以及标志可操作的地理位置。此外,该标志是人工引入到源代码中的,所以实验平台上的标志可与移动应用程序之间建立一致关联。随后,此标志就可以用来管理应用程序的行为。


从正在运行的应用程序的角度来看,功能标志是单个键,映射到两个或多个条件,例如开/关、颜色值、大小和复制文本。在启动时,应用程序先查询标志管理系统,并为应用的每个标志检索特定处理条件,返回的值决定了应用程序中功能的存在和行为。


当逐步推出单个功能时,我们有一个控制条件(未启用该功能)和一个处理条件(已启用该功能)。我们倾向于首先将处理条件应用于小部分用户,如果推广成功,则逐渐将应用扩展到所有相关用户(例如,每个在特定地理位置上的人)。如果在发布期间出现问题,我们可以停止并收回该功能,尽可能减小对用户的影响。


该系统还可以处理同一功能的各种不同实现,例如尝试在不同的用户组上测试不同的接口(例如 A / B 测试)。


最终,我们希望向全球所有用户推广这些功能。有时,我们希望保留某些功能标记使代码中的功能可以正常使用,一旦应用出现问题,我们可以迅速通过功能标记控制应用状态,这样就不会造成整个应用程序瘫痪。但是,由于大多数功能嵌套在其他功能下,这种预留的切断开关还是无法终止大多数功能标志。


自动删除与过时标志相关的代码

当标志过时,我们需要在功能标志管理系统中将其禁用,并从源代码中删除与该标志有关的所有代码,包括目前无法实现的该功能的替代版本,这样能在很大程度上避免技术负担。


但是,这么简单的清理步骤往往会被很多开发者忽略,从而留下技术负担,这些不必要的代码会在多个维度上影响软件开发。


为了解决技术债务问题,Uber 设计并实现了 Piranha。Piranha 原意为“食人鱼”,这个名字是根据工具本身特征而得来的。Piranha 分析了抽象语法数(AST)的程序以生成适当的重构,并将其打包到 diff 中。将 diff 分配给标志的作者以供进一步检查,作者可以按原版进行实施(提交至 master),或在实施之前执行任何其他重构。我们还围绕 Piranha 构建了工作流程,以使其能以可配置的方式定期删除过时代码。


功能标记示例

让我们来看一个简单的示例,该示例说明了 Uber 源代码中功能标志的基本用法。


最初,我们在 RidesExpName 中的标志列表中定义一个名为 RIDES_NEW_FEATURE 的新标志,并将其注册到标志管理系统中。随后,我们使用功能标志 API isTreated 将标志写入代码,并分别在 if 和 else 分支下提供处理/控制行为的实现:


public enum RidesExpName implements ExpName {   RIDES_NEW_FEATURE,}if (experiments.isTreated(RIDES_NEW_FEATURE)) {     // implementation for treatment (on) behavior} else {     // implementation for control (off) behavior}
复制代码


为了使用各种标志值测试代码,对于每个单元测试,我们可以添加注释以指定功能标志的值。下面,当所选择的标志处于已处理状态时,运行 test_new_feature:


@Test@RidesExpTest(treated=RidesExpName.RIDES_NEW_FEATURE)public void test_new_feature() {}
复制代码


当 RIDES_NEW_FEATURE 失效时,所有与之相关的代码都需要从代码库中删除。这包括:


  1. RidesExpName 中的定义;

  2. isTreated API 中的使用;

  3. @RidesExpTest.注释


此外,else-branch 的内容、目前无法实现的控制行为的实现都必须删除。我们还希望删除与那些已经删除行为相关的所有测试代码。不删除这些代码会逐渐增加源代码的复杂性,使整体系统更难维护。


自动化挑战

自动检测过时标志并删除关联代码,这项技术目前还存在一定困难,这一过程中需要确定这些标志是否被人使用了以及哪些人拥有标志,再到其代码编写的细节都是很复杂的问题。因此,克服这些挑战是 Piranha 发展的关键。


使用静态分析构建 Piranha

考虑到 Piranha 的应用背景,我们设想可以通过应用静态分析来删除因过时标志遗留下来的废旧代码。


我们确定了清理的三个关键维度:


  • 删除紧邻功能标志 API 的代码。

  • 删除由于执行上一步而无法访问的代码,我们将此称为深度清洁。

  • 删除与功能标志有关的测试代码。


我们根据在代码库中观察到的编码模式,选择了一种迭代设计技术的实用方法。


我们观察到了三种标志 API:


  • 返回布尔值的布尔型 API ,用于确定执行所采用的控制路径。

  • 更新 API ,用于更新正在运行的系统中的功能标志值。

  • 返回非布尔值原始值(整数、双精度等)的参数 API ,该值与从后端控制的实验值相对应。


重构技术解析输入源代码的 AST,以检测使用功能标志 API 的存在。对于布尔 API,我们简化布尔表达式。如果结果值是布尔常量,我们将适当地重构代码。例如,如果布尔 API 作为 if 语句的一部分出现并返回为 true,我们将通过删除整个 if 语句,然后将其替换为 then 语句来重构代码。


如果更新 API,我们只需删除相应的语句。我们不处理参数 API,因为解决它们所需的工程工作量很大,而它们在代码库中出现的频率却低得多。


由于我们观察到布尔 API 不一定要在条件语句中使用,因此我们为重构设计了第二条路径。我们确定右侧是布尔型 API(Piranha 已将其简化为常量)的分配,并跟踪被分配值的变量。同样,我们跟踪返回一个布尔 API 的 wrapper 方法,该 API 简化为常量。随后,我们确定使用被分配值变量或条件语句中的 wrapper 方法,以执行重构。


最后,如果标记注释与输入处理行为匹配,我们只需删除测试的注释,如果不匹配,则要丢弃整个测试来处理标记注释测试。


Piranha 在 Uber 的应用实践

我们实现了 Piranha 在 Objective-C、Swift 和 Java 程序中的重构。PiranhaJava 能重构 Java 应用程序中与过时的功能标志相关的代码,尤其是针对 Android 平台的代码。它在 Java 的 Error Error Prone 上作为 Error Error Prone 插件实现。PiranhaSwift 使用 SwiftSyntax 在 Swift 中实现,用于重构 Swift 代码。PiranhaObjC 用于清理 Objective-C 程序中的代码,并在 C ++中作为 Clang 插件实现,内部使用 AST 匹配器和重写器来解析和重写 AST。


尽管 Piranha 作为独立工具执行代码重构任务,但是开发人员总是想不起及时清理代码,因此它的使用频率并不算高。正如 Piranha 自动化标记清除一样,我们需要一个系统来自动启动这些清除。


在 Uber,我们建立了工作流 pipeline,该 pipeline 定期生成差异和任务以清除陈旧的功能标志。Piranha pipeline 在标志管理系统中查询陈旧标志列表,并且对于这些标志中的每个标志,分别启用 Piranha,输入陈旧标志的名称、其所有人以及预期的输出行为(处理或控制)。



图注:在我们的 Piranha 工作流中,标志 pipeline 系统定期将可能过时的标志列表发送给 Piranha,Piranha 会生成一个差异并将其发送给原始标志作者。然后作者可以确定是否要放置差异。


上图展示了 Piranha 工作流 pipeline 架构图。Piranha 生成一个 diff (即拉取请求),并将其放入代码审阅系统中,该标志的原始作者为默认审阅者。作者可以接受 diff ,或者根据需要对其进行修改,也可以拒绝修改并将该标志标记为不过期。pipeline 还在任务管理系统中生成了一个清理任务,以跟踪每个生成 diff 的状态。由于开发人员可能无法及时发现问题,因此我们还引入了一个名为 PiranhaTidy 的提醒机器人,定期添加打开 Piranha 相关任务的提醒。我们观察到,目前使用 Piranha 自动生成 diff 的时间不超过 3 分钟。


使用 Piranha 删除代码

我们很高兴地宣布,Piranha 支持三种语言,包括 Java、Swift 和 Objective-C。如想要使用 Piranha,代码需满足以下条件:


  • 广泛使用功能标志

  • 具有特定的 API 以控制功能标志的行为

  • 用 Java、Swift 或 Objective-C 实现


项目地址:


https://github.com/uber/piranha


原文链接:


https://eng.uber.com/piranha/


作者介绍


Murali Krishna Ramanathan


Murali Krishna Ramanathan 是 Uber 的编程系统研究科学家。他目前致力于构建程序分析工具,以提高开发人员的生产率。


Lazaro Clapp


Lazaro Clapp 是 Uber 编程系统团队的高级工程师,他主要在为 Java 应用程序开发静态分析工具。


Rajkishore Barik


Rajkishore Barik 是 Uber 编程系统团队的编程系统研究科学家和技术经理。他目前致力于构建工具来理解数据中心的性能异常,包括为 Swift 和 Go 开发静态分析和转换工具。


Manu Sridharan


Manu Sridharan 曾是 Uber 的一名工程师,现在是加利福尼亚大学河滨分校计算机科学与工程副教授。


2020 年 3 月 18 日 13:402512

评论

发布
暂无评论
发现更多内容

架构师训练营-第二章-依赖倒置原则&接口隔离原则

而立

极客大学架构师训练营

第二周学习总结

武鹏

哪些框架是遵循依赖倒置原则的?

朱月俊

小师妹学JVM之:GC的垃圾回收算法

程序那些事

JVM 「Java 25周年」 小师妹 JIT GC

基本的面向对象原则(Basic OO principles)

旭东(Frank)

编程思维 极客大学架构师训练营

一个包子铺看懂 I/O 模型演变

小眼睛聊技术

Java 程序员 架构 后端 nio

架构师训练营 - 第二周 - 学习总结

韩挺

架构师训练营 - 第二周 - 作业

韩挺

数据库周刊28│开发者最喜爱的数据库是什么?阿里云脱口秀聊程序员转型;MySQL update误操作;PG流复制踩坑;PG异机归档;MySQL架构选型;Oracle技能表;Oracle文件损坏处理……

墨天轮

数据库

Week 02 学习总结

卧石漾溪

极客大学架构师训练营

永远招聘:打造高绩效团队的最佳姿势

伴鱼技术团队

企业文化 管理 团队建设 绩效 团队组织

第二次作业总结

朱月俊

架构师训练营第二周

小树林

这也太拧巴了吧?结局意想不到

非著名程序员

程序员 程序人生 提升认知

第二周作业

武鹏

架构师训练营 - 软件设计原则

Pontus

极客大学架构师训练营

给行动找个理由

Neco.W

行动派 决策

架构师训练营二期作业

老姜

为什么坐车会晕车呢

石云升

生活,随想 日常思考 晕车

做一个有原则的码农可好?

Dawn

极客大学架构师训练营

品软件架构原则模式之美

老姜

用接口隔离原则优化 Cache 类的设计

朱月俊

架构师训练营第二章总结

叮叮董董

依赖倒置和案例

王锟

架构师训练营 - 软件设计原则

Pontus

极客大学架构师训练营

第二次作业

朱月俊

千万不能让程序员给娃娃取名字

码农神说

程序员

ARTS打卡Week 04

teoking

ios LeetCode ARTS 打卡计划

老大吩咐的可重入分布式锁,终于完美的实现了!!!

楼下小黑哥

Java redis 分布式锁

架构师训练营第二章课后作业

叮叮董董

第二周作业

changtai

极客大学架构师训练营

「中国技术开放日·长沙站」现场直播

「中国技术开放日·长沙站」现场直播

Uber开源自动删除旧代码工具Piranha,支持Java、Swift和Objective-C三种语言-InfoQ