当前位置: 首页 > 科技观察

微信Android模块化架构重构实践(下)

时间:2023-03-16 16:31:31 科技观察

第1部分:http://zhuanlan.51cto.com/art/201708/547499.htm架构重构的取舍与取舍,我们也看了业界,发布了通过各种解决方案,希望能找到一些解决办法。我们参考了业界许多开放和已发布的架构设计。总的来说,目前AndroidApp的整体设计除了关注“大前端”外,基本都是关注“插件/应用沙盒”。可以参考atlas、small、DroidPlugin、DynamicApk等方案,不难发现,使模块动态化是它们的核心能力。“大前端”是一个热门方向。结合我们自身的情况,考虑到迁移现有代码并不是一件容易的事情,团队在短期内完全切换编程工具是不现实的。这种“远水难解近渴”的感觉只能暂时放弃。我们将目光投向了“插件化/应用程序沙盒”。沙箱的优点是隔离性强。一些解决方案可以完全隔离代码和资源。境界限制极为严苛,效果非常好。此外,大多数解决方案还具备动态更新和补丁功能,动态化已基本成为标配。所以这个时候我们在考虑要不要走这样一条路。一切从自身情况出发。从微信的角度来看,我们最关心的问题是如何约束代码边界,如何防止架构恶化,如何提高开发效率。在这样的情况下,我们重新审视了动态插件和沙箱的解决方案。先看动态,再看行业经验。目前的动态通常有两个主要用途:1)作为热补丁;2)有强烈需求并行发布的业务,比如业务需求,作为快速发布的手段。对于第二点,就目前微信团队来说,是很少遇到的弱需求。相比之下,工具和电子商务应用程序通常具有匹配的使用场景。对于第一点,我们期望在模块化方面达到一个比较纯粹的目标,专注于解决代码本身的问题,然后在开发阶段之外使用tinker实现热补丁的动态需求。让我们再看看隔离。需要注意的是,这里的隔离类型主要来自于沙箱架构的作用。对于非独立插件项目(需要编译依赖其他插件),除动态外,其功能在代码隔离方面与普通模块相同,不再赘述。我们期待代码与代码之间的干净划分效果,但适得其反。图20-模块依赖示意图从上面的模块依赖示意图可以看出,微信业务模块之间的数据关系相当复杂,模块之间访问数据和共享某些功能的情况非常普遍。实际情况比示意图更麻烦。面对微信业务数据的频繁相互调用,沙箱隔离很容易导致代码复用和相互调用困难。在微信上实现起来难度极大。不管重构的难度有多大,光靠隔离带来的好处未必能弥补开发效率上的损失。发展模式也是如此。微信安卓团队目前每次迭代大概有30到40人参与,内部沟通成本也不算高到无法接受。通常,开发人员可能需要同时开发和修改多个模块,并保证它们之间相互独立,模块间通信可能会很频繁。在这种情况下,模块调用者不方便是非常重要的。此外,还有其他问题需要考虑。从几个成熟的方案中,可以看到挂接Android框架、修改aapt、更换或封装androidgradleplugin、代理组件等设计,这些方案的复杂度和兼容成本都不容忽视。使用和维护它们需要仔细平衡。所以思前想后,我们还是选择了回到原来的模块化道路。一切问题的根源在于说了很多年的代码边界、解耦和内聚。只要有清晰独立的代码模块,以后还有其他改动的可能。除了代码,在架构内部的模块负责人制度中还有一句话,“没有监督的权利一定会被腐蚀”。如果放到软件开发这个行业,那就意味着“没有监督的代码肯定会变质”。所以代码应该受到“监督”。为了长期有效地维护代码质量,我们开始实施新的代码审查机制——模块负责人制。代码审查的好处是不可否认的。在此之前,由于微信业务的快速发展,同学们经常会根据自己的需求改变开发方向。面对业务模块比人多的情况,开发者往往需要自己开发多个模块。因此,很多模块被无数人维护过,尤其是基础支撑项目。这种游击式的方式开发效率高,支持了微信快速的研发节奏,但也导致了很多“无主代码”。大家对代码缺乏“归属感”,也降低了改进和优化模块的欲望。另外,在此之前,codereview都是由leader来review申请回流trunk的MergeRequest,导致效率低下,容易遗漏问题。合理的代码审查应该是全员的。模块领导系统试图改变这些现状。通过声明模块,每个人都对模块的代码和设计负责,对模块提供的接口服务负责,并监督其他人修改自己模块的行为。这些情况显着提高了开发人员的代码所有权,改变了大家修改、优化和修改代码的动机。推行模块负责人制,逐步推进大规模代码审查。这种方式非常适合像微信这样一开始就没有实施全员CodeReview的项目。目前,模块负责机制运行平稳,代码审查率和模块接受率均有提升。重构与开发者心态的关系在一个长期没有改进的框架中,开发者的习惯可能会逐渐变成跟风和保守开发。这可以粗略地描述为“只要别人这样做,我也会这样做。这样的设计即使不好,也不会错”。随着这种心态越来越普遍,又出现了另一种情况:经常听到一些同学抱怨一些代码,却很少看到代码在改进。这说明有些沉积问题还没有被大家发现,但是也没有人愿意去修改。在这种情况下,随着时间的推移,代码和框架会越来越差,一些问题会逐渐成为“老病”。面对这个问题,首先要说的是,这不是开发商资质不合格的问题。其实有想法的开发者很多,但是要把每一个想法转化为代码,让大家接受,并不是一件容易的事。.尤其是在一个大框架中,尝试改变的成本是非常高的。如果他的主要任务不是改进一些模块,那么很多想法就无法变成现实。这就是为什么保守主义和从众的习惯逐渐变得普遍的原因。保守的气氛需要打破。当你开始重构时,你会发现团队中会有很多积极的声音回应,他们会抛出积压的想法和意见。一个问题的解决可能会带来解决另一个问题的机会,其他开发同学的一些想法可能更容易实现。因此,时不时地推动一些模块的重构,发布一些对代码的不满,是一个很好的激活。另外,重构之后,还需要考虑切换代码组织方式来指导开发,使用更多的模板,正确的代码示例等,让他们可以放心的参考。模块划分经验谈维护代码边界代码边界就像一堵墙,架构的恶化从这堵墙的倒塌开始。从以往的经验来看,编译隔离是唯一的约束手段,简单的约定或准则不可能永远保持下去。所以无论如何,尽量不要放过对编译的约束。然后,将接口和实现分离,其他项目只依赖接口不依赖实现,这样分界效果更好。当然,伤害无处不在。比如遇到某个模块急需添加多个接口时,可能会出现跳过接口,直接依赖实现项目开发的情况。这时候可以考虑通过codereview进行监控,也可以开发一个简单的编译脚本来检查是否有不当的依赖。划定模块边界的细节在对代码进行解耦时,即使大体上模块职责划分清晰,但由于模块之间的各种业务关系,细节上还是会纠缠不清。事实上,由于需求和功能的不同,并没有完全适用于每一个应用的模块划分规则。随着业务的发展变化,模块边界不合适的情况完全符合预期。那么如何让每个人的模块划分更加合理,或者说当你遇到两难的时候,通过什么方式你会理解的更好呢?我们建议的方法其实很简单:尽量“讲一个有逻辑的故事”,哪个故事讲得通,就可以作为拆分的选择。因为代码解耦从来都不是问题,纠结的是解耦行为能不能被理解。例如模块间通信使用的数据结构是哪个模块的问题,就可以这样仲裁。在纠结的时候,一个自圆其说的解决办法往往就足够了。我们要尽量避免的是胡乱拼凑,纯粹为了类型解耦而解耦的情况。模块的总体组织设计一个模块,我们有一个总体组织,可以将模块分为三个项目:实现项目、api项目和库项目。实施项目提供逻辑实施。api项目提供对外接口和数据结构。library工程,提供了该模块的一些工具类。从另一个角度看,实施项目其实与应用的状态和生命周期有关,它的执行依赖于应用的各种状态。库项目不关心这些状态。因此,也可以看成是提供某种功能的库,实现就是如何使用这个功能。比如我们实现一个表情模块。库项目提供表情资源、表情渲染和播放能力,api项目提供使用表情的服务接口,实现项目提供api的实现、何时开始加载表情资源、缓存管理、表情功能等比如商店等等。当然,这是一个指导性的建议。很多时候,library项目和api项目之间没有明确的界限是很正常的。但是强烈建议至少有一个实现项目和一个api项目。在使用依赖分析工具对代码进行解耦时,快速分析代码的依赖关系可以大大提高工作效率。AndroidStudio提供了一个很好的工具。图21-Analyzedependency工具文件、资源、项目都可以进行依赖分析。有了分析结果,一步步分离代码就容易多了。***重构整体架构并不是一件容易的事情,通常不可能让整个团队仅仅为了重构而停下来。所以,微信的重构一直是“分裂”->“灰度化”->“回流”随着版本迭代的循环节奏。“设计系统的组织产生等同于组织间通信结构的设计和架构”。对于微信前几年走过的路,如今团队内部的沟通形式,依然可以实现更直接的沟通。这些情况决定了微信今天的技术选择。因此,在方案选择上,我们更愿意寻求一种相对简单、合适的方式来解决问题——使用纯模块化来保持后续架构的灵活性和健壮性,重新强调依赖关系,强调应用状态和生命周期,并加强代码的边界。除了代码的设计,我们在代码之外也做了一些努力。我们认同codereview的意思,我们也开始实行moduleowner的review机制。另外,我们还打算加强文档的使用,当然这个还在规划中。原文链接:https://www.qcloud.com/community/article/794491,作者:carlguo【本文为专栏作者《腾讯云技术社区》原创稿件,转载请联系原作者获得授权】点此查看该作者更多好文