一、背景

高德 App 经过多年的发展,其代码量已达到数百万行级别,支撑了高德地图复杂的业务功能。但与此同时,随着团队的扩张和业务的复杂化,越来越碎片化的代码以及代码之间复杂的依赖关系带来诸多维护性问题,较为突出的问题包括:

不敢轻易修改或下线对外暴露的接口或组件,因为不知道有什么地方对自己有依赖、会受到影响,于是代码变得臃肿,包大小也变得越来越大;

模块在没有变动的情况下,发布到新版本的客户端时,需要全量回归测试整个功能,因为不知道所依赖的模块是否有变动;

难以判断 Native 从业务实现转变为底层支撑的趋势是否合理,治理是否有效;

这些问题已经达到了我们必须开始治理的程度了,而解决此类问题的关键在于需要了解代码间的依赖关系。

二、高德 APP 平台架构

为了消除一些疑惑,在讨论依赖分析的实现前,先简单说明一下高德 APP 的平台架构,以便对一些名词和场景有一些背景了解。

高德 APP 从语言平台上可以分为 4 个部分,JS 层主要负责业务逻辑和 UI 框架;中间有 C++层做高性能渲染(主要是地图渲染),同时实现了一些切面 API,这样可以在双端只维护一套逻辑了;Android 和 iOS 层主要作为适配层,做一些操作系统接口的对接和双端差异化的(尽可能)抹平。

这里的切面是指 JS 层与 Native/C++ 层的分界线,这里会实现一些切面 API,也就是 JS 层与 Native/C++ 层交互的一系列接口,如蓝牙接口、系统信息接口等,由 Native/C++ 层来实现接口,然后往 JS 层暴露,由 JS 层调用。

三、基础实现原理

整个项目最基本也是最重要的数据就是依赖关系。所谓依赖关系,最简单的例子就是文件 A 依赖文件 B 的某个方法。

要将这个关系查出来,一般来说需要经过两个步骤。

第一步:编译源码,获得 AST

遍历所有源码,通过语法分析,生成抽象语法树(Abstract Syntax Tree, AST)。以 JS 扫描器为例,我采用了 typeScript 模块作为编译器,它同时支持 JS(X)、TS(X),通过 ts.createSourceFile 来生成 AST。除 JS 外,iOS 采用的是 CLang,Android 采用的是字节码分析,C++ 采用的是符号表分析。

第二步:路径提取,依赖寻路

从 AST 上我们可以找到所有的引用和暴露表达式,以 JS 为例就是 import/ require 和 export/ module.exports。寻找表达式的方法就是递归地遍历所有语法节点,在 JS 中我采用了 TypeScript 编译器提供的 ts.forEachChild 来进行遍历,通过 ts.SyntaxKind 进行语法节点类型的识别。

找到表达式后,通过依赖路径找到具体的依赖文件。以 JS 为例,我们可以通过 const { identifierName } = require('@bundleName/fileName') 的方式引用其它模块(bundleName)的某个文件(fileName)的某些标识符(identifierName),我们就需要根据这表达式来定位到具体的标识符。

跨切面的依赖会需要多做一步,需要将切面 API 分为调用侧和声明侧,在 JS 层通过 AST 分析出调用侧数据,在 Native/C++ 层分析出声明侧数据(对应到具体实现切面 API 的标识符),将调用侧和声明侧数据通过版本号关联到一起,即可实现全依赖链路贯通。

我们把这个关系以及一些元数据保存下来,就可以作为源数据来作数据分析了。

四、项目架构

整体项目架构如下:

我们使用 Node.js 和集团的 egg.js 框架搭建了本依赖分析工程服务,并且考虑到数据使用场景的多变性和多样性,我选用了 GraphQL 作为查询接口,输出我们定义的数据类型,由上层应用自行封装,如果出现多个上层应用同时需要类似的数据,我们也会进行整合复用。

其中数据加工模块是独立模块,由 Node.js 编写,支持其它项目复用,未来会计划在 IDE 等项目复用。

左侧是我们的数据消费方,这里只列举了几个;右侧是我们的数据库,用于储存分析结果;下侧是四端扫描器和触发器,四端分别对自己平台的源码进行源数据生产,触发器支持发布流程触发事件触发、定时触发、前端触发(应用侧前端,不是 Web 前端)和人工触发等。

五、应用场景及实现原理

全链路依赖关系的使用场景有无穷的想象力,这里挑几个来举例。

影响范围判断(逆向依赖分析)

第一个我们能想到的应用场景就是影响范围判断,这也是我们这个项目的第一个抓手。大家都能想到,如果维护一个接口(或组件),我们会发现当越来越多地方用的时候,迭代它的风险会随之而越来越高,我们需要明确地知道到底有哪些地方调用了这个接口,以确定到底要回归测试多少功能、要怎么做发布、怎么做兼容等。而这就需要进行逆向依赖分析了。

逆向依赖是相对扫描器中分析出来的依赖关系的,扫描器分析出来的我们称之为正向依赖,它主要表示「此模块依赖了哪些别的模块」;而逆向依赖则指的是「此模块被哪些模块依赖了」。所以很自然地,我们的逆向依赖就是基于正向依赖关系做的数据加工。

(逆向依赖查询页面)

基于逆向依赖数据,结合多个版本的数据,我们还能算出「连续未被引用的版本数」,以衡量下线接口的安全性。

(一些切面 API 的连续未被使用的版本数)

组件库、框架和切面 API 的维护者是这个能力的重度用户,这个能力为他们带来了数据支撑,明确了自己的修改将会影响多少的其它模块,从而进行变更、发布决策和回归测试。

版本间变动分析

版本提测时,我们可以对两个版本进行依赖链比对,分析出文件的变动及其整个影响链路,为 QA 提供一些数据支持,能更精确地知道有哪些功能要进行回归测试,有哪些不需要。

版本间变动分析有很多场景,除了正常的版本迭代的场景之外,还有一个常见的场景:模块在未变动的情况下被集成到新版本的高德 APP 中,那就会出现「发布代码不变,而所依赖的其它模块有变动」的情况,尤其有是 Native/C++ 和公用模块。测试环境需要知道的是,当前模块所依赖的其它模块到底有哪些变动、这些变动对此模块的影响是什么、需要回归测试哪些功能点等。

这个数据的主要消费方是 QA 同学,他们利用这个数据可以提高测试效率,也能发现漏考虑的回归点。

趋势变化判断

前面也提到过,由于高德 APP 时间跨度很大,以及之前未进行限制,所以我们有部分业务逻辑代码仍然是通过 Native 来实现的,我们希望逐渐迁移到 JS 或 C++ 层实现,Native 仅作适配。

而要判断这个治理的进度和效果,需要从两个方面的数据来支撑,一是各平台代码行数,这个我们另有专门的服务做,暂且不提;二是接口趋势。接口趋势也分为调用侧和声明侧两种,按照我们治理的方向,我们期望的效果应该是:一条 Native 业务切面 API 的调用量按版本/时间不断减少的曲线,当一些 API 的调用量为 0 后就可以把 API 下线掉,这样就会随之出现另一条曲线——Native 业务切面 API 的声明量也不断减少。

(从某版本开始就不断减少调用的切面 API)

(某版本未被使用的切面 API)

进行架构治理、切面 API 治理的同学是这些数据的主要消费方,有了这些数据他们就能确定架构治理的趋势是否合理、是否能下线某切面 API 等。

包大小优化——无用、重复文件查找

我们也为包大小优化作了贡献。根据依赖关系数据,我们可以找出一些没有被引用或者内容完全一样(md5 值相同)的文件,这些文件也占用了不少体积。

我们利用依赖分析工程找出了上千张这样的图片,@1x @2x @3x 文件是重灾区,有很多假装自己是另一个清晰度的图片被我们揪出来了(我们甚至因此推动了设计师出图标准化和增加了检验工具)。

六、写在最后

以上便是高德全链路依赖分析工程的基本概述,在具体的实现当中,会有无数的细节需要处理,如各种历史遗留问题、多级版本处理产生指数级的代码快照、变动分析产生指数级的分析结果等,其中也涉及到不少编译原理、数据结构与算法(尤其是图结构)等知识,非常考验编程能力和权衡能力,以及最重要的——韧性。欢迎大家一起讨论,一起迸发新的想法、新的场景!

高德APP全链路源码依赖分析工程的更多相关文章

  1. 【lwip】09-IPv4协议&超全源码实现分析

    目录 前言 9.1 IP协议简述 9.2 IP地址分类 9.2.1 私有地址 9.2.2 受限广播地址 9.2.3 直接广播地址 9.2.4 多播地址 9.2.5 环回地址 9.2.6 本地链路地址 ...

  2. 不会DRF?源码都分析透了确定不来看?

    目录 不会DRF?源码都分析透了确定不来看? 快速使用DRF写出接口 序列化和反序列化 drf快速使用 views.py serializer.py urls.py 在settings的app中注册 ...

  3. Spring Ioc源码分析系列--Ioc源码入口分析

    Spring Ioc源码分析系列--Ioc源码入口分析 本系列文章代码基于Spring Framework 5.2.x 前言 上一篇文章Spring Ioc源码分析系列--Ioc的基础知识准备介绍了I ...

  4. FFmpeg的HEVC解码器源码简单分析:解析器(Parser)部分

    ===================================================== HEVC源码分析文章列表: [解码 -libavcodec HEVC 解码器] FFmpeg ...

  5. DispatcherServlet源码注解分析

    DispatcherServlet的介绍与工作流程 DispatcherServlet是SpringMVC的前端分发控制器,用于处理客户端请求,然后交给对应的handler进行处理,返回对应的模型和视 ...

  6. 【Orleans开胃菜系列2】连接Connect源码简易分析

    [Orleans开胃菜系列2]连接Connect源码简易分析 /** * prism.js Github theme based on GitHub's theme. * @author Sam Cl ...

  7. RxJava && Agera 从源码简要分析基本调用流程(2)

    版权声明:本文由晋中望原创文章,转载请注明出处: 文章原文链接:https://www.qcloud.com/community/article/124 来源:腾云阁 https://www.qclo ...

  8. JUC同步器框架AbstractQueuedSynchronizer源码图文分析

    JUC同步器框架AbstractQueuedSynchronizer源码图文分析 前提 Doug Lea大神在编写JUC(java.util.concurrent)包的时候引入了java.util.c ...

  9. 多flavor导致的源码依赖问题-- Cannot choose between the following configurations of project :sample:

    一.背景: 当我们在源码依赖的时候经常会导致一些问题. 我们的主工程有如下配置,它依赖了一个sample的本地工程 flavorDimensions "demo" productF ...

随机推荐

  1. [知识图谱]利用py2neo从Neo4j数据库获取数据

    # -*- coding: utf-8 -*- from py2neo import Graph import json import re class Neo4jToJson(object): &q ...

  2. 「考试」$5T$

    啊因为最近题实在是好啊,只能四五篇四五篇写了. T1. 括号序列的确简单. 当我们维护左右$cnt$后. 到一个左括号的地方的话. 答案就是:$$\sum\limits_{i=1}^{min(lc,r ...

  3. ELK分布式日志+NLog在.NetCore中的应用

    一.ELK简介 ELK是Elasticsearch.Logstash和Kibana首字母的缩写.这三者均是开源软件,这三套开源工具组合起来形成了一套强大的集中式日志管理平台 Elasticsearch ...

  4. 最适合Java开发者的一本书和一软件

    一书-<Java编程思想> 一软件-IntelliJ IDEA Java自学是否可以成功,答案显而易见,可以. 自学Java关键看自己是否有毅力.是否有恒心. 自学Java 自学Java不 ...

  5. Docker 资源 | 官方文件

    Docker资源 Docker官方英文资源: docker官网:http://www.docker.com Docker windows入门:https://docs.docker.com/windo ...

  6. 护网杯web

    首先进入网页后,观察到有sign up 点击sign up 进行注册 再点击sign in 进行登录 进入一个买辣条的界面,可以知道,5元可以买一包大辣条,多包大辣条可以换一包辣条之王,多包辣条之王可 ...

  7. Hibernate的多对多关系

    1.表的关系: 分别有三个表:课程表.学生表.分数表.课程和学生的关系是多对多的,因为一个学生对应多个课程,而一个课程被多个学生选修.如果用一对多.多对一的观点来看待课程和学生的关系显然是不对的,因为 ...

  8. map集合中哪些是线程安全的

    为什么HashMap是线程不安全的 总说 HashMap 是线程不安全的,不安全的,不安全的,那么到底为什么它是线程不安全的呢?要回答这个问题就要先来简单了解一下 HashMap 源码中的使用的存储结 ...

  9. C++中对C的扩展学习新增内容———面向对象(继承)函数扩展性及虚函数机制

    1.c语言中的多态,动态绑定和静态绑定 void do_speak(void(*speak)()) { speak(); } void pig_speak() { cout << &quo ...

  10. nyoj 19-擅长排列的小明(STL-next_permutation())

    19-擅长排列的小明 内存限制:64MB 时间限制:1000ms Special Judge: No accepted:10 submit:16 题目描述: 小明十分聪明,而且十分擅长排列计算.比如给 ...