Blog
Blog
PHODAL

IDEA 是我日常使用最多的开发工具之一,大概仅次于 Firefox (Google 和 StackOverflow 是生产力)和 iTerm 2。因为研究编辑器和 IDE 的关系,我研究了一段时间的 IDEA 插件的相关机制。其中作为研究的主要对象是 Android Studio 对于 Android APK 的构建部分。

日常的搬砖过程中,我们总会因为代码上的一些设计问题,进行争论。而最后结果呢,可能就是『show me you code』,又或者是『你行你来』。

最近,因为公司项目的原因,对一个大型的系统做了一个简要的架构分析。由于,时间上的限制,所以在这里我也只能做一个快速的分析,并没有其它的可能性。 太长不看版步骤: 1. clone 项目的代码,以及相关的依赖 2. 尝试编译系统 3. 借助目录 + 编辑器进行初步分析 4. 借助工具进行可视化分析 5. 配置 IDE,进行源码分析 6. 绘制架构图 7. 从用户旅程验证架构正确性 8. 总结输出 9. 回溯版本,进一步验证 PS:这里所针对的情况是,没有现有架构图的情况。如果已经有现成的架构,那么它的步骤应该是不一样的。依我之间的经验来看,它应该是这样的: 1. 寻找架构图 2. 寻找相关的阅读代码文档、日记 3. 其它同上 ## 0. clone 多数情况下,把远程的代码 clone 到本地,是一件非常简单的事情。但是,并非所有的情况都是如此,因为对一个大型的系统来说,我们要面对着这么一些情况: 1. 代码库过多 2. 代码量过大 于是,在我所需要分析的这个系统里,它采用了 Google 的多仓库管理工具 Repo。这样就从一定程度上解决代码库过多的问题——对于我们来说,我们只需要执行一个 `repo sync`,它就可以帮助我们把所有的代码 clone 下来。而后,我们只需要等待几小时,或者几天,就可以下到我们的代码库了。 ## 1. 尝试编译系统 有了代码之后,我们就可以尝试按文档的步骤来构建应用。期间,我们还需要解决一些工具上的问题,又或者是按官方的 issue 来处理一些异常情况。 与此同时,你还可能会遇到我在这个项目上遇到的问题:当前版本是无法成功构建的。 于是,我还需要重新花一天时间,再找到某一个特定版本的代码……。 ## 2. 借助目录 + 编辑器进行初步分析 与此同时,在我们进行编译的时候,还可以同时简单地对项目进行分析: 1. 目录结构分析。通过查看目录名称和目录结构,分析项目的组成关系。 2. 代码简单分析。嗯,从一个入口点,一步步查看调用关系等。 之所以,我们还不能用 IDE 进行分析的一个原因是:对于这样的一个系统来说,IDE 是一个庞大的吃内存怪物。而在当前时刻,我们还在尝试构建这个系统,它不仅吃内存,还吃 CPU。甚至于,你的电脑还会因此而卡住。 ## 3. 工具可视化 进一步地考虑到了项目的代码量的问题,简单地靠人力分析起来比较困难。我们就需要借助于一些工具来对代码进行分析。 由于这是一个 Java 项目,我就可以用我之前写的系统分析工具:[Coca](https://github.com/phodal/coca)。用它来绘制基本的架构图: ![Package Arch Demo](https://coca.migration.ink/showcases/android-gradle-elements.svg) 还有某一个方法或者是类的上下调用关系: ![call](https://coca.migration.ink/showcases/android-studio-call.svg) ## 4. 配置 IDE,进行源码分析 在腾出了足够的 CPU + 内存资源之后,我们就可以轻松愉快地打开 IDE,进行源码分析。于是,很快地,我就需要等待 IDE 把代码索引完。 好了,IDE 卡住了。 ### 模块分析 接着,我尝试了另外一种可能性,打开其中的某一个工程查看源码,但是很快地我发现了:**缺少依赖**。因为总体的构建失败,导致了总工程的一些依赖无法构建成功。 于是乎,我尝试了另外一种可能性:提取生产环境的依赖。毕竟,我所需要的依赖是一些 jar 包,而 jar 包会伴随着系统一起分发。这样一来,我就能从发布包中复制依赖到工程中使用,然后愉快地继续阅读代码了 —— 顺便地也能从依赖分析项目的情况。 ### 工程内依赖分析 嗯,对于某些模块来说,它的产出是一个 jar 包,那么我们不一定需要阅读它地源码。只需要理清单个模块的构建产物,以及它的作用即可。 ## 5. 绘制架构图 嗯,有了上面的基础之后,我们就可以绘制架构图了。 暂时没啥好的工具推荐,Google Slides、Sketch 这一类的都可以。 如果是调用关系的话,可以用 Graphviz 来绘制。只是呢,我已经用 Coca 来自动化绘制这个依赖关系了。哈哈 ## 6. 用户旅程验证 我们阅读代码时,都是从入口开始验证。如基于 Spring 的微服务项目,都是从 API 注解作为入口点,一步步分析这个系统的架构;如 Angular 开发的前端应用,是从 `main.ts` 开始的。如 IDEA 插件,是从 plugin.xml 开始的,从 Action 绑定用户行为。 以类似的方式,我们就可以在不能调试的情况下,进一步验证架构的提炼是否合理。 ## 7. 回溯版本,重复 考虑到我使用的版本是不能成功编译地版本,所以又花了点时间再下一个旧版本的系统,以验证部分关系是否是正确的。 毕竟只有成功编译地版本,才是正常的版本。 ## 8. 总结输出 这些相关的产物可以有: 1. 过程日志 2. 问题总结 3. 架构图 4. 仿制的 MVP demo 在这里,我们还是强调一下最后一个,我经常拿这种方式来创造轮子。
因为公司相关售前的原因,需要研究一下 Android Studio 的架构及其原理。便需要 clone 一下代码, 编译一下它,并研究它是如何编译的。 总体过程还是非常不顺利的,以至于最后没有在 4.0 上编译成功,只在 3.0 上编译成功。 ## 1. Clone 代码 这一步就比较简单了,按官方的教程来:《[Checkout and build the source code](https://android.googlesource.com/platform/tools/idea/+/refs/heads/studio-master-dev/RELEASE.md)》 ``` $ mkdir studio-master-dev $ cd studio-master-dev $ repo init -u https://android.googlesource.com/platform/manifest -b studio-master-dev $ repo sync -c -j4 -q ``` 不得不说 Android Studio 相关的代码还是蛮大的,大概花了一天的时间 clone。clone 完后,大概占了我 60 G 的存储空间。 ## FAQ ### 编译 在《[Android Studio Release Process](https://android.googlesource.com/platform/tools/idea/+/refs/heads/studio-master-dev/RELEASE.md)》 中记录了如何解决部分 Google 相关的 Vendor 缺少的解决方案 从代码库中删除所有的:tools/vendor/google 示例:https://android.googlesource.com/platform/tools/idea/+/refs/heads/studio-master-dev/RELEASE.md ``` --- a/bazel/toplevel.WORKSPACE +++ b/bazel/toplevel.WORKSPACE @@ -1,13 +1,6 @@ load("//tools/base/bazel:repositories.bzl", "setup_external_repositories") setup_external_repositories() -local_repository( - name = "blaze", - path = "tools/vendor/google3/blaze", -) -load("@blaze//:binds.bzl", "blaze_binds") -blaze_binds() - http_archive( name = "bazel_toolchains", urls = [ ``` ### SDK 或者 NDK 找不到 ``` ln -s ~/Library/Android/sdk/ /Users/fdhuang/jvm/studio-master-dev/prebuilts/studio/sdk/darwin ln -s ~/sdk/android-ndk-r20/ /Users/fdhuang/jvm/studio-master-dev/prebuilts/studio/sdk/darwin/ndk-bundle ``` ### armeabi 问题 ``` [exec] ERROR: /private/var/tmp/_bazel_fdhuang/0711c902f818b6dd779b715988db0de0/external/androidndk/BUILD.bazel:41:1: in cc_toolchain_suite rule @androidndk//:toolchain-libcpp: cc_toolchain_suite '@androidndk//:toolchain-libcpp' does not contain a toolchain for cpu 'armeabi' ``` 打开,复制粘贴 ``` 'armeabi': ':arm-linux-androideabi-clang8.0.7-v7a-libcpp', ``` ### 删除 DS_Store ``` ref_id = fd.readline() File "/usr/local/Cellar/python/3.7.5/Frameworks/Python.framework/Versions/3.7/lib/python3.7/codecs.py", line 322, in decode (result, consumed) = self._buffer_decode(data, self.errors, final) UnicodeDecodeError: 'utf-8' codec can't decode byte 0x80 in position 3131: invalid start byte ``` find . -name ".DS_Store" -delete ## 编译 4.0 失败 不过,最后你还是不会成功的,因为缺少了 NDK 相关的东西: 见 issues: [https://issuetracker.google.com/issues/126764883](https://issuetracker.google.com/issues/126764883)

距离我一次写测试相关话题的文章,已经有相当长的一段时间了。对于自动化测试相关的内容,我大抵还算是熟悉的。毕竟,开发人员写测试这件事在 ThoughtWorks 是自然而然的,它也体现在我的开源项目上。恰好,最近我正在帮助客户设计和实施测试策略。

用 Rust 来写个应用,这个想法颇久了。之前呢,要么找不到合适的场景,要么觉得 Rust 门槛有些高。直到最近呢,刚好对底层编程有点想法,便想着用这门语言做点东西玩玩。

每次看到遗留系统的时候,我总想着设计一个迁移方案。时间一久,收集的案例一多,外加上我也有了越来越多的案例,便想着记录一下这些内容。 ## 遗留系统的迁移 遗留系统的迁移是一个相当复杂的工作,以至于重写的成本甚至比迁移的成本更高。但是从**技术维度**来看,步骤无非就是: - 设定迁移的目标 - 制定迁移的计划 - 迁移计划的验证(PoC) - 实施应用程序的迁移 - 校验迁移结果 对,就是这么简单。 ### 遗留资产 我们通过把数字化时代的遗留资产划分了这几种类型: - **遗留代码**。所有没有测试的代码。 - **遗留基础设施**。所有不安全、没有弹性、不可靠的基础设施。 - **遗留系统**。所有不可观察、没有支持的自制系统或者商业化系统(COTS) - **遗留架构**。所有限制交付价值的架构。 - **旧式流程**。所有不可度量的流程(缺少 KPI、SLO) - **旧式组织**。所有不敏捷且不统一的组织 - **旧式思维**。相信上述内容无法克服或无法改变 替换这些系统的原因,也无非就是: - 降低成本:更快的概念兑现 - 改善客户体验 - 上市 - 可伸缩、可扩展系统 - 技术变革根上业务变革的速度 ### 迁移的目标架构 > 架构量子则是具有高功能内聚并可以独立部署的组件,它包括了支持系统正常工作的所有结构性元素。 —— 《演进式架构》 在单体架构中,量子就是整个应用程序,每个部分都高度耦合,因此开发人员必须对其进行整体部署。 | 架构 | 架构量子 | 可演进性 | |---|---|---| | 没有架构的单体 | 单体(大泥球) | 低 | | 分层单体 | 单体(分层应用) | 低 | | 模块化、结构化单体 | 单体(如模块化的 COTS) | 中 | | 微内核 | 内核 + 插件 | 中 | | 事件驱动 - 中介 | 总线、消费者、订阅者 | 中 | | 事件驱动 - 代理 | 队列、消费者、订阅者 | 高 | | 基于服务的架构 | 微服务 | 高 | ### 过程模式 对应的替换过程模式有: - **改善现有**。现有系统已经过现代化改造,可以通过改进设计来提供更好的结果。通常,核心技术堆栈保持不变,或者可能会引入一些次要的补充。 - **缓慢替换**。IT 系统的组件/功能块已被新技术取代,并作为单独的应用程序移至生产环境,而系统的其余部分仍旧采用旧技术。随着时间的流逝,剩余的组件/功能块将被单独的应用程序取代,然后逐步重建整个系统。 - **普通替换**。整个系统使用新技术进行了重建,旧系统已停用。 它使用标准平台从头开始构建,或者使用第三方程序包作为基础层构建。 当然了,每种模式的要求也有所不同: | | 改善现有 | 缓慢替换 | 完全替换 | | -|-|-|-| | 现有化技术栈 | 低 | 高 | 最高 | | 系统修改 | 应用级别 | 应用级别 / 局部变化 | 企业级 | | 风险等级 | 低 | 中 | 低 - 高 | | 资金需求 | 中 | 中 | 低 - 高 | | 持续时间 | 数月 | 数月到 1 年 | 数月到数年 | | 长期收益 | 低 | 高 | 最高 | | 人生度 | 最高 | 高 | 低 | | 人力成本 | 低 | 中 | 高 | ### 迁移策略 在我们决定好了迁移的目标和模式之后,只需要适合的方式即可: - 保留(Retain),什么都不做。 - 清退(Retire),摆脱原有束缚。 - 重新采购(Repurchase),转移至不同的产品。 - 重新托管(Rehost),即直接迁移。 - 平台更新(Replatform),『修补加迁移』。 - 架构重构(Re-Architect),更改应用程序的架构和开发方式,往往通过使用云原生功能来完成的。 这里,我们主要考虑讨论的是:重新托管、平台更新、架构重构,因为只有这三项是技术活动。 ## 防护网 对于遗留系统的迁移,想必你也相当的有经验了,比如这些常见的实践: - 使用版本管理。 - 小步前进。 - 使用测试作为防护网。 - 频繁提交。 - …… 而在这其中除了架构的设计,最复杂的一部分莫过于:防护网的设计。 ### 自动化测试 适用的场景:遗留代码、遗留系统、 遗留架构。 对应的实施方式: - 代码级重构。 - 组件级重构。 - API 级重构。 - 系统级迁移。 常见的防护措施有: - 单元测试。针对于包级、组件、函数级的代码重构场景。 - 容器内测试。针对于模块化的 OSGI 架构应用。 - API 测试。采用纺锥型测试策略进行系统迁移。 - 端对端测试。较少采用,成本较高,效果较差。 - UI 测试。 - 性能测试。针对于云迁移下的对比。 常见的工具有:xUnit、 REST Assured、Karate、Cucumber 等。 ### 比对 适用场景:遗留基础设施、遗留系统、遗留架构。 基础设施迁移: - 数据库迁移。 - 构建工具迁移。 常见的实施方式有: - 数据比对测试。通过测试对比迁移前后的数据变化,来判定迁移是否成功。 - 数据库比对测试。同上,只是维度变成了数据库。 - Schema 比对。确保数据模型或架构结构在源系统和目标系统之间匹配。 - Row 计数比对。确保计数是针对源和目标之间的表是否匹配。 - 数据汇总测试。对源和目标之间的大量表执行汇总检查。 - 制品比对测试。针对于构建工具迁移,对比构建产物,看是否发生变化。 - checksum 比对。 - class 比对。 常见的工具有:DBDiff、DbUnit 等。 ### 防腐层 适用场景:遗留系统绞杀。 常见的实施方式有: - 过渡 API。适用于遗留系统迁移的过渡模式,在迁移完成好,可以删除。 - 防腐层。即建立与遗留、腐败的代码的层级,以隔离系统变化。 - BFF。适用于多种客户端模式 ## 结论 没有银弹,迁移才是最有意思的技术挑战。

最大化重用会使得可用复杂化。 —— 《Java 应用架构设计》

最近,我又挖了几个开源项目的坑,Ledge、Ledge Framwork、Igso 等等。每次挖新坑的时候,经常性地都要花很多的时间,想着怎么编写 README、完善 README。而就是这么一个简单的 README 的编写,它都要花费我相当长的时间,或是几个小时,或是几天。

上周在公司内部又做了一次关于开源的分享,与三月份那次稍有不同的是,这次的关注点主要是:企业与开源软件。

Feeds

RSS / Atom

最近文章

存档

2026 (2 个月)
2025 (12 个月)
2024 (12 个月)
2023 (12 个月)
2022 (12 个月)
2021 (12 个月)
2020 (12 个月)
2019 (12 个月)
2018 (12 个月)
2017 (12 个月)
2016 (12 个月)
2015 (12 个月)
2014 (12 个月)
2013 (9 个月)
2012 (3 个月)
2011 (1 月)
2010 (1 月)
1991 (1 月)

分类

标签

作者