UML软件工程组织

AOP在大规模软件开发项目应用的尝试与思考
作者:李珉   来源:developerWorks 中国

本文的写作源于一个真实的大型软件开发项目,我们努力尝试在这个项目中推广应用AOP。在此我们将对曾经面临过的一些实际问题与困难进行分析,试图引发关于面向方面软件开发(AOSD)的一些更深层次的思考。本文的作者将站在开发者的角度做出客观的判断,既不是AOP的狂热鼓吹者,同样也不是AOP反对阵营的一员。因此可以视作来自Java开发者对AOP技术应用的客观分析和建设性意见。

关于AOP

关于AOP的概念,笔者在这里不再赘述。谁最先创造了AOP,业界一直有些争议,但普遍接受的说法大概是最先由Gregor J Kiczales在ECOOP'97提出来的,随后Gregor又申请了AOP的专利[US06467086]。很多人可能不太服气,因为他们或多或少早已有了类似的想法,只不过没有想到给他起个新名字罢了。无论是OOP,MOP,还是AOP,其本质的想法都是试图在更贴近现实世界的层次上实现软件开发的模块化。从这个角度看,AOP的想法不过是新瓶装旧酒罢了。其实AOP作为新生事物的出现,并不是一种技术上的飞跃,而是软件模块化发展到某一个阶段的一个阶段性产物。人的思维通常都有一些惯性,在我们饱尝了OOP的艰辛后,有一种新的概念跳出来分析总结了OOP的某些缺点,而且以看起来合理的方式做出改进,难免会给大家一种耳目一新的感觉。但不可否认的是,到目前为止,AOP角色所扮演的应用角色更多的只是对OOP的一种补充,因此作为一种重要的"OP"存在似乎有些名过其实,看起来更像是一种高级的设计模式。然而,在很多人的眼中AOP的分量甚至不亚于OOP,甚至AOP被视作未来软件开发的一个趋势。笔者一直思考一个问题,AOP出现的七八年时间在IT界并不算很短了,有趣的现象是AOP始终保持了小火慢炖的热度,一直没有像大家所期望的那样大红大紫起来。

那么AOP究竟在多大程度上可以帮助我们解决实际的问题呢?让我们尝试在一个真实的软件开发项目中应用AOP。对AOP所推崇的各个典型应用方向加以研究,例如,日志(Log),事务(Transaction), 安全性(Security), 线程池等等。必须说明,我们这里提到的场景,是有一定规模的软件产品开发,我们最终完成的是百兆数量级的软件产品,因此我们研究的范围既不是程序员的个人行为,也不是小范围的示例。让我们一起来看一看有什么有趣的发现。

AOP的实践

我们试验应用AOP的方向很多,这里仅以最具代表性的Log为例。大多数人了解AOP,都是从经典的Log 关注点的剥离开始的。因此这是每一个AOP的爱好者都耳熟能详的案例。按道理,应该是无可争辩的。很不幸,在我们的研究过程中还是碰到了很棘手的问题。

让我们来看一看一个经典的AOP 做日志的例子。

我们假定,按照AOP的思想,主逻辑的开发人员在写代码的时候不应该考虑边缘逻辑。因此,上述的日志代码实际对主逻辑的开发者不可见。假定我们以主流的Log4J为记日志的实现方式,以AspectJ作为Aspect的实现方式。需要重申,本文的写作目的并不是针对某一种AOP的实现平台,选用AspectJ主要因为从语法的角度而言,AspectJ是目前所有AOP实现中覆盖范围最广的一种实现。


 这样一个记日志的横切关注点描述,是最经典的AOP应用,它本身是没有任何问题的。通常我们会怎样用它呢?在继承了这个抽象Aspect的子Aspect实现中指定切入点的位置①,并在这个位置上将实现的逻辑填入通知(Advice)②。

在一个小规模的应用开发环境中这样做是不会有问题的,首先,记日志的切入点不多,无论是采用一对一的位置直接描述,还是利用统一的编码规范来约束都是可行的方案;其次,通知中的逻辑不会很复杂。整体的软件开发流程不会有什么变化的需要,通常的做法是由专门的Aspect开发人员统一编写Aspect,而由大家共享记Log的Aspect。但是不鼓励每一个开发人员都写自己的Aspect,这样就不是横(cross-cut),变成过筛子了(cross-point),软件开发变成一盘散沙,失去控制,AOP带来的好处丧失殆尽。

那么,在我们的项目中,情况怎样呢?上述看似简单的两个点都存在问题:

(1) 具我们统计,在我们开发的软件上一个版本的软件代码中,总共有7万句记Log的调用。如果我们不做任何相关的总结工作,直接一对一的对切入点进行描述,那么在位置①上的切入点描述就有7万条之多;姑且不算工作量,即使这样做了,将来带来的代码维护将是天文数字的成本,此路不通。

那么我们只能寄希望能够提炼出这7万句日志调用的公共模式,我们在这里用到的是一种优化过的Log组件,接口与LOG4J类似,考虑到LOG4J的广泛应用,我们下面将以LOG4J为参照。Log Level类中预定义了五个级别,DEBUG, INFO, WARN, ERROR,FATAL,根据统计,Fatal类型的调用最少,根据Fatal的级别定义,我们或许可以花一定时间整理代码,提炼出捕捉Fatal点的规则。然后次之,WARN和ERROR大约占7%左右,这一部分就不好办了,WARN/ERROR类型的LOG并没有严格的界定,代码的分布点也难寻规律,一定要找到规律,要付出相当大的代价。最后,DEBUG, INFO占据了很大的比例30%-50%,顾名思义,这一部分的代码出现的随机性很大,无论怎样努力都不可能找到有意义的公共规律。此路还是不通。

有一种说法也许可以解释这种想象:如果切入点难于描述的时候,很大原因是因为关注点的定义不准确。此说法有一定道理,以"日志"作为一个方面来切入粒度似乎太大了。那么,唯一的办法是将"日志"关注点进一步拆解。一直拆解到可以接受的程度。但是,我们的问题似乎还没有解决,如果我们的项目足够小,那么这样的拆解总是有一定的限度的,这种做法或许可行。但很不幸,我们的项目很大,要经过相当多的分解才能最终找到日志的规律性。我们还是可能需要成百上千条语句来指定切入点的位置,最终的结果将很难维护,这样的做法对于一个不断演化中的项目而言是脆弱乃至于不可接受的。况且,像Debug这样的Log级别,无论你怎样拆解,都不可能找到完美的规律。通常,任何一个系统中的Log都会保持逻辑的一致性,如果经过了这样的层层分解,Log作为一个逻辑主体的完整性被完全破坏了。这是一种为了AOP而AOP的做法,非但工作量没有减轻,还带来了无穷的后患。

好了,只剩最后一招了,为了用AOP, 我们牺牲掉Log的某些特性,预先定义好编码的规则和日志原则,强制推行。当然,如果想要全面覆盖所有的日志点,这样的日志原则只能定得非常粗。从AOP的角度来讲,技术上是可行的,但粗放的日志规则会带来Log的信息量疯长,对于我们的软件项目来说,还是不可接受,因为日志失去了它的精确性,会对系统的维护产生较大影响,而且大量日志信息的增长对系统整体运行性能的冲击是显而易见的。

(2) 在图1 的第二个要点上我们也同样面临问题,也许从图上的例子你可能还看不出来,因为在所有的介绍AOP的文档材料中介绍Log的例子都很简单。但是现实生活中的Log很不留情面。很多时候程序对Log的调用都有其特殊的原因,它们的Advice需要分别编写。例如在例子产品中我们经常需要把一个变量传给"日志"方面, 而且,经常要传入一个局部变量。至少现在,所有的AOP实现都还不支持对局部变量的捕捉,在可见的将来也不会有这种实现。好吧,只好重构代码,如果您想传参数,必须要遵守我们预先定义的编码命名规则。但是,这样给编程人员带来的限制会很大,你很难说服开发人员手捧厚厚的编码规范,小心翼翼的写程序。

综合上述两个致命缺陷,我们试图推行Log Aspect的工作已经遇到了相当的阻力。

问题分析

原本看起来一件简单的事情,现在变得很复杂了,最初的热情变成了爱恨交织与困惑,一定是哪里出了问题。

在大型的软件开发项目里AOP的切入点应该怎样控制?这牵涉到开发的流程,版本控制,编码规则等等。似乎AOP本身并没有给出明确的答复。当我们谈OOP的时候,涉及到的更多的是类,继承,多态,而更深层次的方法学被隐藏在OOA与OOD中。类似的,我们在提到AOP的时候,表面上似乎就是Aspect,pointcut,advice,… 然而隐藏在代码后面的面向方面的精华却被忽略了。因此,我们主张,在我们学习应用AOP的时候,不要过分沉迷于代码,应当合理的在分析、设计上投入更多的精力。这是我们从上述的失败经历中得到的第一个教训。才能的。 我们不能把AOP想象成为万灵丹,服用之后立刻生效。相反,在实际项目中应用AOP需要多方面的考虑,这样才可能对由AOP产生的问题胸有成竹。

从某种意义上讲,代码级别上的面向方面, 无论是面向Java的语言扩展还是XML,实现起来并没有太大的不同。但是面向方面的技术也应该体现在不同的抽象层面上,如果在大规模的开发中应用,AOA(面向方面的分析),AOD(面向方面的分析设计)是必不可少的,因此,AOSD(面向方面的软件开发)更贴切的描述了面向方面实际上是一个完整的实施过程。关于在更高层面上的AOSD,很多人正在做有益的尝试,例如IBM 支持的基于Eclipse的CME项目,首先要解决的是怎样在软件开发的初始阶段,定义合理的关注点,至于后面的实现是否基于纯粹AOP的工具,并不是重要的问题。脱离分析、设计阶段的关注点分析,直接在代码中使用AOP,只在小规模的应用中可行,一旦面向大规模应用,许多问题就会接踵而来。

除此之外,面向方面的开发过程还必须解决很多的具体问题,诸如:

怎样在切入点的公共模式与切入点的特征描述之间权衡?这是一个怎样合理的控制AOP的粒度的问题,前面我们已经提到过,横切关注点的粒度其实是很难决定的。在高层次上定义的横切关注点, 在底层实现的时候还可能会碰到问题。对具体的AOP实现,可能单纯从代码的角度来看,粒度划分得越细越简单越好。但这样一来,方面的代码与核心代码就会绑定很死,不好维护。而且方面的代码不优雅,看起来好像单纯把一块代码摘出来另放一个地方,刻意隐藏的痕迹很重,很难看出AOP的好处。但粒度如果太粗,AOP的代码就必须依赖公共模式的总结,这样的公共模式首先需要花功夫来定义,力求完美,因为规则的不断变化会带来很多问题,所以应尽量避免出现规则的频繁变动。而且,规则是大家都要遵守的,这样会带来更多的编码限制。当然,对于具体的项目,编码规则的多少是具体的问题,但如何能做到适度,并不是一个简单的问题。

很现实的一个问题,如果在大型开发项目有多个横切关注点的时候,这些关注点之间应该如何打交道。在我们的实际项目中,就遇到这样一个难题,日志关注点在很多情况下是与其他横切关注点纠缠在一起的。其中的难题是横切关注点的剥离先后顺序问题。在一个版本延续性的开放项目中,由于需要重构,这个问题更为突出。某些情况下,横切关注点之间如果存在双向的相互依赖,就必须要修改逻辑,屏蔽这种可能性,否则,以目前的AOP的实现方式,很难处理,AOP的代码会很难看,主逻辑的代码重构会纷繁琐碎,而且极难维护。

在一个大型的软件开发项目中,软件的生命周期应该都处于可管理的状态。如果AOP大规模介入,很多问题都变得很敏感,尤其是QA。按照AOP的想法,关注点是互相隔离开的,因此,实质上AOP造成软件模块间的耦合性变得松散了。软件开发过程的松耦合必然带来测试的复杂性。如果系统中的关注点控制在小范围,那么这种负担可以在小范围的开发单位内部消化掉,但一旦关注点涉及到全局,这种变动就会蔓延,带来的测试和QA的额外工作会迅速增长。并有可能会冲击到现有的测试与QA流程。

大型软件产品的售后服务,通常都会在必要的时候触及源代码,以IBM为例,第三级的服务支持人员,将在源程序上跟踪纠错。很不幸,这些人必须尽快学会AOP,否则他们将无所适从,因为他们可能根本不知道整个应用逻辑是怎样编织在一起的。软件的维护与服务的成本可能会因此提高,并依赖特定的工具。

AOSD发展方向的探讨

首先,这里我们再次强调AOSD,而不是单纯的AOP。面向方面的发展空间,应该不仅仅局限于代码层面。究竟面向方面是否会在未来很大程度上影响的软件开发模式,目前的形势并不明了。面向方面的分析与设计,应该对面向方面技术的未来起到主导作用。以现在面向代码层的AOP的用法,坦率地讲,只是一种小范围的设计模式,并不值得投入太大的热情。很多热门的松散耦合的开发模型,比如SOA,SCA等等,看起来更现实,更合理,他们都有类似的理念,但具有比面向方面有更好的柔性。

面向方面的分析与设计必须能够解决下面的问题:

首先,面向方面的方法学需要完善:

1) 怎样从具体需求中定位横切关注点;这样的定义一定不是来自于简单的基于名字的原则,例如,Log在所有的软件项目中都是横切关注点吗?如果我们软件开发的对象是类似IBM WebSphere 这样的J2EE应用服务器,那么事务 (Transaction)还是横切关注点吗?
 2) 怎样合理的控制方面的粒度?从设计阶段的横切关注点到实现阶段的方面,怎样平滑的过渡?我们经常碰到的问题是,在实现阶段,方面之间仍然有相互依赖性的问题,怎样才能在分析设计阶段就尽量避免这些问题?
 3) 关注点的模型表达方式,我们通过怎样的标准形式来表达关注点,关注点之间的关系,以及横切关注点与主逻辑之间的关系?
 4) 这样的横切关注点分析是可以重复、可验证的吗?有没有可行的通用的方法学来帮助我们使得横切关注点的识别与规范成为可控的过程?

 其次,面向方面的软件工程是无法回避的重要问题,通过上面的问题分析,相信大家都已经认识到AOP对现有软件过程各个阶段的冲击,因此有必要去规范适用于AOP的新的软件开发过程。首先,面向方面的开发必须遵循迭代的模式。但现有的软件工程方法都不足以让我们有足够的信心在当方面多到一定的程度时,软件的开发仍然是可控的。为什么面对以前的OO,基于组件的方式,甚至更新的SOA的开发,我们都不会如此的不安?主要是因为在这些模式下,我们都能够比较简单的看到完整产品的雏形,而方面一旦多到某种程度,软件的整体性就不能直观的被感知到。因此,面向方面对开发流程的冲击是不可避免的。特别是叠代的开发方式,如何对每个开发循环有效控制也是很有学问的。

最后,面向方面的开发工具是大规模应用AOP的技术保障,现有的开发工具都不能保证面向方面的软件开发的效率与完整性。我们需要一种能够更好的集成方面开发的工具。AJDT能够提供一些帮助,但是还远远不够。一个理想的AOP开发工具应该支持:

1) 软件产品的完整视图,能够以可视并且形象的方式组合横切关注点和核心关注点
 2) 能将AOP技术集成到系统的分析设计阶段
 3) 基于方面组合的测试与Debug工具,能够灵活的根据不同的关注点组合进行测试和Debug
 4) 更加灵活和方便的关注点定义方式,对大多数Java开发者屏蔽复杂的Aspect语法

 总结

本文通过我们在大规模项目中应用AOP的实际经验,总结了碰到的问题和取得的进展。通过我们的实践与分析,我们认为在AOP有较大发展之前,以现在的状态,AOP更适合在小规模软件开发项目中应用。而AOP的更大规模应用不仅需要代码级AOP强有力的支持,更依赖于面向方面的分析设计技术与相应软件工程领域的进展。

最后,在看到了问题之余,我们也看到了AOP技术仍然处于一个不断发展的阶段。关于AOP的文章不断涌现,AOP工具的功能在不断加强,而开发人员对AOP的接受程度也在逐步提高。乐观的看,也许在不久的将来,AOP就能获得理论和实践上的长足进步,从而解决本文中提到的问题。让我们拭目以待。

参考资料

AspectJ是目前使用最广泛也是功能最强大的AOP代码级别的实现。我们可以从http://www.eclipse.org/aspectj 获得它。
 AJDT是基于Eclipse的AspectJ的集成开发环境,它包含了最新版本的AspectJ。可以从http://www.eclipse.org/ajdt 获得它。
 http://aosd.net/ 上有很多AOP相关的资料和论文。另外,您也可以通过这个网站参加每年一次的AOSD会议。
 Developerworks上的AOP@Work系列能够帮助我们深入了解AOP的现状
 CME是一个有趣的工具,它对AOP在整个软件工程生命周期中的运用提供了支持。特别有趣的是它能够帮助开发人员发现和提取Crosscutting Concern。可以从http://www.eclipse.org/cme获得它。

 

版权所有:UML软件工程组织