原文: http://dev.csdn.net/article/29/29128.shtm
代码自动生成和抽象
Hoping (原作)
基本概念
抽象在软件开发中的重要性是不言而喻的。如果一个系统有了正确的抽象,那么这个系统就更容易理解,更容易维护,开发起来也更为高效,最为重要的是也更容易把事情作对。Grady Booch甚至认为抽象是应对软件复杂性最为有效的手段。在面临一个复杂的系统时,往往只要再提升一层抽象层次(当然要是正确的抽象),那么该系统就会立即变得清晰、简单,理解、开发、维护起来也更为容易一些。不过,值得注意的是,虽然有了一个正确的抽象后,可以大大降低理解、开发和维护的难度,但是要想得到一个正确、合适的抽象却是非常困难的。
提起代码自动生成,可能大多数人立即会想到这样一种情形:只要获取了系统的需求,那么就会自动地从这些需求生成系统的代码。这种想法固然很好,但是在目前的科学发展水平(这种技术不单是软件技术的问题,它还和人思维的生物基础有密切关系)下却还无法实现。需求和能够自动转化为代码的精确的形式化规范之间有一个巨大的鸿沟,目前这项转换工作还只能由人来独立地完成。因此,这种技术在目前来说只能是一个神话。我们在本文中所指的代码自动生成是针对比较局限的领域而言的,即需求已经被正确的理解并由人转化为解决方案领域中的抽象模型。代码自动生成并不是为了替代人去完成系统的软件开发的,它只是一种支援抽象的工具而已。
领域特定语言(DSL)
其实,大家在每天的软件开发中都在不经意的使用着这项工具。当我们在使用面向对象语言进行软件开发时,其实我们是在一种抽象的层面上进行工作。有了这层抽象,我们所编写的软件就会更有表现力,更为简洁。比如:当我们写下下面的代码行时:class Cat extends Animal。我们想表达的是Cat is a Animal,面向对象语言为我们提供了强有力的支持。而这个抽象的实现细节则由编译器来完成,我们无需关心。这样我们就能够在一个更高、更直接、更易于理解抽象的层面进行开发和交流,同时也大大降低了出现错误的机会。当我们使用支持泛型或者AOP的语言进行软件开发时,其实我们是在另外一种抽象层面上工作。比如:当我们写下如下代码时:
template
T Add(T, T)
我们想表达的是求两个类型为T的变量之和,而不管T到底是什么具体类型。想想看,如果语言不支持这种表达规范,我们要写多少个雷同的Add方法。有了这种表达规范,我们就可以直接、简洁地表达出我们的意图,而具体的转换工作就有编译器代劳了。还有,如果我们在支持AOP的环境中进行软件开发,那么我们只要使用该环境提供的AOP语言规范定义出我们希望的横切关系(其实就是一种抽象),剩余代码的编写和插入工作就由该环境帮我们自动完成了。虽然编译器或者开发环境在底层生成的实际上也是大量重复的代码,但是这些代码的抽象规范却只有一份,而人们开发、维护、沟通所基于的正是这唯一的一份抽象规范,底层的重复实现细节对开发者来说是不可见的,并且是自动和抽象规范保持一致的。可以说,在开发者的头脑中,是没有重复的。从而有力的支持了“once and only once”和DRY原则。试想,如果语言种没有提供这种描述规范,那么我们要编写多少晦涩、难懂、重复的代码才能描绘我们想要表达的概念。
上面提到的抽象是一些比较通用的机制,因此一般都是语言内置支持的。也正是其通用性使其有效性范围受到了限制。一般来说,上面的一些抽象机制是仅仅针对开发者群体而言的,并且使用这些抽象机制进行的表达也是由编译器来自动生成底层执行代码的。但是,还有一种抽象表达更为重要,它的作用是在开发者和客户之间进行沟通、交流。说它重要是因为它和所要开发的系统是否能够真正满足客户需要密切相关。这种抽象表达更贴近具体的问题领域,因此也称为领域相关语言(Domain-Specific Language(DSL))。比如,如果我们开发的是一个金融系统,那么如果我们能够使用一套金融术语及其关系来刻画金融领域中的一些业务逻辑,那么不但表达起来会简洁、直接得多,更重要的是客户也更容易理解,和客户沟通起来也更为容易。再如,如果我们要开发一个科学计算系统,那么如果我们拥有了一套描述科学计算领域的词汇,那么在表达该系统时不但会容易、自然很多,而且也更加高效。有了这套DSL之后,剩下的工作就是要自己实现一个编译/解释器,来把DSL自动生成为目标语言。由于这个DSL一般都局限于某个特定领域,因此其编译/解释器实现起来也不会有多大困难。
敏锐的读者一定会发现,我们在前面列举的支持面向对象(OO)、泛型(GP)或者面向方面(AOP)的语言,其实也是DSL的一种。只不过它们所针对的是更为通用的,和软件要面临的实际问题领域无关的领域。它们的作用是为了提高一些通用问题描述的抽象层次,并且也为构建更贴近问题领域的抽象提供了基础。
自上而下 还是 自下而上?
写到这里我突然想起一个非常有趣的问题,那就是大家常常争论的自上而下开发和自下而上开发。一般认为,自上而下的开发方法更具目的性一些,也更为自然一些。其实自上而下的方法是有很大风险的,一是往往很多预先的设想很可能本身就是错的,一是这些预先设想落实到底层时要么无法实现,要么无法很好地适配。其结果就是生成一个具有大量冗余、丑陋粘合层代码的系统。而采用DSL思想的自下而上方法则具有很多你可能没有想到的好处。你可以先实现一些DSL中的单个单词,然后再实现一些更大的单元,并试着把这些元素组合为更大的领域逻辑,按照这种方法实现起来的系统往往更加简洁、清楚、干净,甚至更加易于重用。一般来说,如果你想采用DSL的开发方式,动态语言大有用武之地(Python、Ruby都是不错的选择,在www.martinfowler.com/bliki中,Martin Fowler对动态语言和DSL的关系进行了深入、有趣的描述)。
自下而上的做法实际上是在改变、扩充开发语言,使其适合于所面临的问题领域。当你进行软件系统的开发时,你不仅仅只是把你所构思的程序直接映射到实现语言,同时你还不断对语言进行增强,使其更加贴近你所构思的程序,并且表达起来能够更加简单、更加直接。比如:你会想语言中如果有了这个操作符,表达起来就更加清楚、简洁了,那么你就去构建它。这样,语言和程序就会一同演化,直到二者能够形成一种完美的匹配关系。最后,你的程序看起来就会像是用专门为它设计的语言开发的。而当语言和程序能够很好地相互适合时,所编写的代码也就会更清晰、更少、更有效。
值得注意的是,自下而上设计相对于自上而下设计来说,并不意味着用不同的顺序来编写同样的程序。当采用自下而上设计时,常常会得到一个和自上而下设计不同的程序。所得到的不会是一个单一的单片机(monolithic)程序,而是一个具有更多抽象操作符的更“大”的语言和一个用该语言编写的更“小”的程序。此外,这个语言的抽象操作符也很容易在其他的类似的程序中得以重用,所编写程序也更加易读、更加易于理解。
还有一点值得提出,那就是自下而上的开发方法可以尽快地得到来自代码的反馈,可以及时进行重构。我们大家可能都已经知道,设计模式一般是重构的目标,这里我想特别指出的是:DSL往往也是很好的重构目标。
抽象、库和DSL
C++之父Bjarne Stroustrup经常强调的“用库来扩充语言,用库来进行思考”( http://www.artima.com/intv/elegance.html有近期对Bjarne Stroustrup的采访,他再次强调了这个问题),其实就是在强调DSL以及自下向上开发的重要性。库就是DSL的一种形式,Bjarne Stroustrup所列举的科学计算库的例子就是科学计算领域的DSL。构建库的过程其实就是在朝着更加贴近问题领域抽象的方向迈进。明白了这一点,我们就不难理解Bjarne Stroustrup一直强调的泛型在构建一个优雅、高效库的方面的重要性了,因为泛型为构建这种抽象提供了一个坚实的基础。
不仅C++如此,其他一些语言(比如:Java)也拥有众多的库。这些库在我们进行软件开发时,为我们提供了强大的支持。不过,这些库往往都只实现了它所针对领域的一些通用基础性的问题。因此,在某些特定问题上,可能无法直接地使用这些库来进行表达。此时,你就应该考虑建立一个特定于自己问题的特定库了。
结论
作为开发者的我们,该如何做呢?正如本文开始所说的,抽象固然很好,但是要找出适合于自己手边问题的抽象是一件很困难的事。它应该是一个不断从代码编写中得到反馈,然后再修正,接着再编写代码的循环反馈的过程,这也是我为何认为自下而上开发更为有效的原因。我们不应该局限于仅仅会使用某一种工具,某一种开发环境,而应该多想想这些工具、开发环境背后蕴涵的思想。我们应该注重于培养自己发掘抽象的能力,这方面能力的培养既需要很好的和客户沟通的能力,同时还需要有坚实、高超的软件能力。
此外,近期热的不能再热的技术MDA,其核心思想其实就是DSL,不过我对于其试图使用一种语言来刻画所有领域模型的做法并不看好。与其使用它,倒不如逐步演化出一个特定于自己问题领域的Mini DSL和一个DSL编译/解释器,并基于这个DSL来进行开发,这样或许更为直接一些,更为轻便一些,更为清晰一些、更为有效一些,更加具有针对性一些,也更为经济一些J
参考文献
[1] Eric Steven Raymond, The Art of Unix Programming
[2] Eric Evans, Domain-Driven Design: Tackling Complexity in the Heart of Business Software
[3] Andrew Hunt, David Thomas, The Pragmatic Programmer: From Journeyman to Master
[4] Grady Booch, Object-Oriented Analysis and Design with Application