Incendio en la corrupción de ingeniería | Solución completa a las ideas de gobernanza

Autor: Liu Tianyu (Qianfeng)

Una serie de artículos que revisan " Proyecto de disparos contra la corrupción | Gobernanza de Proguard ", " Proyecto de disparos contra la corrupción | Manifiesto de gobierno ", " Proyecto de disparos contra la corrupción: Gobernanza del código Java ", " Proyecto de disparos contra la corrupción | Gestión de recursos ", " Proyecto de disparos contra la corrupción | Dynamic Link Library entonces "Gobernanza ". Este artículo es el último de una serie de artículos, que se enfoca en las ideas generales de gobernanza, el diseño del programa y el pensamiento y las compensaciones detrás de esto.

La calidad de ingeniería es la base para que cualquier producto realice iteraciones de funciones comerciales de manera rápida, eficiente y estable. También es un factor que no se puede ignorar para brindar a los usuarios una buena experiencia con el producto. También es la expectativa y la búsqueda de la excelencia de cualquier ingeniero destacado. Sin embargo, la corrupción del proyecto es un problema al que debe enfrentarse cualquier proyecto de gran envergadura, es extenso y fragmentado, escondido en "rincones" que no se detectan fácilmente y afecta a todos los aspectos del proyecto.

La corrupción del proyecto está acompañada por el proyecto mismo. Se ejecuta a través de cada etapa del ciclo de vida del proyecto. Los cambios en el tiempo, las personas, el código, el proceso, las reglas y cualquier factor conducirán a la corrupción, desde la detección hasta la reparación, el análisis sistemático y el plan de respuesta. formulación, Luego, para aceptar con calma y normalizar la gobernanza sostenible, este artículo los discutirá uno por uno.

Origen

Antes de que un proyecto madure, el problema de corrupción está profundamente oculto en el código, lo que generalmente reduce significativamente la eficiencia de I + D, pero los problemas en línea causados ​​​​no son frecuentes, por lo que es fácil solucionarlo como un problema de un solo punto. Sin embargo, a medida que el grado de corrupción se intensifica, la frecuencia del mismo tipo de problemas es cada vez mayor, y el leve "olor a corrupción" se huele gradualmente. Por lo tanto, una serie de análisis de seguimiento, diseño de programas, herramientas y desarrollo de plataformas, y se han desarrollado prácticas de gobernanza. Echemos un vistazo a la imagen a continuación, y muchos estudiantes de I + D pueden tener sentimientos personales:

1.1 Huele a corrupción

El autor tiene muchos años de experiencia en el campo de la arquitectura de Android, y es directamente responsable o indirectamente involucrado en la estabilidad, el rendimiento de inicio, la reducción del tamaño del paquete, el rendimiento de la ingeniería y la adaptación de nuevas versiones Con la profundización continua de varios elementos de gobierno y la aprobación de tiempo, me he encontrado con varios problemas, tales como: los recursos en conflicto generan inconsistencias en los valores de los recursos en el apk después de múltiples compilaciones, incluso si el código no cambia, lo que eventualmente genera problemas en línea; la modificación del código Java genera llamadas incompatibles , que eventualmente conduce a una excepción de Java en línea; los subprocesos se usan a voluntad, falta de control unificado, por un lado, el rendimiento es preocupante, por otro lado, la cantidad de subprocesos excede el límite personalizado de algunos dispositivos, lo que causa OOM excepción; el código inútil, los recursos y los módulos de función conducen al tamaño del paquete Continúa aumentando; la construcción de apk lleva más y más tiempo, lo que afecta seriamente la eficiencia de la investigación y el desarrollo; se pueden citar docenas de ejemplos de este tipo, y no se repetirán aquí.

Al tratar de mirar y pensar sobre estos temas desde una perspectiva holística, solo para descubrir el poderoso enemigo escondido detrás de ellos: la corrupción de ingeniería. La corrupción de ingeniería, en términos simples, es la acumulación continua de código inútil/redundante/irracional, que es más propenso a problemas y más difícil de localizar, y cuanto más rápida es la iteración, más rápida es la corrupción Incluso sin ninguna iteración, con la nueva versión de os Los cambios en el entorno externo, como la cotización, la supervisión cada vez más estricta del cumplimiento de la privacidad, etc., causarán problemas en el código de stock. A continuación, sumérjase en el proceso de iteración de I+D para ver de dónde proviene la corrupción.

1.2 Análisis de la producción de corrupción

Como se mencionó anteriormente, hay muchos factores que pueden conducir a la corrupción de ingeniería, pero solo hay dos factores de origen principales: el tiempo y las personas. El tiempo significa cambios en el entorno externo del proyecto. Por ejemplo, el número de versión del sistema operativo en el dispositivo de destino se actualizará continuamente, la cadena de herramientas de I+D, el IDE, etc. se actualizarán iterativamente. Un código de proyecto estático se corroerá lentamente con el tiempo. . En comparación con el efecto de degeneración lenta del tiempo en la corrupción de ingeniería, las iteraciones de ingeniería rápidas dirigidas por personas son la mayor fuente de corrupción de ingeniería rápida. En este caso, concentrémonos en observar los roles involucrados en el proceso de iteración y entrega de una versión de la aplicación, cuáles son sus demandas principales y cómo se acumula la corrupción del proyecto en ese "suelo".

La imagen de arriba es un proceso típico de iteración y entrega de la versión de la aplicación móvil. Para aplicaciones grandes y equipos de I + D, cada rol puede tener una posición dedicada y una persona responsable, mientras que para aplicaciones pequeñas y equipos de I + D, puede dividirse en una sola persona. Múltiples roles:

  • Producto y diseño, responsable de la función, la interfaz de usuario, el diseño de interacción, preocupado por el valor que aporta la creatividad y la función a los usuarios, así como la interacción y la visualización fluida y fresca;
  • 研发和测试,在接到产品需求以及设计稿后,负责代码开发、实现、效果&质量保障,研发和测试同学,往往希望需求和设计一旦确定后不要总发生变化,此外还希望尽可能复用现有的逻辑和功能,对不断推倒重做式的需求和设计有着天然的“抗拒”,最后还希望能多点时间,再多点时间,来保障代码质量和验收效果;
  • PMO和PTM负责版本节奏、管控发布过程,关心整体的需求吞吐量,以及过程和线上质量;
  • 渠道和运营负责将新版本app,通过各种渠道准时交付到用户手中,并通过层出不穷的运营手段,来获取新用户以及用户对app功能使用的全面、快速增长;
  • 在前面这个过程中,安全和法务需要保障app的安全漏洞得到及时解决,隐私合规等相关事项不出现风险性问题。

最终,用户获取或者升级到最新版本app,其核心诉求是这个新版本app“好用吗?好玩吗?”。随之而来的除了用户,还有各方监管&检测机构,在获取到新版本app后,会检查根据当前法律、法规,仔细检查app使用过程中是否存在“违规”现象。

在这样一个app版本交付过程中,可以看到各角色的侧重点并不相同,同时所有角色的诉求最终都要通过代码来承载。工程腐化直接来源于开发者的代码生产活动,开发者本身的意愿、技能和经验,确实会极大影响代码质量,但现代企业级app的功能之复杂,绝不可能所有参与其中的开发者,都能够对app所有代码了如指掌,因此这种对工程或者说代码掌握的局部性,可能是工程腐化产生的更重要因素。

1.3 拆解腐化问题

分析完腐化产生,我们再进一步对Android工程腐化项,进行更细粒度的拆解。从Android工程包含所有“代码”的类型来看,可以分为以下五种:

其中,工程配置是指在apk构建过程中使用到的相关配置,配置内容本身并不会进入到最终apk,这种工程配置腐化,主要是影响工程本身的复杂度,甚至是构建过程耗时,例如大量的proguard配置项。其它四种类型,manifest、java代码、资源、动态链接库so,也是组成apk的所有可能“元素”,自身或者相互之间都可能存在各种各样的腐化问题,直接导致apk稳定性、性能、包大小、UI&功能异常、隐私合规风险等等,或者提高这些问题出现的可能性。

在实际工具开发和治理实践中,也正是按照上述类型实现分而治之。

应对方案

在完成腐化产生分析,以及按类型拆解后,接下来需要制定有效的应对方案。

首先,必须明确并时刻牢记的指导原则是:“用正确的方式,做正确的事,无论简单还是困难”。“正确的事”往往比较容易界定,并达成共识,但是“用正确的方式”却有些困难,因为有时候“不正确的方式”意味着捷径,可以快速取得目标成果,例如:假设我们需要将app中所有线程使用切换到统一线程池实现,有两种方式可以完成,一种是直接使用构建时aop技术对线程调用代码直接进行替换,另一种是建立非统一线程池使用的检测&卡口机制,在保障有效防控增量代码情况下,逐步修改存量代码。显然,第一种方式可以快速达成目标,但是却会增加apk构建耗时,同时如果这个aop处理过程本身,一旦出现问题导致替换不成功,或者替换过程异常终止导致字节码替换不完整,那么又是另一种“工程腐化”。第二种方式无法快速达成目标,但是可以有效止住腐化趋势,并逐步消化存量问题,虽然卡口本身需要日常审批评估,并且存量代码清理也并非一蹴而就,但代码源头上的直接改正,才是解决工程腐化问题的”正确方式“。

2.1 人vs流程

工程腐化来自于人在版本迭代流程中,对工程代码进行的不合理变更,因此,工程腐化治理需要围绕“人”和“流程”来进行。

对于人这个因素,业界已经有非常成熟有效的做法,例如:进行代码review、制定代码规范、定制IDE的Lint规则、持续进行技术培训等,这些都能够提高开发者的代码设计和编码水平,从而在源头减少腐化代码产生。此外,能够潜移默化的提高研发团队整体工程质量和素养,对工程质量带来更为全面的提升。但是,这种方式有一些问题,也绝不能忽视:参与到一个工程的开发者,其技术认知、水平、理解能力并不一致,这些规范/规则的执行效果难以保障,带来的潜在成本可能也会很高。

对于工程腐化来讲,完全依靠这些围绕人的方案,不确定性非常高,而腐化的防治需要一种确定性的机制来“守好这道门”,同时,防治本身需要做到较低的成本,因此,我们将重点放在流程上面。流程具有客观、固定、有保障的特性,一方面以全面的apk检测分析技术为核心,对腐化项精准定位并在流程关键节点部署卡口,及时感知,有问题就地处理,从而实现零新增。另一方面,对于存量腐化项,提供多样化的辅助工具,降低整改风险和成本,提高效率。冰冻三尺,非一日之寒,因此解冻的过程,也不能够搞成大跃进式的清理模式,而是需要在尽量不影响日常研发活动前提下逐步迭代,最终实现存量清零。

围绕人和流程的这些应对方案,并不是二选一而应该是相辅相成,前者重在从源头全面减少腐化项产生,后者重在无差别的阻止其中能够有效检测的腐化项进入到最终apk,同时增强开发者防腐化意识,并促进代码Review、代码规范等有效执行,从而形成良性循环。

2.2 分析工具

作为核心的apk检测分析技术,到底包含哪些具体的能力呢?来看下面这张图:

上图是当前检测分析技术汇总,可以分为冗余冲突、关键配置、引用关系、辅助提效四个类型。前三种类型直接对应具体的腐化项,最后一种则是帮助开发者在日常研发过程中,更好的定位和分析问题。对于每一项检测能力,此处先不详述,在“向工程腐化开炮”系列文章中,分别与具体实践相结合进行了相关讲解。

2.3 卡口体系

这些检测能力,是如何与流程相结合的呢,来看下面这个流程卡口示意图:

对于开发/测试同学,在提测、集成、灰度/正式版本发布这些关键节点,都需要进行apk构建,同时,会自动触发已经部署好的各项检测分析。如果是本地打包,检测不通过,会直接构建失败,并在失败原因中,给出相关信息;如果是CI/CD平台打包,卡口结果会以平台页面形式呈现;无论哪种模式,都会中断流程,待研发同学修复问题后,再继续进行。这样,就实现了腐化问题的及时感知,就地修改。

以平台模式为例,每次提交测试/集成时,apk构建都会触发卡口检测,如果有卡口项未通过则阻断流程。卡口结果示例如下:

在具备了这样一套机能力和机制后,我们接下来看看,如何对各类腐化问题进行治理和防控。首先,先明确“模块”这个概念,对工程腐化与治理的影响,以及工具建设和治理实践。

模块治理

一个完整apk的产生,可以认为是一个“拼积木”的过程;每一块积木,都可能包含java代码/资源、Android资源、AndroidManifest文件、动态链接库so、proguard配置,将这些积木按照一定规则拼接,同类元素混合&压缩,即成为最终的apk文件。上述这些“积木”,用更贴近技术的术语来讲,就是模块。模块为功能复用提供可能,也为并行研发模式提供基础,一般来讲,越大型和复杂的工程,其模块化程度也越高。

工程腐化的产生,本质是由功能的复杂度以及代码变更导致,模块化本身虽然会带来一定的腐化问题,但更重要的是,为工程腐化问题治理提供便利。试想一下,一个由上百人划分为十多个团队,共同参与迭代的app,如果都在一个app工程中开发代码,先不说如何解决代码协作,一旦发生腐化问题,如何进行分配本身就是一个极大的挑战。在现实工程领域,模块化程度一般(正常的工程选择)都会随着功能和开发人员的增加而不断提高,在这个前提下,工程腐化治理首先要做的事情,就是要明确知道每一个具体的腐化问题,来自哪几个模块,这是将问题进行分发和处理的前提。接下来,首先会给出模块的分类,然后讲述针对模块开发的几个“辅助分析能力”,以及在此之上的治理实践。

3.1 模块分类

app工程中以外部依赖形式引入的jar/aar,以及与app工程平行的subproject,可能是日常研发过程中接触最多的模块类型,除此之外,Andriod原生还支持其它类型模块。从apk构建视角来看,模块的完整分类图如下:

上图展示了5种模块类型,以及几个维度:在apk构建过程中是否需要经历源码编译、是否在maven仓库中存在,以及可能存在的依赖关系。下面分别进行讲解:

  • app-project有且仅有1个,用于生成apk,包含源代码,因此需要源码编译。可以依赖sub-project、local jar、flat aar、external module;
  • sub-project可以有0或多个,一般与app-project平行,同样包含源代码,可以依赖sub-project、local jar、external module;
  • local jar不能单独存在,java代码已经以编译后的class字节码形式存在,不能依赖其它类型模块;
  • flat aar是Android原生提供的一种引入非maven中aar的方式,同样无需源码编译,并且不能依赖其它类型模块;
  • external module,即外部依赖模块,无需源码编译,可以依赖其它外部模块,依赖信息位于maven仓库对应pom文件中。

一般来讲,一个app的“出生”,是从一个app-project工程开始的:所有代码、资源都写在此工程中,当然也会以外部模块形式引入(依赖)一些二、三方库;随着app承载功能增加,复杂度随之上升,此时也很可能会有更多的开发者加入进来,持续迭代一段时间后,可能会迎来第一次模块化“变革”:将通用功能拆分为多个sub-project;开发人员的增多,会引发代码协作成本提高,此时可能需要从单个代码仓库拆分为多个,便于并行化开发,此时迎来第二次模块化“变革”:代码仓库拆分,以及更细粒度的模块拆分,研发并行程度继续提高。最终,会演进为模块化的究极形态:app-project成为用于打包apk的一个“壳子”,几乎所有代码全部拆分到单独模块和仓库,在app-project中以外部模块形式对其进行依赖(引入),研发高度并行化。

很多大型app,基本都完成了上述这样的演进过程,同时也引发了新的问题。接下来,就来逐一讲述在模块这个维度,研发了哪些工具,进行了哪些治理。

3.2 辅助分析能力

辅助分析能力,主要是站在apk完整构建角度,为开发同学提供模块及其依赖信息,用于解决各种日常问题,例如:

  • “我更新了一个模块的版本号,为什么apk中的代码还是旧的?” —— 查看本次apk构建,目标模块最终使用的版本号是多少,如果没有更新,那么肯定会出现这个问题。
  • “我删除了模块,为什么apk中还有相关代码/资源?” —— 查看本次apk构建,目标模块是否参与到apk构建过程,是app工程直接依赖引入,还是其它模块间接依赖引入,快速定位原因。
  • “我在一个模块工程中,使用了另一个模块中的方法,但是在apk中却找不到此方法,是什么原因?” —— 查看本次apk构建,依赖的另一个模块版本号是多少,升级目标工程中对此模块依赖的版本号,重新编译目标工程,看是否方法已被删除,转移或者签名有变化。

接下来,分别对每项辅助分析能力进行简单介绍。

外部依赖模块列表

外部依赖模块列表,统一输出所有参与到本次apk构建的外部依赖模块,及其版本号、类型。示例结果:

com.youku.arch:testlib:0.1-SNAPSHOT@aar
com.youku.arch:testlib2:0.3@aar
复制代码

被依赖关系检测

在apk构建过程中,有一些外部依赖模块是通过间接依赖(没有在app工程中直接声明依赖)引入进来的,这个间接依赖关系,存在于maven仓库中模块对应的POM文件。通过被依赖关系检测功能,可以方便的找到一个模块,被哪些其它模块所直接依赖,用于进行模块下线,或者归属关系判定(根据依赖关系,判断模块属于哪个上层业务)。示例分析结果:

com.youku.android:y-core
|-- [provided] com.youku.android:ct-ad
|-- [compile] com.youku.android:catl
|-- [runtime] com.youku.android:MtRec

com.tb.android:z_dev
|-- [compile] com.tb.android:zcore
复制代码

注意,这里的分析结果,是被依赖关系。在这个例子中,com.youku.android:ct-ad模块以provided方式,声明了依赖com.youku.android:y-core模块;com.youku.android:catl模块以compile方式,声明了依赖com.youku.android:y-core模块;其它内容以此类推。其中,依赖类型一般包括以下几种:

  • compile。此类型依赖,如果不额外添加exclude设置,会导致模块被打入apk;
  • provided。此类型依赖,不会导致模块被打入apk;
  • runtime。此类型依赖,不会导致模块被打入apk。

当然,模块在发布到maven仓库时,可以定制pom文件内容,所以如果模块发布时,并未正确的将工程中对其它模块的依赖关系写入到pom中,那么上述检测结果,也会存在对应的错误信息,例如:漏掉真实依赖模块、依赖类型与实际不符、包含多余依赖模块等。

不匹配依赖关系检测

在模块化开发模式下,各个模块独立开发,并最终参与apk构建,这会导致很难感知到其依赖的模块进行了升级:模块自己在进行构建时,使用的还是对应依赖模块的旧版本,所以可以编译通过,但是在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带来不确定性隐患,因此,也成为模块腐化的直接治理目标。

snapshot版本号

在apk构建开始阶段,直接从maven仓库下载外部依赖模块对应版本号的jar/aar文件,参与后续构建过程。其中,SNAPSHOT版本号由于可以随时更新jar/aar到maven仓库,而在app发布版本构建时,并不希望这种情况发生,这会带来各种难以预期的线上风险。因此apk构建过程,是否存在SNAPSHOT版本号的外部依赖模块,需要被严格管控住。

为了,研发了snapshot版本号检测功能,筛选出参与到apk构建过程所有版本号为snapshot的外部模块。示例内容如下:

com.youku.arch:testlib:0.1-SNAPSHOT
com.youku.arch:testlib2:0.2-SNAPSHOT
复制代码

进一步,在app版本迭代关键节点,例如:集成、灰度/正式版本发布,利用此项检测能力形成卡口。优酷在几年前,就已经以本地卡口形式(apk构建失败)上线此功能,并在2021年将此卡口融入到整个卡口体系,成为其中一个卡口项,累计拦截7次,有效防止snapshot版本模块引入到apk构建过程中。

snapshot依赖

开发阶段,为了方便模块间联合调试,通常会将依赖的模块版本修改为SNAPSHOT,在完成联合调试后的正式版本打包过程中,如果没有将依赖模块的SNAPSHOT版本号修改回正式版本,而这个时间窗口内,依赖模块的SNAPSHOT版本一旦有更新,会导致模块正式版本编译时依赖非预期代码,最终导致apk运行时出现各种不兼容问题,例如:API不兼容(类、变量、方法签名不匹配)、常量不一致(常量在模块编译时,会进行常量展开)。

snapshot依赖检测功能,正是为此而生,在检测结果中列出每个模块依赖的snapshot版本号模块,以及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.2

com.youku.android:OUtil:1.0.4.11
|-- com.youku.android:OService:20210105-SNAPSHOT ==> 1.3.8.2
复制代码

作为腐化治理项,优酷在2021年初上线此功能,当时有200多个模块在pom文件中存在snapshot模块依赖,当时统一添加到了白名单,在接下来版本迭代过程中逐步清理,截止目前已清理近40%,效果显著。在同一时间于app版本迭代关键节点,形成了对应流程卡口,近一年时间累计拦截25次,有效防止由此导致的线上风险问题发生。

其它治理实践

上述模块相关腐化治理,只是与工程腐化这场持久战的前哨。针对前面工程腐化的元素级分类拆解,开辟了以下“五大战场”,可以前往查看详情(点击跳转):

  • proguard配置
  • manifest
  • java代码
  • 资源
  • 动态链接库so

还能做些什么

在优酷近两年的工程腐化实践中,得到了很多研发同学的支持,他们怀抱匠心、热情与勇气,及时解决出现的新增问题,一点一点的去消化存量技术债,长期的坚持和努力共同换来目前工程腐化问题的全面显著降低。“用正确的方式,做正确的事,无论简单还是困难”,这既是优酷进行工程腐化解决方案设计和治理实践时,所坚定遵循的原则,也是本系列文章想要传达出来的技术理念。

目前能够通过工具检测到的具体腐化问题,加起来不过20余项,相对于工程腐化的冰山,毫不夸张的说这真的只是一角儿。况且,这里所给出的应对方案,也仅仅能够解决其中一类问题,面对那些极度复杂,甚至牵一发而动全身的腐化问题,尚缺少有效解决方案。面对工程腐化,还有很长的路要走,还有很多事情可以并且需要去做,向工程腐化开炮,是一种直接而切中要害去解决问题的态度,积跬步行千里,与诸君共勉。

关注【阿里巴巴移动技术】微信公众号,每周 3 篇移动技术实践&干货给你思考!

Supongo que te gusta

Origin juejin.im/post/7080411994020380685
Recomendado
Clasificación