软件开发|软件开发架构模式浅谈:一些思考和实践记录

软件开发|软件开发架构模式浅谈:一些思考和实践记录

文章图片

软件开发|软件开发架构模式浅谈:一些思考和实践记录

文章图片

软件开发|软件开发架构模式浅谈:一些思考和实践记录

文章图片

软件开发|软件开发架构模式浅谈:一些思考和实践记录

文章图片

软件开发|软件开发架构模式浅谈:一些思考和实践记录

文章图片

软件开发|软件开发架构模式浅谈:一些思考和实践记录

文章图片

软件开发|软件开发架构模式浅谈:一些思考和实践记录

文章图片

软件开发|软件开发架构模式浅谈:一些思考和实践记录

文章图片

软件开发|软件开发架构模式浅谈:一些思考和实践记录

文章图片

软件开发|软件开发架构模式浅谈:一些思考和实践记录

文章图片



一 背景和问题 我个人平时会比较慎用“架构”这个词
一方面是觉得业界有很多架构大师和架构模式 , 而我的认知和实践有限; 另一方面是因为这个词看着挺高大上、有点务虚 , 如果不结合实际场景的具体问题来讨论 , 容易陷入“PHP是最好的语言”这样的辩论赛中 。 而不同场景中又有各自的问题 , 程序员们通过自己的理解和思考、针对实际场景对一些架构模式进行了扩展实践 , 以此来解决遇到的问题 , 也会基于同一个模式延伸出一些派生概念 。兵无常势 , 水无常形 。 所以 , 我个人的观点是:以要解决的问题为出发点 , 去讨论我们要采用的架构模式(技术方案) 。
另外 , 由于我们是站在很多巨人肩膀上的 , 讨论时可以站在一些如SOLID等软件设计/开发原则的基础上 。
写这篇文章 , 我也是从解决一些问题的目的出发的:























































































































































【软件开发|软件开发架构模式浅谈:一些思考和实践记录】
最近和团队同学讨论了相关话题 , 虽然大多数同学在实践上基本一致 , 但具体到话术、名词概念和具体使用的理解和实践上有些差异(这是很正常的 , 因为业界对同一个模式的理解和实践也不同) 。 我结合一些实际编码场景做了一番陈述 , 为了避免后续重复大费口舌 , 所以打算写下来 , 以后有需要直接发文章链接 。由于我个人的认知和实践有限 , 所以也希望能抛(huan)砖(ying)引(lai)玉(pen) , 让我学到更多 。虽然同一个架构模式在不同业务/技术领域的实施会有区别 , 但同一个团队内应该保持一致性 , 因为这样有助于日常的code review、功能模块的交接backup等活动 , 尤其是有利于使用统一的单测建设方案来保障我们的产品质量 。实际问题:我最近在开发商家合并发货的功能 , 但由于之前基础发货功能的界面和逻辑并不是我开发的 , 所以我在修改原有代码、支持有非常多细节逻辑的合并发货能力时 , 就在担心对原有发(zhong)货(yao)能力的影响 。 而这时候 , 如果有单测的保障 , 我就可以更放心地进行功能升级改造了 —— 别说更复杂的合并发货能力了 , 而这类诉求在复杂的交易场景里很普遍 。提炼一下我遇到的具体问题: 在由不同开发人员持续迭代、进行功能升级的软件开发活动中 , 如何保障具有复杂逻辑的商家经营工具的产品质量 。软件开发活动是整个流程的核心环节:接收产品和视觉设计需求/变更作为输入 , 然后输出客户可用的终端产品 。而统一的软件开发架构模式 , 则是我们保障软件开发质量的基础 。 (这里就不具体展开WHY了) 由于讨论的是具体面向客户使用的业务场景 , 少不了客户操作交互的视图层(View) , 所以我从MVC开始谈起 。二 从表现层的MVC谈起 虽然我平时比较慎用“架构”这个词 , 但我平时喜欢随手拍一些建筑物 。 因为建筑之美 , 会让我联想到软件的架构也应该有美感 , 毕竟Software Architecture这个概念也是起源于Architecture 。 这时候 , 架构这个词就会给我一种接地气的感觉:有多少块砖 , 每块砖做什么用、放到哪里去 , 这块砖 和 那块砖怎么黏在一起或互相支撑 。 当然 , 由于软件的可移植性、可复用性 , 从某些角度来讲 , 软件架构相比建筑架构有其更复杂的地方 。MVC诞生至今已经超过40年了(Since 1979) , 10多年前就得到过很广泛的讨论和实践 , 穿越时空到今天肯定有其反脆弱性和内在核心价值 。 虽然如今乍看起来好像已经过气、被讨论过千百遍了 , 但仍然有很多程序员会有不同理解和看法 , 或多或少 。 这是很正常的 , 上面也提到了部分原因 , 这里具体再展开下 。1 MVC在经典三层架构里的位置 MVC是一种通用架构模式 早期PC时代应用在桌面客户端 ,后来在Web时代变得流行(我以前写PHP也用过相关MVC框架) ,如今在移动互联网时代也得到广泛应用 。上面这三个场景的应用 , 都是面向客户的 , 需要交互表现的 。从MVC命名中的View(视图)也可以看出 , MVC模式应用在软件系统架构里的表现层 。在业界某知名公司的官方文档里 , 也明确把MVC放在Web Presentation Patterns下 。我之所以没有在上图中对M-V-C添加箭头线条 , 是因为在这一点上 , 不同程序员也有不同理解和实践 。这是第一个需要明确的点:MVC架构模式在多层系统架构里的应用范围 。左侧 业务表现层-业务服务层-基础服务层 是移动端三层架构模式 , 未涉及到 C/S 交互;右侧是Web B/S场景的三层架构模式 。因为有些应用会比较简单 , 根本不需要业务服务或基础服务层 , 纯粹靠一个MVC(或者VC)就能交付出一个Mobile/Web App; 而且在一些业务系统里 , Web前端/桌面客户端/移动App 也可能会被简化为 大前端/大终端表现层; 所以可能基于不同信息 , 不同程序员对此会有不同认知 。但随着用户终端应用的重要性和复杂度的提升 , 已经从简单应用发展到复杂多团队协同的平台型或航母级应用 , 仅靠一个MVC来完成交付是不合适的 。我们也可以反过来想 , 程序员会把以下代码放在客户端代码的哪一层: 对Web引擎的扩展逻辑 。通信协议的结构定义 , 以及相应的socket连接和通信代码 。一个业务相关且UI无关的平台开放能力 。Crash捕获、卡顿监控、日志埋点等功能实现 , 比如Android在做APM相关事情时会采用AOP方式 , 利用ASM、AspectJ等方案来做字节码插桩 。…… 2 业界基于MVC模式的不同实践 前面提到不同程序员对MVC模式的理解和实践存在差异 业界大厂亦然 , 以下会结合业界一些知名且有影响力的公司在MVC模式上的实践 , 做进一步的展开讨论 。知名公司A 知名公司A在指导开发者使用MVC时 , 推荐下图方式: 可以看出在他们的实践上: Controller可以引用View和Model 。View可以引用Model 。这里的Model倾向于是Passive 。同时 , 他们建议: 在强类型视图场景 , 控制器从模型创建并填充ViewModel实例 , 该ViewModel 实例包含要在该视图上显示的数据 。当控制器由于责任过多而变得过于复杂时 , 也就是业界戏称的“MVC means Massive View Controller” , 需要将业务逻辑从控制器移出并推入域模型中 。知名公司B 说到Massive View Controller , 知名公司B在移动互联网方兴未艾的时候 , 推荐下图所示的MVC实践方案: 上图呈现出: Controller引用View和Model 。Model通过一些松耦合方式来触达Controller , 如广播通知、callback等 , 驱动Controller做出响应 。View通过代理模式等方案弱依赖Controller , 由Controller对各种用户操作、UI渲染诉求做出响应 。而View和Model之间是隔离的 , Model变化后对View的更新操作全部由Controller负责 。不过相应的官方文档已经被声明是过期文档了 , 并备注不一定是目前的最佳实践 。是的 , 随着移动互联网蓬勃发展 , 十年前的“最佳实践”被一路多种挑战 —— 在采用这种方案的开发领域中 , 如何重构Massive View Controller为Lighter View Controller已经成为了一个专题 。对比和思考 A和B的异同点 相同点:Model包含 所需的数据结构封装 , 以及相应数据操作的方法定义 。 即Data + 本地或远端的CURD 。差异点:在知名公司A给的图中 , View可以引用Model , 而在知名公司B给的图中则不行 。一些问题和思考 View有箭头指向Model , 这里的引用关系是指什么?是View持有Model.Data数据对象 , 还是View调用Model.CURD方法 。Controller的本意是Controing Logic , 那除了ViewController外 , 是否还可以有其它的XxController , 比如DataSourceController、NotificationController? 从命名上看 , 既然ViewController 既有View 又有Controller , 那为什么把它放在 C里面 , 而不放在V里面呢?比如当我们在iOS/Android开发中引入MVVM模式后 , ViewController或Activity属于M-VM-V的哪部分呢 , 代码放在哪个目录下呢? 我有类名使用ViewModel后缀就代表我使用MVVM模式了吗? Martin Fowler 作为 《重构 : 改善既有代码的设计》、《企业应用架构模式》等著作的作者; 敏捷软件开发宣言创作者之一; MVVM模式诞生时参考引用的技术专家 。Martin Fowler给的MVC模式图如下: 和上面知名公司A和B的图 , 又不一样了 , 不过他这里也是认为View可以引用Model的 。MVC和DDD Martin Fowler和《领域驱动设计》作者Eric Evans也讨论过MVC中Model的设计理念: 贫血模型:将Model分为简(pin)单(xue)的Model数据对象 , 和处理操作数据对象的Service/Manager/BizLogic等 。 示例:为aPerson修改name , 则由 CitizenService.changeNameOfPerson( aPerson ) 这种方式来实现 。充血模型:将对应领域的处理逻辑放到领域模型中 , 使得这个领域模型更饱(chong)满(xue) 。 示例:aPerson要刷牙 , 则由 aPerson.brushTeeth() 来实现 。 补充:充血模型更有面向对象编程的味道 , 尤其是搭配交易领域等业务场景 , 更有体感 。 不过稍微细想一下 , 可能就会发现DDD对设计的要求会更高 , 从而对研发周期和质量保障提出了新的要求 , 并且可能引起对现有系统的大规模重构 。 (盒马的DDD实践) 也就是说 , 大到MVC各个模块的依赖引用关系 , 细到Model中的代码设计方式 , 业界都有不同的理念和实践 。Java Web开发领域也对Model的设计产生过非常激烈的讨论 。小结 先抛开具体模块的代码设计方案 , 基于以上几种业界大厂或专家的描述 , 我小结了以下这张图并标注了待解问题: 问题一:如何解决MVC中Controller的膨胀臃肿问题? 要回答如何解决 , 需要先思考为什么膨胀 。问题二:View能否引用Model? 要回答能否引用 , 需要先定义引用关系是什么 。 是持有对象 , 还是调用CURD接口操作对象 。 又或者这两者没有必要区分 , 因为持有的对象本身就可能带CURD接口 。参考上面相关资料 , 目前业界有的支持、有的反对 。问题三:存在View -Model , 那么是否可以反过来存在 Model -View? 和问题二在描述上相反但又有关联 , 如果对问题再进一步提问的话: 使用 -引用关系 , 是为了解决什么问题? 使用 -引用关系 , 会产生什么问题? 如同文章开头所说 , 以上问题需要结合具体场景来展开(见 实际案例结合) , 尽量从务虚到务实 。3 Redux-like Architecture and Framework 随着前后端分离得更彻底 , 终端设备性能和用户体验重要性的提升 , 前端领域也得到了蓬勃发展 , 开发方式也有了比较大的变化 , MVC-like方式不再是主流: UI开发方面从早期的命令式到现在的声明式 。整体应用和业务逻辑实现方面 , 从早期的OOP写法 , 转向基于FP的响应式编程 。 比如Redux的数据流、React的Hook特性等 。各种框架蓬勃发展 , 一些概念和模式的提出、实践应用方面 , 我个人认为是领先并影响客户端的 。 其中Redux是一个经典案例 , 并且我觉得Redux官方也挺开放包容的 , 比如Dan Abramov写的《You Might Not Need Redux》 。和MVC延伸派生出的MVC Family一样 , Redux提出或重新带火了数据流、状态管理等概念 , 开始影响其它平台领域 , 并诞生了一些框架 。比如ReSwift、swift-composable-architecture , 以及SwiftUI里的State and Data Flow 。虽然我也写过点React , 但并没有怎么实践过Redux 。不过这些变化和影响 , 是我们在解决问题的过程中需要结合考虑的 。三 实际案例结合 1 常见的数据结构定义和使用 程序 = 数据结构 + 算法 。 —— Nicklaus Wirth , Pascal之父 , 图灵奖获得者 这句话乍看起来可能会有点面向过程设计的感觉 , 但OOP中的对象其实也是由数据+方法组成 , 而FP则更不用说了 。在编码开发活动中 , 会存在以上3种数据结构定义和使用方式: 原生数据结构 , 比如list/array、map/dictionary、tuple等 。类似MyContact的数据结构定义 , 由服务端返回的数据进行转化 , 并可能根据业务逻辑按需加上一些标志位给Controller消费 。类似ContactViewModel这样的纯粹为视图View服务的数据结构定义 。补充:(1)MyContact 和 ContactViewModel 只是特意区分的命名 , 实际上 MyContact 也可以是纯粹为视图View服务的数据结构定义 。 (2)但是 , 合适的命名有助于帮助我们思考和编码 , 从表达上呈现出我们的倾向和重点 。\"There are only two hard things in Computer Science: cache invalidation and naming things.\"—— Phil Karlton 2 常见的多复杂卡片的列表场景 这个场景可以部分回答问题一:为什么Controller会膨胀 , 以及如何解决 。其它部分答案则落在复杂页面场景的多delegate、target-action、notification-observer等视图交互响应的处理逻辑上 。我认为 , 之前反对View引用Model , 就是导致MVC变成Massive View-Controller的一个原因 。另一个原因我认为是工具链只提供了ViewController这样的Controller模板 , 隐式教导开发者都在这里写代码 。这也可能是因为十几年前移动互联网还没发展起来 , 移动App的复杂度低 , 所以提供了在当时简单够用的方案 。当只能由Controller 持有-Model的时候 , 那么在多复杂卡片的列表场景中 , 必须由Controller来更新每个View的属性/状态 。MyViewController需要为ContactCell更新它的各种相关属性 , 类似的还有AddressCell、PackageCell等 。MyViewController在更新AddressCell展示前 , 可能还需要先为它计算出合适的富文本展示内容 。MyViewController需要响应不同Cell的点击交互行为 , 包含但不限于按钮点击、输入框变化、富文本跳转、键盘起落等 。MyViewController需要响应CollectionView/TableView的DataSource/Delegate各种方法实现 。MyViewController需要响应Model层的变更通知 , 或者是另外一个ViewController抛过来的广播通知 。…… 然后MyViewController就爆炸了 。针对这种场景 , 我的解法是: 1.通过让View-Model , 基于工厂模式 , 把组件化Cell基于数据更新的布局逻辑交给View负责 , 如contactCell.configUIWithModel( contactModel ) 。 这样有点类似上面DDD提到的充血Model , 具备高内聚的特点 , 带来好处: a.和减轻控制器负担、推入域模型类似 , 通过把数据驱动布局的代码推入组件域内 , 减轻了MyViewController的负担 。b.利于做这部分组件化Cell的UI测试 。c.利于这些组件化视图复用到其它场景 , 比如交易管理场景的订单卡片可以复用到搜索场景中 , 不用在SeachViewController里复制粘贴一大堆代码 , 只需要从Model取一个数据对象丢给组件化Cell即可 。2.基于ViewController , 拆分出不同职责的扩展 , 比如MyViewController+Delegate专门复杂响应代理事件处理 。3.定义出其它类型的Controller , 比如MyDataSourceController , 专门为TableView提供数据源 , 可以类比参考Android中ListView的Adapter+ViewHolder 。工厂模式下 , 产品的刷漆、烘干、印花等操作会在内部完成 , 不会丢一个模型让客户去自己贴logo 。 MVC的每一部分都可以用不同的设计模式来组合实施 , 实现解耦或动态灵活的目标 。对应下图: 到这里已经回答了前面的问题一和问题二 。 更多解法可以参考上面提到的相关建议 , 比如lighter view controllers 这里采用了 VIew -Model 的方案 , 用来参与解决Massive View-Controller的问题 , 并且让View更容易复用和做UI测试 , 带来了好处 。可以结合前面提到的 “当控制器由于责任过多而变得过于复杂时 , 需要将业务逻辑从控制器移出并推入域模型中 。 ” 再进一步讨论下 。我的理解和举例: 存在一个输入框 , 让用户提交物流单号 。用户在输入过程或者完成输入后 , 由View通过delegate模式路由给Controller做校验 , 而Controller可能还要进一步依赖Model去做更完整的校验(如网络请求到服务端 , 因为物流单号的规则很多而且可能动态更新) 。当Controller责任过多、代码膨胀、过于复杂时 , 就将物流单号这块业务逻辑推入 物流(单号)域模型中 , 即由View直接通过delegate模式交给 LogisticsModel来做校验 。也是 View -Model。不一定对 , 抛(huan)砖(ying)引(lai)玉(pen) 。那么 , 存在VIew -Model , 有什么坏处吗? 3 一个Kotlin跨平台场景案例 这里不具体展开讲Kotlin及其跨平台相关内容 , 只是描述从MVC模式做跨平台迁移时遇到的问题 。这里的ViewController/Activity放在哪里 , 也和上面的一个问题相呼应 。虽然D-KMP主张通过全新构建工程写代码的方式来实践 , 但从实际情况出发 , 绝大多数现有系统都会是以单点尝试、渐进式的方式来落地 , 或发展、或回撤 。那么 , 如上图所示: 如果在既有MVC代码结构中 , View -Model , 在这种渐进式迁移场景下 , 就需要修改View来适配新的ViewModel 。如果 View 不引用 Model , 则是由 Controller来做胶水层设置更新视图(命令式UI) 。后者的好处是 , 保持View的独立性 , 只需要修改这个业务场景对应的单个Controller即可 。 因为View具有可复用性 , 可能在另外还没准备迁移/改变的模块里也有使用 。所以 , 此处不建议 View -Model 。 那么 , 谁对呢? 4 谁对谁错的务虚讨论 当有程序员要推荐使用其它架构模式的时候 , 通常开头的一句话就是先说MVC模式的问题 。比如ReSwift在写 Why ReSwift? 时 , 开头第一句话就是:Model-View-Controller (MVC) is not a holistic application architecture. 我个人认为 架构是一个名词(n.)+动词(v.) 。 架构(n.v.)是为了帮助 开发者在交流时有一致的理解、在业务需要时能够便于扩展、在出问题时能够快速定位等等(对抗熵增) 。 具体还是看采用这个架构的得失 , 要解决什么问题或带来什么好处 , 然后带来什么成本或付出什么代价 。 架构(v.)通过分配每部分代码的职责并为他们取名(好比iOS/Android开发工程师这样的岗位名称 , 让别人一看就知道是做什么的) , 然后几个名字加在一起 形成了架构模式这个抽象概念 。 从具体到抽象 , 然后再由这个抽象概念去指导程序员实践写代码 , 促进了架构的传播 , 比如MVC、MVP、MVVM、MVVM-C、MVI、MV-Whatever , VIPER , Redux and More…… 有时候并不一定是架构模式的错 , 还有可能是平台/框架在让架构模式自传播时采用的具体方案出了点问题 , 又或者是开发者自己写代码的问题 。有时候架构模式之间也不是互斥的 , 也可以在不同场景下互补 。 比如在MVC里 , 每个业务表现模块不一定要有三个元素齐聚 , 甚至也可以 VC-M-VC 共用一个M(可参考斯坦福iOS开发课程内容) 。有一个实际的业务场景是这么实践的: MVC和MVVM在一个业务场景里相结合 。这里提到了MVVM , 前面的Kotlin跨平台图 , 除了涉及到 View -Model 的引用关系是否应该存在的讨论 , 也涉及到了Declarative-UI和MVVM架构模式等概念 , 再顺便展开下 。1)MVVM 和 MVW MVC、MVP 和 Declarative-UI 这三个名词概念都有几十年历史了 , 其中声明式UI在近十年又开始火了起来;而MVVM则有十几年历史 。官方对MVVM有这么一些描述: V和VM是一对多的关系 , 即View 1:n ViewModel 。 相比于 MVC里 Controller 1:n View , 显然MVC的C更容易膨胀 , 所以上面也提到了使用多Controller方案 。V引用VM , VM操作M , 而VM不需要引用V(和MVP不同) 。 VM作为V的上下文 , 包含V所需用来展示的数据 , 响应V被用户触发的事件 。V对M无感知 , 反过来 M 对 V和VM 也无感知 。 整体是松散解耦的模式 。当VM的属性值发生变化时 , 通过数据绑定方式传达给V 。 这就需要有机制支撑 , 有对应的机制MVC也能做数据绑定 。基于上述描述我画了下图: 总体来说 , MVVM是基于事件驱动的、以数据绑定机制为支撑的松散解耦架构模式 。它的前提是有系统/平台/框架的机制支撑 , 不然实现成本和复杂度有点大 。相比MVC , 它的优势是视图控制逻辑不太会膨胀 , 代码单元更容易被测试 , 在可读性、可维护性上更好 。它可能带来的成本/问题是入门上手较难 , 简单场景下使用容易过度设计 , 复杂场景下出问题调试比较麻烦 。考虑到ViewController在该场景的薄胶水特质 , 以及也参与视图展示和用户响应 , 所以我把它放在V里 。由于业界有太多MV-开头的模式名词了 , 所以Angular官方直接声明了一个MVW: I hereby declare AngularJS to be MVW framework - Model-View-Whatever. Where Whatever stands for \"whatever works for you\". 这种声明有个好处 , 就是免去了谁对谁错/谁好谁坏的争执 , 而重点关注于谁适合 。5 谁对谁错的务实案例:VIPER和分层演变 关于谁对谁错/谁好谁坏 , 可以再来看一个案例 。VIPER架构模式的应用 VIPER概念由View、Interactor、Presenter、Entity、Router几个元素组成 , 大致如下: 我之前写过MVC、MVP、MVVM、VIPER等架构模式下的代码 , 完全实现或不完全实现 。有一次 , 我在某个业务表现模块里应用VIPER , 然后定义了一个XxxNetworkInteractor类 , 用来负责做网络请求 。VIPER架构模式的演变 后来随着业务场景的变化 , 一个业务模块可能需要对接多套网关服务 , XxxNetworkInteractor就要做抽象隔离 , 消除业务逻辑对多套网关请求的感知 , 并且应用到其它业务模块 。 这就有点像 VPER - I - VPER 模式 —— 多个业务模块对 Interactor 进行了复用 。进化 随着对接网关数的增加、网关能力的增强 , XxxNetworkInteractor也配套建设了更多能力 。随着使用XxxNetworkInteractor的模块越来越多 , 不同业务的一些通用诉求和处理逻辑也随之增加 。随着XxxNetworkInteractor的功能越来越强大 , 代码越来越多 , 逐渐需要抽离出一个单独模块 , 不管是从提高编译速度 , 还是从封装暴露等角度 。于是 , XxxNetworkSDK.framework诞生了 。退化 再后来 , 随着技术和业务的变化 , 底层网关服务ATop成为了领域范围内的事实标准 , 之前遇到的问题也消失了 , 而XxxNetworkSDK又带来一些成本 , 比如要配套ATop的能力升级进行迭代维护 。此时 , XxxNetworkSDK可以退出历史舞台了 , 让业务模块直接面向ATop编程 , 这样既降低了维护成本 , 又带来了一定的包大小收益 。以后 , 假如有另外一个领域范围的业务场景(采用了XTop) , 要复用最上层的业务模块 , 由于在另外一个领域范围 , 业务模块要从 调用ATop 改为调用XTop 。那么 , 接手或负责迁移复用的开发者 , 会不会在想:为什么这些业务模块直接调用ATop , 而没有使用一个XxxNetworkInteractor来做分层隔离呢?让业务不要感知具体的数据库或网络服务实现 。如果有 , 他只要做一件事情:让XxxNetworkInteractor对接XTop即可 。如果没有 , 他需要去替换修改每个业务模块的调用代码、返回值处理等等 。四 写在最后 , 回到问题 不管是务虚的讨论 , 还是务实的案例 , 或者是Angular的“whatever works for you”的观点 , 结论是没有谁对谁错、谁好谁坏 , 只有实际场景下要解决的核心问题 。兵无常势 , 水无常形 。 那么 , 回到想要解决的问题: 在由不同开发人员持续迭代、进行功能升级的软件开发活动中 , 如何保障具有复杂逻辑的商家经营工具的产品质量 。我的想法是以可测性作为手段 , 来保障功能升级改造或代码重构后 , 可在合理时间范围内得到充分回归验收 , 保障相关组件、模块或整体产品的质量 。具体的方案实施上 , 因为是以可测性为重要关注点 , 再结合目前的技术方向 , 我会倾向于采用MVVM 。MVVM的架构模式在理解和认知上比较成熟(应该是移动端开发领域TOP2流行的) , 便于实施和传播 , 并通过模式的名称来体现要强调的关注点:可测性 。MVVM可以更好地结合SwiftUI+Combine、Kotlin跨平台等技术方向 。MVVM完全版的上手门槛和简单场景的过度设计问题 , 可以通过采用不完全版MVVM来解决 。 不完全版的MVVM可以cover适用于简单场景的MVC , 近似于 超集-子集 关系 。 虽然也可以通过多Controller的方式来解决MVC膨胀问题 , 但MVC的命名在实践中就容易让程序员弱化掉可测性这个关注点(也可能是我个人理解和实践不够正确) 。复杂场景的调试问题、更多可测性的实践 , 需要再摸索下 , 也希望得到相关分享和指点 。作者 | 思禽 原文链接:https://developer.aliyun.com/article/832823?utm_content=g_1000313179 本文为阿里云原创内容 , 未经允许不得转载 。