当前位置: 首页 > 后端技术 > Java

射击工程腐败-治理理念全解读

时间:2023-04-01 18:19:40 Java

作者:刘天宇(钱枫)系列文章回顾《向工程腐化开炮 | proguard治理》《向工程腐化开炮 | manifest治理》《向工程腐化开炮:Java代码治理》《向工程腐化开炮|资源治理》《向工程腐化开炮|动态链接库so治理》。本文是系列文章的最后一篇,重点介绍整体治理思路、方案设计,以及背后的思考和取舍。工程质量是任何产品能够快速、高效、稳定地迭代业务功能的基础。也是给用户带来良好的产品体验不可忽视的因素。也是任何优秀工程师对卓越的期望和追求。然而,工程腐败是任何大型项目都不得不面对的问题。它广泛而零散,隐藏在不易被察觉的“角落”,影响着项目的方方面面。工程腐败与项目本身如影随形,贯穿于项目生命周期的各个阶段。时间、人、代码、流程、规则等任何因素的变化都会导致腐败,从检测到修复,从系统分析到制定解决方案,再到坦诚接受和常态化可持续治理,本文将逐一探讨他们一个一个。在项目成熟之前,腐败问题深藏在代码中,一般会显着降低研发效率,但线上问题很少见,很容易作为单点问题修复。但是,随着腐败程度的加剧,同类型问题出现的频率越来越高,逐渐闻到淡淡的“腐败”的味道,这也是为什么会有后续的一系列分析、程序设计、工具和平台开发以及治理实践。看下图,很多研发同学可能都有亲身经历:1.1闻到了腐败的味道版本os适配等方向,随着各项治理项的不断深入和时间的推移,遇到了各种各样的问题,例如:资源冲突导致多次构建后apk没有变化会出现资源值不一致,最终导致上线问题;java代码修改会导致调用不兼容,最终导致线上java异常;线程随意使用,没有统一控制。一方面性能堪忧,另一方面线程数过多超过某些设备自定义限制导致OOM异常;无用代码&资源&功能模块导致包体积不断增大;apk构建时间越来越长,严重影响研发效率;此类例子可列举数十条,此处不再赘述。当我试图从整体的角度来看待和思考这些问题时,我发现了背后的强大敌人——工程腐败。工程腐败,简单来说就是无用/冗余/不合理代码的不断积累,更容易出错,更难定位问题,而且迭代越快,腐败越快,即使没有任何迭代,随着新版本的os外部环境的变化,比如列表、越来越严格的隐私合规法规等,都会导致现有代码出现问题。接下来,深入研发迭代过程,看看腐败从何而来。1.2腐蚀分析如前所述,导致工程腐蚀的因素有很多,但最原始的因素只有两个:时间和人。时间意味着项目外部环境的变化。例如,目标设备中的OS版本号会不断升级,研发工具链、IDE等也会迭代更新。静态工程代码会随着时间慢慢衰减。与时间对工程腐败的缓慢变化影响相比,以人为主导的快速工程迭代是快速工程腐败的最大来源。既然如此,我们就来关注一下,在一个APP版本迭代交付过程中涉及到哪些角色,它的核心诉求是什么,工程腐败是如何在这样的“土壤”中不断积聚的。上图显示了一个典型的移动应用程序版本迭代和交付过程。对于大型应用和研发团队,可能每个角色都有专门的职位和人员,而对于小型应用和研发团队,可能每个角色都有一个人负责。多重角色:产品和设计,负责功能、UI、交互设计,关注创意和功能给用户带来的价值,以及视觉和交互的流畅和炫酷;研发和测试,在收到产品需求和设计稿后,最后,负责代码开发、实现、效果&质量保证、研发和测试的同学,往往希望需求和设计一旦确定,就不会一直改变。此外,他们也希望尽可能复用已有的逻辑和功能,避免不断推翻和重做的要求和设计风格有天然的“抵触”。最后,希望多花点时间保证代码质量和验收效果;PMO和PTM负责版本节奏,控制发布流程,关心整体需求吞吐量,以及过程和在线质量;渠道和运营负责通过各种渠道将新版app按时交付给用户,并通过层出不穷的运营手段获取新用户,全面快速增长用户对app功能的使用;在前期流程中,安全法务需要确保及时解决APP的安全漏洞,不存在隐私合规等相关风险问题。最终,当用户获取或升级到最新版本的应用时,他们的核心诉求是“好用吗?好玩吗?”。除了用户之外,还有各种监管和测试机构。获取新版APP后,他们会根据现行法律法规,检查使用APP过程中是否存在“违规”行为。在这样一个app版本的交付过程中,可以看出每个角色的侧重点是不同的,同时所有角色的诉求最终都必须通过代码来承载。工程腐败直接来自开发人员的代码生产活动。开发者自身的意志、技能、经验确实会极大地影响代码质量。但是,现代企业级应用的功能非常复杂,不可能让所有的开发者都参与进来。他们可以知道应用程序的所有代码,因此工程或代码控制的本地化可能是工程腐败的更重要因素。1.3Corruption问题的拆解在分析完corruption之后,我们将进一步对Android项目的corruption项进行更细粒度的拆解。从Android项目中包含的所有“代码”的类型来看,可以分为以下五种:其中,项目配置是指构建apk过程中使用的相关配置,配置内容本身会没有进入最终的apk。项目配置损坏主要影响项目本身的复杂性,甚至是构建过程中耗时的,比如大量的proguard配置项。其他四种类型,manifest、java代码、资源、动态链接库so,也都是可能构成apk的“元素”,它们之间或者彼此之间可能存在各种腐败问题,直接导致稳定性和apk性能、包大小、UI&功能异常、隐私合规风险等,或增加出现这些问题的可能性。在实际的工具开发和治理实践中,也是按照上述类型来实现分而治之。对策在完成腐蚀发生的分析和按类型拆解后,需要制定有效的对策。首先,必须明确并始终牢记的指导原则是:“无论多么容易或多么困难,都以正确的方式做正确的事”。“正确的事情”往往更容易定义和达成共识,但“正确的方式”却有些困难,因为有时“错误的方式”意味着可以快速达到预期结果的捷径,例如:假设我们需要App中的所有线程都是通过切换到一个统一的线程池来实现的。有两种方法可以做到这一点。一种是直接使用build-timeaop技术直接替换线程调用代码,另一种是建立非统一的线程池。利用端口机制逐步修改存量代码,同时保证有效防控增量代码。显然,第一种方法可以快速达到目的,但是会增加apk构建的耗时。同时,如果aop处理本身出现问题导致替换不成功,或者替换过程异常终止导致字节码替换不完整,那么又是一次“工程腐败”。第二种方法不能快速达到目的,但可以有效遏制腐败趋势,逐步消化存量问题。虽然卡口本身需要日常审批和评估,存量代码清理也不是一蹴而就的,但在代码源头直接修正才是关键。解决工程腐败问题的“正道”。2.1PeoplevsProcess工程腐败源于版本迭代过程中人对工程代码的不合理改动。因此,工程腐败治理需要围绕“人”和“过程”展开。对于人为因素,业界已经有非常成熟有效的做法,例如:进行代码审查、制定代码规范、定制IDELint规则、持续技术培训等,都可以提高代码设计和编码水平开发人员,从而从源头上减少损坏代码的生成。此外,还能潜移默化地提升研发??团队的整体工程素质和素质,为工程质量带来更全面的提升。但是,这种方式也存在一些不容忽视的问题:参与项目的开发人员的技术认知、水平和理解能力不一致。也会很高。就工程腐败而言,完全依靠这些围绕人的计划具有非常高的不确定性,防控腐败需要确定性机制“守门”。同时,防控本身需要做到一个比较低的成本,所以我们把重点放在过程上。过程是客观的、固定的、有保证的。一方面,以全面的apk检测分析技术为核心,准确定位损坏项,并在流程关键节点部署检查点,及时发现并就地处理,做到零创新。增加。另一方面,针对存量腐败项目,提供多种辅助工具,降低整改风险和成本,提高效率。冰冻三尺非一日之寒,解冻的过程不能变成大跃进的清扫模式。而是需要在尽可能不影响日常研发活动的情况下逐步迭代,最终实现清库存。这些围绕人员和流程的解决方案不是一种选择,而是应该相互补充。前者侧重于从源头上减少腐败项的产生,后者侧重于不分青红皂白地阻止可有效检测的腐败项进入最终的apk。同时增强开发者的防腐意识,促进代码审查和代码规范的有效实施,从而形成良性循环。2.2作为核心的apk检测分析技术,分析工具具体包含哪些能力?看下图:上图是对目前检测分析技术的总结,可以分为冗余冲突、关键配置、引用关系、辅助效率提升四种。前三种直接对应具体的腐败项,最后一种是帮助开发者在日常研发过程中更好的定位分析问题。对于每一种检测能力,我这里就不赘述了。在《烧成工程腐蚀》系列文章中,结合具体实践给出了相关解释。2.3Checkpoint系统的检测能力如何与流程相结合?下面我们来看一下checkpoint流程示意图:对于开发/测试同学来说,测试推广、集成、灰度/正式版发布等关键节点需要构建apk,同时会自动触发已部署的各种检测和分析。如果是本地打包,如果测试失败,会直接构建失败,失败原因中会给出相关信息;如果打包在CI/CD平台上,checkpoint的结果会以平台页面的形式呈现;无论是哪种模式,都会中断研发同学修复问题后,流程会继续。这样就实现了对腐败问题的及时感知和就地修改。以平台模式为例,每次提交测试/集成时,apk构建都会触发checkpoint检测,如果任何checkpoint项失败,进程就会被阻塞。刺刀结果的一个例子如下:有了这样一套机器能力和机制之后,我们来看看如何管理和预防各种腐败问题。首先,先厘清“模块”的概念,它对工程腐败和治理的影响,以及工具构建和治理的实践。模块管理一个完整的apk的生成,可以认为是一个“搭积木”的过程;每个构建块可能包含java代码/资源、Android资源、AndroidManifest文件、动态链接库和混淆器配置。将相似的元素定期拼接、混合、压缩,成为最终的apk文件。用更专业的术语来说,这些“构建块”是模块。模块提供了功能复用的可能性,也为并行研发模型提供了基础。一般来说,项目越大越复杂,其模块化程度越高。工程腐败的发生,本质上是由于功能复杂和代码改动造成的。虽然模块化本身会带来一定的腐败问题,但更重要的是为工程腐败问题的治理提供了便利。想象一个应用程序,数百人分成十多个团队参与迭代。如果所有的代码都在一个app项目中开发,更不用说如何解决代码协同问题了。大挑战。在真实工程领域,模块化程度(正常工程选型)会随着功能和开发人员的增加而不断增加。在这个前提下,工程腐败控制首先要做的就是清楚地了解每一个具体的腐败问题,它来自哪些模块,是对问题进行分发和处理的前提。下面先给出模块的分类,然后介绍针对模块开发的几种“辅助分析能力”,以及基于它们的治理实践。3.1模块分类app工程中以外部依赖的形式引入的jar/aar,以及与app工程并行的子工程,可能是日常开发过程中暴露最多的模块类型。此外,Andriod还原生支持其他类型的模块。从apk构建的角度来看,完整的模块分类图如下:上图展示了5类模块,几个维度:apk构建过程中是否需要源码编译,是否存在maven仓库中,以及可能存在的依赖关系。下面分别说明一下:app-project只有一个,用于生成apk,包含源码,所以需要源码编译。可以依赖子项目、本地jar、flataar、外部模块;子项目可以有0个或者多个,一般和app-project并行,也包含源码,可以依赖子项目、本地jar、外部模块;本地jar不能单独存在,java代码已经以编译类字节码的形式存在,不能依赖其他类型的模块;flataar是Android原生提供的一种引入non-mavenaar的方式,同样不需要源码编译,不能依赖其他类型的模块;externalmodule,即外部依赖模块,不需要源码编译,可以依赖其他外部模块。依赖信息位于maven仓库对应的pom文件中。一般来说,一个app的“诞生”是从一个app-project开始的:所有的代码和资源都写在这个project中,当然也会以外部的形式引入(依赖)一些二方和第三方库模块;随着app承载功能的增加,复杂度也会增加。这时候,可能会有更多的开发者加入进来,经过一段时间的不断迭代,可能会迎来第一次模块化“革命”:将通用功能拆解为多个子项目;开发人员的增加会导致代码协作成本的增加。这时候可能需要将单个代码仓库拆分成多个,方便并行开发。这时候,迎来了第二次模块化“变革”:代码仓库拆分,以及更细粒度的模块拆分,研发并行度不断提升。最终会演变成模块化的终极形态:app-project成为打包apk的“外壳”,几乎所有的代码都被拆分成独立的模块和仓库,以外部模块的形式集成在app-project中依赖(introduce),开发并行度高很多大型App基本完成了上述演进过程,同时也出现了新的问题。接下来,我们在模块的维度上,开发了哪些工具,进行了哪些治理,一一描述。3.2辅助分析能力辅助分析能力主要是从完整apk构建的角度,为开发者提供模块及其依赖信息,解决各种日常问题,例如:“我更新了一个模块的版本号,为什么没有包含在apk?代码还是旧的?”——查看这个apkbuild,目标模块的最终版本号是多少,如果没有更新,那么肯定会出现这个问题。“我删除了模块,为什么apk里面还有相关的代码/资源?”——查看本次apk构建,目标模块是否参与了apk构建过程,app项目是直接依赖import,还是其他模块间接依赖import,快速定位原因。“我在一个模块项目中使用了另一个模块中的方法,但是在apk中找不到这个方法,是什么原因?”——查看依赖此apk构建的另一个模块的版本号,在目标工程中升级此模块依赖的版本号,重新编译目标工程,查看方法是否被删除、转移或签名改变.接下来简单介绍一下各个辅助分析能力。外部依赖模块列表外部依赖模块列表会统一输出所有参与本次apk构建的外部依赖模块,以及它们的版本号和类型。示例结果:com.youku.arch:testlib:0.1-SNAPSHOT@aarcom.youku.arch:testlib2:0.3@aarisdetectedbydependencies在apk构建过程中,一些外部依赖模块被间接依赖(不在app项目中)直接声明依赖),这种间接依赖存在于maven仓库中模块对应的POM文件中。通过依赖检测功能,可以很方便的找出一个模块直接依赖了哪些其他模块,用于下线模块或者判断从属关系(根据依赖关系,判断模块属于哪个上层业务).示例分析结果:com.youku.android:y-core|--[provided]com.youku.android:ct-ad|--[compile]com.youku.android:catl|--[runtime]com.youku.android:MtReccom.tb.android:z_dev|--[compile]com.tb.android:zcore注意这里的分析结果是依赖关系。本例中,com.youku.android:ct-ad模块在provided模式下声明依赖com.youku.android:y-core模块;com.youku.android:catl模块声明依赖com.youku.android:y核心模块;其他内容等等。其中,依赖类型一般包括以下几种类型:编译。这种类型的依赖,如果不加额外的exclude设置,会导致模块被打包到apk中;假如。这种类型的依赖不会导致模块加载到apk中;运行。这种类型的依赖不会导致模块被打包到apk中。当然,当模块发布到maven仓库时,pom文件的内容是可以自定义的,所以如果模块发布时,项目中其他模块的依赖没有正确写入pom,那么上面的检测results也会有相应的Error信息,比如:缺少真正的依赖模块,依赖类型与实际不符,包括冗余的依赖模块等,这样就很难感知到它所依赖的模块已经升级了:模块本身在构建的时候,仍然使用对应依赖的旧版本模块,所以可以编译,但是编译apk的时候,很可能是它所依赖的模块的版本号升级了,导致一些不匹配的引用。不匹配依赖检测只是为了方便各个模块的开发,清楚地掌握该模块在编译时所依赖的其他模块的版本号与这些模块在编译apk时所使用的版本号的区别,从而在模块项目中及时实施。依赖于模块版本号的升级操作。示例分析结果:com.youku.android:YTask|--com.youku.android:BFra:1.0.0-SNAPSHOT==>1.0.0.44|--com.youku.android:BUIKit:20190617-SNAPSHOT==>1.0.1.66|--com.youku.android:YUI:1.4.2.16-SNAPSHOT==>1.4.10在上面的例子中,编译YTask模块时,依赖的BFra模块是1.0.0-SNAPSHOT版本,并且在构建apk时使用的BFra模块是1.0.0.44版本,以此类推。另外,它还额外提供了一个功能,将所有外部依赖模块的pom文件统一输出到apk构建产品文件中,方便集中查看和定位问题。3.3治理实践在以上辅助分析能力的基础上,有两种情况会给构建的apk带来不确定性和隐患。因此,它也成为模块腐败的直接治理对象。快照版本号在apk构建之初,直接从maven仓库下载外部依赖模块对应版本号的jar/aar文件,参与后续的构建过程。其中SNAPSHOT版本号可以随时更新jar/aar到maven仓库,但是在构建apprelease版本时,预计不会出现这种情况,会带来各种不可预知的线上风险。因此,在apk构建过程中,需要严格控制是否存在带有SNAPSHOT版本号的外部依赖模块。为了开发快照版本号检测功能,筛选掉所有参与apk构建过程的具有快照版本号的外部模块。示例内容如下:com.youku.arch:testlib:0.1-SNAPSHOTcom.youku.arch:testlib2:0.2-SNAPSHOT进一步迭代app版本中的关键节点,如:集成,灰度/正式版发布,以及利用这种检测能力形成刺刀。优酷在几年前就以本地检查点(apk构建失败)的形式推出了这个功能,并将在2021年将这个检查点整合到整个检查点系统中,成为检查点项目之一,共有7个拦截,有效防止快照版本模块被引入到apk构建过程中。快照取决于开发阶段。为了方便模块间的联调,通常将依赖的模块版本改为SNAPSHOT。联调后的正式版打包过程中,如果依赖模块的SNAPSHOT版本号没有改回正式版,而在这个时间窗口内,一旦更新依赖模块的SNAPSHOT版本,就会导致官方模块版本在编译时依赖非预期代码,最终导致apk运行时出现各种不兼容问题,例如:API不兼容(类、变量、方法签名不匹配)、常量不一致(常量会被扩展当编译模块时)。快照依赖检测功能就是为此而生。检测结果中列出了每个模块依赖的快照版本号模块,以及构建apk时该模块对应的版本号。示例内容如下:com.youku.android:YHPage:1.9.35.5|--com.ali.android:VCommon:20210309-SNAPSHOT==>11.1.6.4|--com.youku.android:YRes:20210309-SNAPSHOT==>1.0.44.2com.youku.android:OUtil:1.0.4.11|--com.youku.android:OService:20210105-SNAPSHOT=>1.3.8.2作为反腐项目,优酷将在2021年初,当时pom文件中有200多个模块有snapshot模块依赖,当时被加入白名单,将在下一个版本迭代过程中逐步清理。截至目前,已清理近40%,效果显着。同时对app版本的关键节点进行迭代,形成相应的流程关卡。一年来共拦截25次,有效杜绝了由此引发的网络风险问题的发生。与上述其他治理实践相关的腐败控制只是反工程腐败持久战的前线。针对之前项目腐败的元素级分类拆解,打通了以下“五大战场”,可以去查看详情(点击跳转):proguard配置manifestjava代码资源动态链接库还等什么近两年在优酷的项目中能不能做腐败的实践,得到了很多研发同学的支持。他们凭着聪明才智、热情和勇气,及时解决了新的问题,一点一滴地消化了已有的技术债,用长期的坚持和努力换来了现在的项目腐败。整体问题明显减少。“用正确的方法做正确的事,无论简单还是困难”,这不仅是优酷在设计和管理工程腐败解决方案时坚定遵循的原则,也是本系列文章想要传达的技术理念。目前,工具能够检测到的具体腐败问题加起来只有20余项。与工程腐败的冰山一角相比,毫不夸张地说,这真的只是冰山一角。更何况,这里给出的解决方案只能解决一类问题,对于极其复杂、牵一发而动全身的腐败问题,还缺乏有效的解决方案。面对工程腐败,任重而道远,能做和需要做的事情还有很多。打击工程腐败,是解决问题的直接、切入点。共勉。关注【阿里移动技术】微信公众号,每周3个移动技术实践&干货等你来思考!