编辑导语:面向方面编程也就是AOP,它使开发人员可以更好地将本不该彼此纠缠在一起的任务(例如数学运算和异常处理)分离开来;本文作者从面向方面看软件设计,我们一起来了解一下。
产品小王今天接到了一个新的需求——客户希望把登录验证时间从12小时调整到24小时。
产品小王接到需求时,心想这不就是把服务器中的数字从12改成24吗?简单得很,所以为了展现自己的专业性,他一口答应市场部的同事说今天就可以实现你这个需求;心里还想着用一点小手段就让市场部同事欠自己一个人情,以后找市场部办事方便多了。
于是产品小王便找到研发老王说:能不能帮忙把登录验证时间改成24小时啊,我已经答应市场部了今天实现,没问题吧。
研发老王一听就说这个做不了,你赶紧和研发部同事说下,别耽误人家事了。
小王一听,急了,急忙问到为什么啊,不就是改个数字吗?
老王说,可不仅仅是改个数字,我们当时在实现系统的时候,为了快速实现安全的相关功能,所以安全模块是贯穿很多模块的;如果要改这个数字,首先要看在各个模块中关于安全模块的相关代码,然后再查看修改代码产生的影响,最后还要测试数字修改后代码是否能正常运行。
小王心想:为什么改个数字要这么复杂啊?
其实这个问题在软件工程中很早就出现了,软件工程中也早就有一个很好的解决办法——面向方面编程(AOSE:Aspect-Oriented Software Engineering),这篇文章就详细展开说明什么是面向方面编程。
一、面向及面向方面
一般情况下,在软件实现过程中,单个需求需要多个组件实现,而每个组件也可能同时服务于多个需求。
换句话说就是一个组件可以服务多个需求,一个组件中也包含实现多个系统需求的代码;正如在下面这张图片中,安全需求组件和恢复需求组件同时服务于客户需求,账户需求和管理需求,组件之间相互搭配,进而实现系统功能的。
软件系统结构
在图中可以看到,在这个简单的系统中有三个核心功能组件,分别是客户需求,账户需求和管理需求;同时为了保持这三个核心功能组件能稳定运行,增加了安全需求和恢复需求组件。
在面向方面编程中,核心关注点(Concerns)指系统要实现的主要功能,比如上述图片中的客户需求、账户需求、管理需求;而把服务于核心关注点实现的功能称为横切关注点(CrossCutting Concerns),比如上述图片中的中的安全需求和恢复需求。
传统的代码实现过程中,核心关注点的实现总是包含额外的代码来实现横切关注点,这就会导致代码混乱和分散;虽然这种程序的实现方式能够提高效率,但是这种结构会导致的横切关注点的组件修改成本,复用成本都非常高。
原因是需要找出横切关注点与核心关注点组件间如何相互配合,并评估修改后对核心关注点组件的影响,修改完成后,还要全部验证核心关注点的组件。
说到这里,已经说明清楚面向方面编程的起因,接下来将说明什么是方面以及什么是面向方面编程。
方面指的就实现一个功能的程序,与其他程序不同的是,方面更偏向于描述程序间组成方法——一个可执行的方面根据自身的描述去组合对象,方法和其他方面创建处理的,同时规定了程序在什么地方运行。
方面的主要内容包括切入点、程序和连接点:切入点说明约定方面在什么时间开始执行程序;连接点指定系统在执行完程序后继续执行的程序,包括进行方法调用、初始化变量或者更新域,定义引用的事件集合等,如下图所示。
面向方面编程实例
面向方面编程正是基于方面的概念而诞生的,是一种专门实现横向关注点组件的编程思想。
二、分离关注点
面向方面编程的核心内容是分离关注点,是思考和构建软件系统的重要方法。
在面向方面编程中将关注点划分为各自独立的关注点,要求程序中的每个方面(类、方法、过程等)只为实现一个目的,进而降低修改和复用方面的成本,甚至不用思考关注点之间的相互影响。
当用关注点来表示一个需求或者一组需求的时候,我们可以很容易在实现组件中跟踪需求;如果需求发生改变,研发人员可以快速定位到需要需改的代码,并且不需要考虑方面之间的相互影响,快速实现需求改变。
三、实现面向方面编程
在面向方面编程中,关注点是从系统需求中导出的,利用分离关注点的概念作为考虑需求和设计系统的基础;这个是进行软件设计的原则,下文中展开的软件设计的步骤也是基于此进行描述。
1. 明确软件需求
和大部分的软件设计一样,在进行软件设计时要最先明确软件需求,也就是明确软件的主要功能是什么;只有抓住了主要功能,才能保证我们在软件设计过程中不会偏离方向。
2. 核心系统设计
明确软件需求后,我们就可以通过软件需求推导出核心关注点和横切关注点。
3. 方面识别和设计
通过视点识别方面是一种最常用的方法。如下图所示,每个视点代表一种用户的一组关注点,而每组关注点又可以分为核心关注点系统和横切关注点。
通过视点识别关注点,能很好地保证我们在分析时不重复、不遗漏,尽可能找到所有的关注点。
面向方面的设计和编程
面向方面的设计是利用方面进行系统设计的过程,通过方面来实现那些在需求工程中所找出来的横切关注点,同时将方面与系统的其他组件组合在一起。
4. 冲突分析和解决
在将方面和系统的其他组件结合在一起时,要分析并解决可能存在的冲突,保证不发生组合的二义性;在不保证二义性的情况下,要找到每个方面与系统合适的切入点;只有找到了合适的切入点,方面运行才会符合设计者的需求。
需要特别注意的是,由于方面都是基于对系统一定的预期而独立设计的,当多个方面一起作用与一个系统,一个方面对系统的影响会导致其他方面运行失败。
5. 名字设计
面向设计的最后一步是方面及切入点的名字设计,由于面向方面特殊的软件执行顺序,所以在设计面向方面程序时要特别注意方面和切入点的名字设计,避免方面通过切入点执行程序时调用错误。
6. 总结
面向方面编程的流程图如下:
面向方面的设计过程的流程图
四、测试
由于面向方面编程代码的特殊结构,测试是具有一定困难的,主要的原因是方面的程序与主体代码是紧凑的,而不是松散的;就算测试后在一处能正常工作,在其他环境下也未必能正常工作。
下面展开说明可能的测试方法和遇到的困难:
1. 阅读代码测试
面向方面编程的代码之间通过切入点进行连接,导致代码无法直接阅读;虽然可以借助一些代码阅读工具可以使代码“变平”,从而降低代码的阅读难度,但是面向方面编程的程序语言本身是动态的,而非静态的;所以借助工具阅读代码的只是解决了表面问题,无法解决实际问题。
2. 白盒测试
与白盒类似的还有结构化测试,这两种测试方法的共同点是设计能提供一定程度覆盖的测试方法;比如保证软件的每个执行路径都执行过一遍,每个程序语句都执行过一次。
遇到的问题和阅读代码测试类似,由于面向编程软件通过切入点连接,导致面向方面的程序不是结构化程序;在某些运行环境下,一些方面可能被执行,一些方面不执行;而在另一种运行环境下,方面的执行情况又是另一种情况,方面之间又相互影响;所以产生了庞大的测试地图,大大增加了测试难度。
很多现实世界中的系统都会涉及敏感操作,为了减少敏感操作带来的损失,系统需要在敏感操作之前验证用户身份,并进行相关的记录操作,比如生成日志、发出通知等。
那么这种功能的实现逻辑是什么呢?这篇文章将以财务系统的数据查看功能为例子说明面向方面编程如何实现用户认证。
财务系统中有很多敏感数据及敏感操作权限,这些数据及操作只能被有相关权限的员工看到及使用。
但是在很多情况下,仅仅靠权限控制无法达到理想效果,比如财务人员打开了相关页面并短暂离开座位去完成其他工作,这个时候该页面上的数据和操作没有任何保护措施。
为了对敏感数据及操作有更好的保护效果,产品往往会在相关领域增加账号验证——要查看敏感数据或者进行敏感操作需要先验证账号密码;如果密码不匹配,将直接退出系统并记录相关操作信息;如果账号密码正确,则继续操作并记录相关操作信息。
这种功能的实现逻辑大概有以下两种:
- 修改每个敏感数据及操作的组件,让组件去调用账号密码验证并生成操作信息;
- 修改系统代码,使得每次进行敏感操作的时候都调用账号密码认证,在认证完成后记录相关信息;
以上两种实现逻辑都不是非常合理的,原因如下:
- 第一种逻辑的问题是会导致验证操作和记录操作绑定在一起,不但造成代码冗余而且会固化代码,降低代码的灵活性;
- 第二种逻辑的问题是将代码分散操作,会导致后续修改需求时无法快速定位对应的代码,同时也会固化代码,降低代码的灵活性;
其实不难看出,账号密码验证及操作是属于横切关注点,可以使用面向方面编程实现,具体如下:
- 切入点:查看敏感数据或者进行敏感操作;
- 切入点程序:进行账号密码验证操作;
- 连接点程序:记录操作的相关信息等;
代码结构如下图所示:
敏感操作的面向方面编程结构图
从上例中可以看到,面向方面编程的优点是代码冗余少且灵活;但是在一定程度上增加了代码的复杂度,可能会出现无法预知的问题。
面向方面编程并没有大范围使用,从这个意义上来说,面向方面编程对编写代码的意义不大,面向方面编程更多的意义在于系统架构;通过面向方面思想设计的架构能有更强的灵活性和更少的冗余,这也是个人了解完面向方面后的最大感受。
作者:宝宝心里苦啊;公众号:宝宝心里苦啊
本文由 @宝宝心里苦啊 原创,未经作者许可,禁止转载。