31 July 2011

经常听到这样一种说法:单元测试迫使你不得不进行仔细的架构设计。

这说法没错,如果没有好的架构设计,代码几乎是不能做单元测试的:到处都在new,静态方法随手调用,等等等等,如此这般。

但通常这样做的结果是引入了不必要的过度设计。本来new一个对象就用了,现在要做依赖注入,无论是用spring之类的基于xml的配置注入,还是guice那样的annotation注入,总之一但有了这些,代码几乎就变得不可读了,因为你从代码逻辑里你根本看不出某个对象是什么,是怎么构造出来的;为了弄明白一段代码究竟是怎么工作的,你需要找到做依赖注入的地方:xml或者annotation,或者随便什么。总之如果没有一个好的IDE帮你在各种不同的文件中穿梭,没有一个详细的设计文档,你需要相当长的时间来整明白一个前辈设计的优秀系统是如何被粘在一起工作的。

也许你同样有优于常人的头脑、敏锐的洞察力,可以在很短的时间内把东西看懂,然后继续开发,但并不是所有人都能做到这一点。这样问题就来了,一个所谓优秀的架构设计能维持多久?也许换了几拨人之后,最初的设计思想就完全没人能理解了,然后新人会在代码里恶狠狠地写下shit之类的注释,或者把看不懂的代码拷贝一份出来修修补补,或者经过一番痛苦的思想斗争之后完全重写,替换成他所熟悉的依赖注入框架。天呐,这是在做优秀的架构设计吗?不觉得是在自掘坟墓吗?

先别准备开口反驳,或者破口大骂,我一点儿反对依赖注入的意思都没有。我非常赞成在系统关键部位使用依赖注入,特别是那些模块接口的地方。这就像中医的针灸,针总是要扎在关键的位置,而不是插满全身,前者治病救人,后者纯粹就是解恨。

而选择依赖注入的方式,能用annotation就别用xml。推崇xml的人最喜欢说的一句话就是:用xml配置一下,可以修改系统行为而不用重新编译,特别是对于Java这样的静态语言。不错,xml是可以做到这一点,但是仔细回想一下,究竟有多少次我们这样干过?究竟有多少是真正的需求,而不是开发人员或者系统设计师一厢情愿的天真想法?注意,我们是在谈依赖注入的配置,而不是单纯的参数配置。必须弄明白的是,用作依赖注入的xml也是代码的一部分,也就是说,如果脱离了xml只看代码,你压根儿就读不懂。而且xml这玩意儿信息冗余得很,你得练就一双法眼,透过tag看本质。顺便说一句,Java里面的xml实现是我见过的最恶心的设计,简直可以作为Java语言使用的优秀典范,因为它可能用到了所有的Java语言特性。

同样的过度设计问题在AOP里也存在,不过这可能完全是我的个人喜好,可以任意批判。我根本无法理解为什么为了省掉一些异常处理的代码,而引入一堆一堆屎一样的xml配置。你可以说这是为了提炼出系统的本质,让人很容易看懂代码逻辑是什么而不用纠缠于异常情况,但是哥们儿,代码里50%以上都是在处理异常,异常根本就是逻辑的一部分,作为一个开发或者维护人员,我需要知道异常时候究竟干了什么,而不是忽略。如果需要业务人员也能很容易明白代码做了什么,我们需要的是DSL,而不是AOP。搞Java的人就喜欢弄出一个个看起来很酷的缩写,AOP,SOA,JEE,JSE,EJB,JCA,JAXP,JAXB,JMX,JMS……我已经晕了,不过也没什么,也就是一帮人躲在屋子里折腾出来的一些规范而已。

在需要依赖注入的地方用annotation的方式注入,在该简洁的地方毫不犹豫地直接调用,允许一定的代码冗余,把架构设计做得让中等水平的人可以很快看懂并且继续开发,让水平次点儿的人也能明白是怎么回事儿,这样的系统也许会具有更强的生命力。

但是一些耦合度很强的类怎样做单元测试呢?其实在Java世界里这个问题早就解决了,有一票儿的单元测试mock工具:JMock,Mockito,Powermock,等等。别再纠结于“做个依赖注入吧,否则怎么做单元测试”这种问题了,依赖注入不是为了解决如何做单元测试的,不能为了单元测试刻意做一些不必要的复杂设计,因为单元测试可能也就是芝麻而已。



blog comments powered by Disqus