Day933.如何将设计最终落地到代码 -系统重构实战
如何将设计最终落地到代码
Hi,我是阿昌,今天学习记录的是关于如何将设计最终落地到代码的内容。
这要将各个组件拆分到独立的模块工程中,最终将架构设计落地到代码中。
组件化架构重构 5 个关键的步骤分别是:
- 设计
- 守护
- 解耦
- 移动
- 验收
其中设计及守护通常针对的是整个系统,解耦、移动及验收则是针对组件来进行。
前面设计阶段的工作已经完成,那么接下来,就来完成守护的步骤,为 Sharing 项目补充基本的功能自动化测试,作为重构的安全网。
一、补充自动化测试
补充自动化测试这一步,可以按照用户的核心使用场景来覆盖。
以 Sharing 项目为例,用户的主要操作为登录系统,然后查看消息、文件以及个人账户的信息。
将这些主要的场景梳理成 4 个 UI 的大型测试,作为基本的冒烟测试。后面是 4 个用例。
- 用户打开应用,输入正确的账户密码登录成功。
- 用户进入主页面,切换至到消息页面,能够正常显示消息列表信息。
- 用户进入主页面,切换到文件页面,能够正常显示文件列表信息。
- 用户进入主页面,切换到账户页面,能够正常显示登录的个人信息。
接下来,将这 4 个核心的用例变成自动化测试用例。
@Test
public void show_login_success_when_input_correct_username_and_password() {ActivityScenario.launch(LoginActivity.class);onView(withId(R.id.username)).perform(typeText("123@163.com"));onView(withId(R.id.password)).perform(typeText("123456"));Intents.init();onView(withId(R.id.login)).perform(click());intended(allOf(toPackage("com.jkb.junbin.sharing"),hasComponent(hasClassName(MainActivity.class.getName()))));
}@Test
public void show_message_ui_when_click_tab_messsage() {//givenActivityScenario<MainActivity> scenario = ActivityScenario.launch(MainActivity.class);scenario.onActivity(activity -> {//whenonView(withText("消息")).perform(click());//thenonView(withText("张三共享文件到消息中...")).check(isVisible());onView(withText("1.pdf")).check(isVisible());onView(withText("2021-03-17 14:47:55")).check(isVisible());onView(withText("李四共享视频到消息中...")).check(isVisible());onView(withText("2.pdf")).check(isVisible());onView(withText("2021-03-17 14:48:08")).check(isVisible());});
}@Test
public void show_show_file_ui_when_click_tab_file() {//givenActivityScenario<MainActivity> scenario = ActivityScenario.launch(MainActivity.class);scenario.onActivity(activity -> {//whenonView(withText("文件")).perform(click());//thenonView(withText("Android遗留系统重构.pdf")).check(isVisible());onView(withText("100.00K")).check(isVisible());onView(withText("研发那些事第一季.mp4")).check(isVisible());onView(withText("9.67K")).check(isVisible());});
}@Test
public void show_account_ui_when_click_tab_account() {//givenActivityScenario<MainActivity> scenario = ActivityScenario.launch(MainActivity.class);scenario.onActivity(activity -> {//whenonView(withText("个人")).perform(click());//thenonView(withText("test")).check(isVisible());});
}
详细的用例设计及写法,可以参考自动化测试和如何提升遗留系统代码的可测试性里编写自动化测试的方法。
用例的执行结果是后面这样。

从报告的执行可以看出,一轮基本的冒烟测试执行时间在 10s 左右,可以在重构的过程中,频繁地运行这些测试得到反馈。一旦发现代码修改破换了原有的逻辑,就可以马上修复。
在覆盖守护测试这一步,理论上来说覆盖越多场景,那么守护的质量就越高。但是这样做带来的成本也会提高,所以建议在这一步至少覆盖用户核心的使用场景及操作路径。
有了基本的冒烟守护测试,接下来就可以开始动手解耦了。

这么多组件,应该先从哪些组件入手呢?
建议是按从下往上的优先级开始解耦,因为上层的组件依赖下层的组件,所以必须先将下层组件独立出来。
二、日志组件解耦
遵循从下往上解耦的原则,先来看看日志组件的解耦。
后面的截图是根据依赖分析扫描后的结果。

从图里可以看出,目前日志组件的主要问题是依赖了账户组件账户信息。
LogUtils 类依赖了 AccountController 类中的 username 属性。
针对这个异常的依赖,可以采用提取参数的方式来解耦,让日志组件只依赖字符串类型的 username,具体的安全重构是后面这样。

解耦完成以后,需要重新运行一下依赖分析工具确认符合架构约束规则,然后就可以移动文件了。
如果还没有新建对应的模块,则需要先建立一个新的模块。
建立完成后,需要在 gradle 的配置文件中加入模块的依赖。

接下来,就可以使用 Modularize 功能移动文件了。

移动完成后,最后一步就是验收阶段,需要保证编译通过、自动化守护测试运行通过。
后面截图表示技术组件架构守护用例执行通过,基本的冒烟测试功能验收通过。


因为各个组件的移动和验收步骤和这里讲的日志组件类似,差异主要在分析解耦和解除依赖上,所以之后学习如何解耦其他组件时,主要演示解耦过程。
三、基座组件解耦
来看基座组件的解耦,同样结合依赖分析扫描结果截图来分析。

根据依赖分析扫描结果可以看出,目前基座组件主要依赖了各个业务组件的页面以及相关的一些发布功能。
MainActivity 依赖了 Message Fragment、FileFragment 以及 AccountFragment。另外也依赖了 MessageFragment 中的 uploadDynamic 方法以及 FileFragmnet 中的 uploadFile 方法。针对页面的依赖,可以使用路由的方式来解开耦合。
这里先用反射的形式来处理,具体使用路由框架的方式。

对于基座组件依赖了特性组件的具体行为,这里可以采用倒置的方式,让特性组件将行为注入到基座组件,这样才符合架构的约束规则。
以 MainActivity 调用 MessageFragment 的 uploadDynamic 方法为例,加深理解。
首先定义触发消息模块点击上传的接口。
public interface IMessageAddClickListener {public void onClick();
}
接着将原本依赖 uploadDynamic 的地方,调整为调用 IMessageAddClickListener 接口。

最后在消息模块中将具体的实现注入到基座组件中。
//消息页面
((MainActivity) getActivity()).setMessageAddClickListener(() -> uploadDynamic());
解耦完成后,重新使用工具做检查,结果如下图所示,可以看到,基座组件依赖特性组件的用例已经运行通过。

四、文件组件解耦
看看文件组件的耦合情况。

同样,根据依赖分析扫描结果可以看出,目前文件组件的上传及下载功能主要依赖了账户组件的个人信息。
FileController 类依赖了 AccountController 类中的 username、isLogin 属性,FileFragment 类同样也依赖了 AccountController 类中的 username 属性。
前面讲架构设计的时候(回顾如何进行组件化分析和设计的组件划分),提到文件的传输功能会被多个特性组件使用,所以这里除了要解除文件组件和账户模块的依赖,还要把文件模块的上传下载功能独立成一个单独的功能组件。
这里重构的思路是将上传下载相关的代码提取到独立的 FileTransfer 类中,然后通过提取参数的方式与账户模块解耦,最后移动到独立的功能组件中。
具体的重构过程是后面这样。

提取文件传输组件后,我们重新来看文件模块的依赖情况。
如下图所示,目前主要是 FileController 依赖了账户模块的 isLogin 登录状态,FileFragmnet 依赖了账户模块的 usename 信息。

对于特性组件之间的耦合,可以采用提取接口的方式来解耦,具体的操作是后面这样。

注意重构完成后,接口的注入应该是使用注入框架来实现,这里同样先采用反射的机制来完成实现的注入。
解耦完成后,继续使用工具做检查,结果如下图所示,文件特性组件不能依赖账户特性组件的用例已经运行通过。

五、消息组件解耦
看消息组件的耦合分析。

通过上图的依赖分析结果可以看出,消息模块主要依赖文件模块的上传下载功能,另外与文件模块类似,同样依赖了账户模块的账户信息。针对于与文件模块的耦合,可以直接使用文件传输模块功能组件解耦。
对于账户模块的耦合可以参考文件组件解耦的方式,使用 IAccountState 接口来解耦,操作和前面几个组件类似,这里就不再重复演示了。
最后,可以看到,随着解耦工作的完成,最终所有的守护用例全部执行通过。

六、总结
组件化架构重构的 5 个步骤,最终将 Sharing 按新的架构设计落地到代码中。
因为上层组件依赖下层组件,所以在重构的过程中,按架构从下层到上层的顺序,依次结耦组件解耦。
最终,来看看架构设计图在代码中的落地情况。
至此,也来总结一下组件化架构重构前后的对比。

通过对比,我们发现重构后的 Sharing 项目,架构设计更加清晰,并且因为加了自动化的手段守护架构,可以有效地避免架构腐化。
同时,自动化测试的加入也能帮助我们更早发现问题,实现测试的左移。基座组件解耦时,使用了反射来实现页面之间的依赖解耦。
在文件组件解耦合时,同样使用了反射来解决接口实现注入的问题。使用反射虽然能达到解耦的目的,但是代码中也存在了大量的重复代码。
相关文章:
Day933.如何将设计最终落地到代码 -系统重构实战
如何将设计最终落地到代码 Hi,我是阿昌,今天学习记录的是关于如何将设计最终落地到代码的内容。 这要将各个组件拆分到独立的模块工程中,最终将架构设计落地到代码中。 组件化架构重构 5 个关键的步骤分别是: 设计守护解耦移动…...
209. 长度最小的子数组
209. 长度最小的子数组 力扣题目链接(opens new window) 给定一个含有 n 个正整数的数组和一个正整数 s ,找出该数组中满足其和 ≥ s 的长度最小的 连续 子数组,并返回其长度。如果不存在符合条件的子数组,返回 0。 示例: 输入…...
【数据结构与算法】查找(Search)【详解】
文章目录查找查找概论一、查找的基本概念顺序表查找一、定义二、算法有序表查找一、折半查找二、插值查找三、斐波那契查找线性索引查找一、稠密索引二、分块索引三、倒排索引二叉树排序与平衡二叉树一、二叉排序树1、定义2、二叉排序树的常见操作3、性能分析二、平衡二叉树1、…...
一文学会 Spring MVC 表单标签
✅作者简介:2022年博客新星 第八。热爱国学的Java后端开发者,修心和技术同步精进。 🍎个人主页:Java Fans的博客 🍊个人信条:不迁怒,不贰过。小知识,大智慧。 💞当前专栏…...
如何在 Windows10 下运行 Tensorflow 的目标检测?
前言 看过很多博主通过 Object Detection 实现了一些皮卡丘捕捉,二维码检测等诸多特定项的目标检测。而我跟着他们的案例来运行的时候,不是 Tensorflow 版本冲突,就是缺少什么包,还有是运行官方 object_detection_tutorial 不展示…...
【jvm系列-04】精通运行时数据区共享区域---堆
JVM系列整体栏目 内容链接地址【一】初识虚拟机与java虚拟机https://blog.csdn.net/zhenghuishengq/article/details/129544460【二】jvm的类加载子系统以及jclasslib的基本使用https://blog.csdn.net/zhenghuishengq/article/details/129610963【三】运行时私有区域之虚拟机栈…...
ctfshow愚人杯 re easy_pyc wp
一、反编译题目pyc文件 题目下载解压后是一个.pyc文件,那就去反编译看看呗,因为之前用过uncompyle6,直接去命令行执行 uncompyle6 -o ez_re.py ez_re.pyc 得到ez_re.py源码一份~ 但是这里我用uncompyle6反编译的结果不知道为啥就出来很多奇…...
Ubuntu18.04 系统中本地代码上传至Gitlab库
主要步骤如下: 设置SSH Key 上传项目 1.创建SSH Key 每次上传可重新设置一个SSH Key或者使用已有SSH Key (1)创建SSH Key 创建一个新的SSH Key,终端输入以下指令,其中 “xxxxxx163.com” 是邮箱账号: s…...
Leetcode.1665 完成所有任务的最少初始能量
题目链接 Leetcode.1665 完成所有任务的最少初始能量 Rating : 1901 题目描述 给你一个任务数组 tasks,其中 tasks[i] [actuali, minimumi]: actuali是完成第 i 个任务 需要耗费 的实际能量。minimumi是开始第 i 个任务前需要达到的最低能…...
【C++笔试强训】第一天
选择题 解析:在for循环的循环条件(y 123) && (x < 4)中 ,&& 表示逻辑与,从左向右判断两边条件是否成立,只有当两边的条件都为真时,这条语句才为真。左边y 123是赋值语句,一直为真&…...
【网络安全软件】上海道宁与Cybereason为您提供未雨绸缪的攻击保护,终结对端点、整个企业以及网络上任何角落的网络攻击
Cybereason可收集 计算机网络内任何活动方面的数据 如运行当中的程序 被用户访问的文件以及 员工及任何获授权使用网络中的计算机人的 键盘输入和鼠标移动情况 Cybereason提供 即时结束网络攻击的精确度 在计算机、移动设备、服务器和云中 到战斗移动的任何地方 一、开…...
基于RK3568的Android11 适配 MIPI 屏幕
文章目录 前言一、mipi接口是什么?二、原理图三、屏幕点亮流程四、屏幕关键参数1.General Specification2. Power on/off sequence3.Timing五、屏幕初始化序列改写如何把原厂给的数据转换为设备需要的时序dcs小知识:初始化时序:退出时序:总结前言 在本小节会学习到如何适配…...
Ubuntu安装python
CentOS 安装 Python3 没什么坑,按照步骤一步步来就可以了。 但 Ubuntu 安装 Python3 的坑却不少,这里总结一下,避免以后继续踩坑。 我用的是 ubuntu16.04,安装最新版本的 Python3.8.3 第1步:安装编译环境 安装之前…...
django 运用pycharm的各种故障汇总(1)
一.用django入门第一个问题:pycharm的[community]社区版-免费开源与[professional]专业版注册收费两个版本:用django只能有[professional]版本便捷、专业; 解决方案的各种学习总结: 1.破解版:网上找了很多资料,基本已经没效果,不要报太大希望; 2.找中间途径然后有:Python 、…...
【设计模式】单例模式Singleton(Java)
文章目录定义类图Java经典实现懒汉Lazy Mode:饿汉Eager Mode:在饿汉下的多线程案例在懒汉下的多线程案例总结定义 单例模式(单件模式)确保一个类只有一个实例,并提供一个全局访问点。——HeadFirst 单例模式通过过防…...
机器学习中的公平性
文章目录机器学习公平性评估指标群体公平性指标个人公平性指标引起机器学习模型不公平的潜在因素提升机器学习模型公平性的措施机器学习公平性 定义: 机器学习公平性主要研究如何通过解决或缓解“不公平”来增加模型的公平性,以及如何确保模型的输出结果…...
Docker镜像之Docker Compose讲解
文章目录1 docker-compose1.1 compose编排工具简介1.2 安装docker-compose1.3 编排启动镜像1.4 haproxy代理后端docker容器1.5 安装socat 直接操作socket控制haproxy1.6 compose中yml 配置指令参考1.6.1 简单命令1.6.2 build1.6.3 depends_on1.6.4 deploy1.6.5 logging1.6.6 ne…...
蓝桥杯30天真题冲刺|题解报告|第三十天
大家好,我是snippet,今天是我们这次蓝桥省赛前一起刷题的最后一天了,今天打了一场力扣周赛,前面3个题都是有思路的,第三个题只过了一半的案例,后面看完大佬们的题解彻悟,下面是我今天的题解 目录…...
配置 Git Husky 代码提交约束
介绍 Git Husky 是一个可以管理 Git Hooks 的工具,它可以帮助我们在代码提交的时候运行脚本,以确保代码提交符合特定的规范和约定。 在 Git 中,允许在操作特定的事件时执行特定的脚本,这些事件我们称之为 Hooks。 Git Husky 利…...
IntelliJ IDEA 2023.1 最新变化
文章目录IntelliJ IDEA 2023.1 最新变化一. 主要更新1. 新 UI 增强 测试版启用新 UI2. 在项目打开时更早提供 IDE 功能3. 更快地导入 Maven 项目4.后台提交检查5. Spring Security 匹配器和请求映射的导航 Ultimate二. 用户体验1. 全 IDE 缩放2. 保存多个工具窗口布局的选项3. …...
铭豹扩展坞 USB转网口 突然无法识别解决方法
当 USB 转网口扩展坞在一台笔记本上无法识别,但在其他电脑上正常工作时,问题通常出在笔记本自身或其与扩展坞的兼容性上。以下是系统化的定位思路和排查步骤,帮助你快速找到故障原因: 背景: 一个M-pard(铭豹)扩展坞的网卡突然无法识别了,扩展出来的三个USB接口正常。…...
Linux链表操作全解析
Linux C语言链表深度解析与实战技巧 一、链表基础概念与内核链表优势1.1 为什么使用链表?1.2 Linux 内核链表与用户态链表的区别 二、内核链表结构与宏解析常用宏/函数 三、内核链表的优点四、用户态链表示例五、双向循环链表在内核中的实现优势5.1 插入效率5.2 安全…...
Redis相关知识总结(缓存雪崩,缓存穿透,缓存击穿,Redis实现分布式锁,如何保持数据库和缓存一致)
文章目录 1.什么是Redis?2.为什么要使用redis作为mysql的缓存?3.什么是缓存雪崩、缓存穿透、缓存击穿?3.1缓存雪崩3.1.1 大量缓存同时过期3.1.2 Redis宕机 3.2 缓存击穿3.3 缓存穿透3.4 总结 4. 数据库和缓存如何保持一致性5. Redis实现分布式…...
相机Camera日志实例分析之二:相机Camx【专业模式开启直方图拍照】单帧流程日志详解
【关注我,后续持续新增专题博文,谢谢!!!】 上一篇我们讲了: 这一篇我们开始讲: 目录 一、场景操作步骤 二、日志基础关键字分级如下 三、场景日志如下: 一、场景操作步骤 操作步…...
python/java环境配置
环境变量放一起 python: 1.首先下载Python Python下载地址:Download Python | Python.org downloads ---windows -- 64 2.安装Python 下面两个,然后自定义,全选 可以把前4个选上 3.环境配置 1)搜高级系统设置 2…...
理解 MCP 工作流:使用 Ollama 和 LangChain 构建本地 MCP 客户端
🌟 什么是 MCP? 模型控制协议 (MCP) 是一种创新的协议,旨在无缝连接 AI 模型与应用程序。 MCP 是一个开源协议,它标准化了我们的 LLM 应用程序连接所需工具和数据源并与之协作的方式。 可以把它想象成你的 AI 模型 和想要使用它…...
用机器学习破解新能源领域的“弃风”难题
音乐发烧友深有体会,玩音乐的本质就是玩电网。火电声音偏暖,水电偏冷,风电偏空旷。至于太阳能发的电,则略显朦胧和单薄。 不知你是否有感觉,近两年家里的音响声音越来越冷,听起来越来越单薄? —…...
Go语言多线程问题
打印零与奇偶数(leetcode 1116) 方法1:使用互斥锁和条件变量 package mainimport ("fmt""sync" )type ZeroEvenOdd struct {n intzeroMutex sync.MutexevenMutex sync.MutexoddMutex sync.Mutexcurrent int…...
安卓基础(Java 和 Gradle 版本)
1. 设置项目的 JDK 版本 方法1:通过 Project Structure File → Project Structure... (或按 CtrlAltShiftS) 左侧选择 SDK Location 在 Gradle Settings 部分,设置 Gradle JDK 方法2:通过 Settings File → Settings... (或 CtrlAltS)…...
9-Oracle 23 ai Vector Search 特性 知识准备
很多小伙伴是不是参加了 免费认证课程(限时至2025/5/15) Oracle AI Vector Search 1Z0-184-25考试,都顺利拿到certified了没。 各行各业的AI 大模型的到来,传统的数据库中的SQL还能不能打,结构化和非结构的话数据如何和…...

