在Uber,我们使用功能标志(feature flags)定制移动应用程序的执行,为不同的用户组提供不同的功能。例如,这些标志允许我们将用户的体验本地化到我们操作的不同区域,更重要的是,我们可以逐步向用户推出功能,并尝试同一功能的不同变体。

然而,在一个特性被100%地发布给我们的用户或者一个实验性的特性被认为是不成功的之后,代码中的特性标志就过时了。这些非功能特性标志可以说是技术“债务”,使开发人员难以处理代码库,并且可能会使我们的应用程序膨胀。为了应对,可能需要进行一些不必要的操作,影响用户的性能体验,甚至影响整个应用程序的可靠性。

对于工程师来说,消除这些“债务”可能会耗费大量时间,从而影响功能的开发。

为了实现这一过程的自动化,我们开发了Piranha。这是一个通过扫描源代码,删除过时的,功能标志相关的,代码优化工具。它可以带来更干净、更安全、更高效和更可维护的代码库。在Uber的Android和iOS版代码的持续更新过程中,我们尝试运行Piranha,并使用它删除了大约2000个过时的功能标志及其相关代码。

我们相信Piranha会是一个很棒的工具,尤其适用于那些在APP部署中使用了功能标志等的组织机构。因此在介绍工具的同时,也开放了它的源代码(https://github.com/uber/piranha)。当前版本支持Objective-C、Swift和Java,开源贡献者可尝试使Piranha支持更多的语言,或者提高其执行深层代码重构的能力。

功能标志(feature flags)和它的生命周期

为了引入标志,开发人员在Uber的标志管理系统中创建一个条目,并输入诸如标志名称、标志类型、目标推出百分比、目标平台和标志运行的位置等属性。此外,在源代码中手动引入了标记,从而在实验平台的标记和移动应用程序的实例之间建立了一致的关联。从那时起,这个标志作为代码中的变量来管理应用程序的行为。

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

在最简单的情况下,当逐步推出单个功能时,我们有一个控制(control)条件(该功能未启用)和一个处理(treatment )条件(该功能已启用)。最初我们倾向于将处理条件应用于一小部分用户,如果推广成功,则逐渐扩大应用范围,以涵盖与功能相关的所有用户(例如,特定地理位置的每个人)。如果在推出过程中出现问题,我们可以停止和回滚,确保对用户的影响最小。

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

许多功能最终在全球100%的用户中推出。有时,我们会在代码中保留功能标志来保护该功能,作为非关键应用程序功能的“安全终止开关”。这样一个小功能中的潜在bug或崩溃,就可以通过关闭这个“开关”来轻松应对(否则可能会毁掉整个应用)。但是,由于大多数功能嵌套在其他功能下,因此此类主动的“安全终止开关”并不是大多数功能标志的最终状态。

我们认为与100%覆盖的功能相关联的功能标志都是“过时的”,这意味着标志本身不再起作用,可以用完整版本的硬编码来代替。类似的情况也发生在实验或处理条件下,这些实验或处理条件已经完全恢复到其控制(即无特征)状态。

过期标志导致的技术“债务”

当标志过期时,应在功能标志管理系统中禁用该标志,并且需要从源代码中删除与该标志相关的所有代码构件,包括对应功能的其他不可用版本的实现。这确保了改进的代码卫生性并避免了技术债务。

然而实际上,开发人员并不总是执行这个简单的事后清理过程,因而留下与过期标志相关的代码,导致技术债务的累积。与这些代码的存在会影响多个维度的软件开发。首先,开发人员必须对与这些过时标志相关的控制流进行推理,并处理大量无用的代码。其次,在意外情况下(例如,由于标记管理后端错误),此类代码仍然可以执行,从而降低了应用程序的总体可靠性。然后,测试中还需要保证这些无用路径的覆盖率。最后,无用代码和无用测试的存在会影响整个构建和测试时间,从而影响开发人员的生产力。

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

为了解决过期标志带来的技术“债务”问题,我们设计并实现了这个自动的源代码到源代码(source-to-source)重构工具Piranha。该工具用于自动生成差异修订(https://secure.phabricator.com/book/phabricator/article/differential/)(类似于,diff)以删除与过期特征标志对应的代码。Piranha会把标志的名字、预期的处理方式和标志作者的名字作为输入。它分析程序的抽象语法树(https://en.wikipedia.org/wiki/Abstract_syntax_tree)(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
}

为了测试不同的标志值,对于每个单元测试,我们可以添加一个注解(annotation )来指定特征标志的值。例如下面的示例,当该标志处于treated对应的状态时test_new_feature将运行:

@Test@RidesExpTest(treated=RidesExpName.RIDES_NEW_FEATURE)public void test_new_feature() {…}

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

1.RidesExpName中的定义。

2.它在已创建的API中的用法。

3. @RidesExpTest的注解。

此外,必须删除else分支的内容,该分支实现了现在不再访问的控制行为。我们还想删除涉及此删除行为的任何测试的代码。

不删除这些代码???这些代码组件会逐渐增加源代码的复杂性,影响总体的可维护性。

自动化的挑战

可以预料,在自动检测过期标志和相关代码删除方面存在许多困难。从确定标志是否正在使用到标志的所有者,再到其代码细节的实现。克服这些挑战是Piranha的关键。

过期标志

确定一个标志是否过期是非常重要的。首先,作为某种功能或控制,相关的标志应该被百分之百地上线。否则则可能意味着它的实验仍在进行中。即使当它已经上线时,开发人员可能还没有准备好消除这个标志。例如,标志可以用作终止开关或用于监视调试信息。因此,即使标志已完全上线,它们仍可能在使用中。

标志所有权

在Uber早期的发展过程中,确定过期标志的所有权信息变得非常困难。即使我们可以完全确定标志的作者身份,该作者也可能已经异动到另一个团队或离开了公司。

代码风格

与功能标志相关的代码没有任何标准限制,这增加了自动化工具设计的复杂性。例如,与标志相关的代码的helper函数很难与代码中的任何其他函数区分开来。此外,有些测试中开发人员允许对标志进行手动状态更改,这类测试的复杂性可能会限制工具执行全面的清理。例如,当与标志相关的代码正在进行单元测试时,有时不清楚是否可以完全放弃测试,因为相关功能已被删除。有时也需要删除测试主体中的特定状态的更改,以便可以继续测试剩余的功能。

用静态分析法构建Piranha

基于以上背景知识和分析,我们可以通过静态分析(https://en.wikipedia.org/wiki/Static_program_analysis)来有效的删除过期标志相关的不必要代码。

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

1.删除与功能标志api直接关联的代码。

2.删除由于执行之前步骤而不可达的代码。我们称之为深度清理。

3.删除与功能标志相关的测试。

为了在所有三个维度上执行精确的清理,必须执行可达性分析,以识别无法到达的代码区域,并实现算法来识别与特征标志相关的测试。在理想情况下完全自动化的实现,可以确保开发人员只需检查删除并将更改合并到master分支中,但它仍需要克服两个挑战:确保执行清理的底层分析是健全和完整的,以及对应的方法可实现并且在处理上百万行代码时仍然能够保证时效性。

由于以健全和完整的方式确定可达性通常是不切实际的。这种方式,开发者在清理后的干预量是未知的,而且工程投资的回报也是不清楚的,因此我们决定不建立这样复杂的分析。取而代之的是,我们根据在代码库中观察到的编码模式,选择了一种迭代设计的可行方案。

我们发现通常有三种标志api:

1.返回布尔值并用于确定执行的、控制路径的布尔api

2.更新运行系统中的功能标志值的更新api

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

我们的重构技术会解析输入源代码的ast,以检测所考虑的特性标志api的使用的地方。

对于布尔api,我们执行简单的布尔表达式简化。如果得到的值是一个布尔常量,我们会对代码进行适当的重构。例如,如果布尔API作为if语句的一部分出现并简化为true,那么我们通过删除整个>if语句来重构代码,并将其替换为then子句中的语句。

对于更新api,我们只需删除相应的语句。

对于参数api,我们不会处理。因为处理它们所需的工程工作量要大得多,它们在代码库中出现的频率要低得多。

由于我们观察到布尔api不总是在条件语句中使用,因此我们在重构中设计了第二个步骤。我们识别右边是布尔API的赋值,Piranha将其简化为常量并跟踪该变量。类似地,我们跟踪包装器方法,返回一个被简化为常量的布尔API。随后,我们定位赋值变量或包装器方法在保护条件下的使用,以此来执行重构。

最后,对于测试的标记注释,如果注解(annotations)与输入不匹配,我们会通过丢弃整个测试来处理。否则,我们只需删除测试的注释。

综上所述,Piranha将以下内容作为输入:过期标志、处理行为和标志的所有者。它分析了在预定义的特性标志api中使用此标志的代码,并根据处理行为重构它以删除各种相关代码。

Piranha在Uber的应用

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

虽然Piranha作为一个独立的工具可以执行代码重构,但开发人员并不总是优先考虑标志的清除,因此可能不会那么频繁地使用它。就像Piranha可以自动清除标记一样,我们需要一个系统来自动启动这些清除。

在内部,我们构建了一个规范工作流,该管道定期(我们是每周执行)生成差异和清理过期功能标志的任务。这个工作流向标志管理系统查询过期标志的列表,对于每个这些标志,它分别调用Piranha,并以过期标志的名称、所有者和预期的输出行为(处理或控制)作为系统的输入。

图1. 在我们的Piranha工作流中,标志管理系统定期向Piranha发送一个可能过期的标志列表,Piranha生成一个diff并将其发送给原始标志作者。然后,作者可以决定是否处理diff。

上图1显示了Piranha工作流的结构图。Piranha生成一个diff(即一个pull请求)并将其放入我们的代码审查系统中,标志的原始作者是默认的审查者。作者可以按原样接受diff,或者根据需要修改它,或者拒绝并将标志标记为不过期。工作流还会在我们的任务管理系统中生成一个清理任务,以跟踪每个生成的差异的状态。由于开发人员不能总是及时处理这些差异,我们还引入了一个名为“PiranhaTidy”的提醒机器人,以定期为相关任务添加提醒。

Piranha工作流使用一种启发式方法,将超过一个特定期限(例如8周)未在标志管理系统中修改过的视为过期的志,并为这些标志生成diff。负责处理Piranha输出的各个团队,为标志配置确切的过期时间。我们观察到目前使用Piranha产生diff所需的时间少于3分钟。

在你的代码中使用Piranha

对三种所受支持语言,我们很高兴地宣布食人鱼现在是开源的。如果您的代码满足以下条件,我们相信该工具将对您的团队有用:

1.广泛使用功能标志

2.有特定的api来控制标志的行为

3.是用Java、Swift或Objective-C实现的

为代码库设置Piranha 非常简单:在属性文件中定义与功能标志相关的api和预期行为,然后以标志名称和预期输出为参数运行Piranha 。有关在每种语言如何使用Piranha 的详细信息,请参阅文档(https://github.com/uber/piranha)。

我们欢迎开发人员为Piranha贡献代码 。所有的开发人员都是受欢迎的。并且,致力于Piranha 的实现,可能是理解该领域非专家程序分析细微差别的一个非常好方法。有许多有趣的项目涉及到优化Piranha 生成的代码重构、将Piranha 扩展到其他语言(如Kotlin、Go等),以及设计和实现其他与功能标志相关的程序分析。那么,请下载Piranha 的源代码(https://github.com/uber/piranha)开始吧。

如果您有兴趣加入我们的编程系统团队(https://www.uber.com/us/en/about/science/?_ga=2.144053109.1910292101.1588403826-1280549756.1585307101),从事编程语言、编译器和软件工程方面的项目,并且在程序分析、编译器和相关领域有学术或行业经验,请联系我们的团队(programming-systems-group@uber.com)。

有关Piranha 的详细研究论文(https://github.com/uber/piranha/blob/master/report.pdf)将发表在韩国首尔举行的软件工程、软件工程和实践跟踪国际会议(International Conference of Software Engineering, Software Engineering and Practices track )(ICSE-SEIP'20)上。

1.翻译自Uber Engineering,原文地址:https://eng.uber.com/piranha/ 外网连接不稳定,附上英文原文,按需自取。

2.文中we第一人称未做修改,意为原文团队(本人非团队成员)。

3.并不生产水,只是搬运工

Piranha介绍:过期代码自动删除的开源工具相关推荐

  1. html段落自动删除,利用JS代码自动删除稿件的普通弹幕功能

    事情的起因是在b站投稿了一个高级弹幕测试的视频(av9940487),但是由于b站的弹幕池机制是新的弹幕顶掉旧的弹幕,所以导致一些人发的高级弹幕很快就被顶掉了. 所以就想着写个脚本来自动删除属性为普通 ...

  2. Excel文档VBA代码自动删除

    有时候,不想让自己编写的代码让别人长期使用或换地方使用,又不想让文档自杀,就可以设计限期清除代码,或其他限制条件下清除.具体限制条件可参考作者<Excel文档自杀程序设计>Workbook ...

  3. 关闭IDEA保存代码自动删除末尾空格

    问题描述: 其他开发工具的代码导入idea后,idea修改了代码保存时发现自动把行末的空格删除了,在查看修改记录时很费劲.并且给代码合并带来了干扰.下面给出了解决方法. IDEA版本:2020.3.2 ...

  4. 程序员幸福感拉满:一键为代码自动生成注释的工具,拿走不谢!

    提升程序员幸福感的CodeGeeX代码解释功能上线了! 一个"古老的传说":程序员最讨厌的2件事:一是编写程序时要写注释,二是看别人编写的程序里没写注释: 可见,手动为代码添加注释 ...

  5. python代码自动补全利器----Kite介绍安装使用教程

    目录 Kite介绍 Kite安装 总结 介绍一款针对python代码自动补全的工具 -- Kite ,不需额外的环境配置,more importantly是免费的哦~~对于经常使用Python的朋友们 ...

  6. IIS日志自动删除程序 收藏

    很多使用Windows IIS的站长可能都会遇到这个问题,就是服务器的IIS日志增长经常会导致磁盘空间被占满,而IIS也没有自动删除日志的功能,因此需要经常关注即时清理日志,因此我这里就介绍一个能够自 ...

  7. dev c++代码自动补全_python3代码如何自动补全?

    不知道小伙伴们是不是在勤勤恳恳的一点点写代码,小编最近写代码非常轻松.虽然这样说出来会引起群愤,但不得不说真的很好用.其实就是一个补全代码的工具,在使用之前小编也抱有怀疑的态度,将信将疑.不过最后还是 ...

  8. SQL Server 定时自动备份和自动删除方法图文超详细步骤

    SQL Server自动备份和自动删除 打开数据库管理工具,选择管理–>维护计划 右键–>维护计划向导 点击下一步,计划名称可以自定义,在计划选项上,点击更改,频率更改为每天(可以自己的实 ...

  9. Python代码自动排版工具(PEP8风格),autopep8

    autopep8是一款将python代码自动排版为的工具,autopep8安装 1.安装autopep8 pip install autopep8 2.PyCharm配置autopep8 Progra ...

  10. 21个必须知道的机器学习开源工具!

    作者 | SebastianScholl 译者 | 刘静,责编 | 郭芮 出品 | CSDN(ID:CSDNnews) 本文将介绍21种用于机器学习的开源工具. 以下为译文: 你肯定已经了解流行的开源 ...

最新文章

  1. 建议你吃透这68个内置函数!
  2. js事件详解二:鼠标和滚轮事件
  3. PHP判断iPhone、iPad、Android、PC设备的方法
  4. applicationSettings设置和appsttings
  5. python素材库_python的JSON库
  6. HDU-3460 Ancient Printer 字典树
  7. 领导者的资质——学习笔记(2):领导者的人格
  8. 20万+奖金池,“智在飞翔”2021 • 无人飞行器智能感知大赛,战火重燃 • 等你来战!!...
  9. keil5函数 默认返回值_Python列表有什么内置函数可以使用,怎么使用这些函数
  10. Windows Serer2003域升级到Windows Server2008R2域
  11. 百练 2972 确定进制 解题报告
  12. JavaScript-解构赋值
  13. python核心数据类型
  14. 2021 年百度之星·程序设计大赛 - 初赛二 1002 随机题意
  15. 印能捷服务器中文字显示方块,修改Preps中文标记字体解决PJTF/JDF无法导入印能捷问题...
  16. 专业能力和表达能力,你觉得哪个更重要?
  17. 在ThinkAdmin中增加显示数据表格汇总金额
  18. 黄蓝专场之 | 小蓝单车生死故事
  19. (20200208已解决)PyCharm中Tab键无效
  20. markdown符号使用

热门文章

  1. win7 去除快捷方式小箭头
  2. win10截图截屏快捷键 截图截屏工具
  3. 读 Robert C. Solomon 之《大问题:简明哲学导论》兼序
  4. 开发人员如何规划自己的职业生涯
  5. linux———/bin/sh、 /bin/bash、 /bin/dash的区别
  6. UML实例(五):在线购物系统设计类图
  7. 《MATLAB 神经网络43个案例分析》:第37章 基于灰色神经网络的预测算法研究——订单需求预测
  8. 机器学习:数据归一化(Scaler)
  9. Silverlight 减小 Xap 的大小
  10. Silverlight实用窍门系列:1.Silverlight读取外部XML加载配置---(使用WebClient读取XAP包同目录下的XML文件))【附带实例源码】...