2.3DSL的问题
前面已经讨论了何时该采用DSL,接下来就该谈论什么时候不该采用DSL,或者至少是使用DSL应注意的问题。
从根本上说,不使用DSL的唯一原因就是,在你的场景下,使用DSL得不到任何好处,或者,至少是DSL的好处不足以抵消构建它的成本。
虽然DSL在有些场合下适用,但同样会带来一些问题。总的来说,我认为通常是高估了这些问题,一般人们不太熟悉如何构造DSL,以及DSL如何适应更为广阔的软件开发图景。还有,许多常提及的DSL问题混淆了DSL和模型,这也伤及了DSL的优势。
许多DSL问题只是与某种特定DSL风格相关,要理解这些问题,我们需要深入理解这些DSL是如何实现的。所以,这些问题留待后面讨论,在这里,我们只看宽泛的问题,这同当前讨论的问题是一致的。
2.3.1语言噪音
在反对DSL的观点中,我最常听到的是称为语言噪音的问题:担心语言难于学习,因此,使用多种语言会比使用一种语言复杂得多。必须了解多种语言,会让工作更为困难,新人加入的门槛也提升了。
当人们谈及这种担心时,他们都会有一些共同的误解。首先,他们通常混淆了学习一门DSL的心血与学习一门通用语言的心血。DSL远比一门通用语言容易,因此,学习起来也要容易得多。
许多批评者知道这一点,但依然反对DSL,即便它们相对容易学习,在一个项目上有多种DSL也增加了理解的难度。这里的误解在于,他们忘了一点,一个项目总有一些复杂的地方,难于学习。即便不用DSL,代码库中仍然有许多需要理解的抽象。通常,这些抽象应该在程序库里,以便于掌握。即使不必学习多种DSL,也不得不学习多个程序库。
所以,真正的问题在于,相比于学习DSL底层模型而言,学习DSL会难多少。我认为,相对于理解模型而言,学习DSL 所增加的成本相当小。确实,因为DSL的的价值就在于,让人们理解和使用模型更容易,所以使用DSL就应该能降低学习成本。
2.3.2构建成本
相对于底层的程序库而言,DSL增加的成本并不大,但这始终是成本。代码需要写,尤其是还要维护。所以,同其他代码一样,它也要做好自己的本职工作。并非所有程序库都值得用DSL封装。如果命令–查询API够用,就没有必要在上面提供额外的API。即便DSL有用,就边界效应而言,构建和维护也需要花费太多的工作量。
DSL的可维护性是一项重要的考量因素。如果团队中的大多数人都觉得难以理解,即使是一种简单的内部DSL,也会带来很大的麻烦。外部DSL更是让许多人望而却步,一个解释器就足以让很多程序员打退堂鼓。
人们不习惯构建DSL,这也让添加DSL的成本变得更高。人们要学习新技术。虽然不应该忽略这些成本,但我们也应该清楚,这个学习曲线的成本能够分摊到未来使用DSL的过程中。
还有一点要清楚,DSL的成本大于构建模型的成本。任何复杂的地方都需要某种机制管理其复杂性,如果复杂到要考虑DSL,几乎肯定复杂到可以从模型中获益的程度。DSL有助于思考模型,降低构建成本。
这会带来一个相关问题,鼓励使用DSL会导致构建出一堆糟糕的DSL。实际上,我盼着构建出一堆糟糕的DSL,就像有很多糟糕的命令–查询API的程序库一样。问题在于,DSL会不会把事情弄得更糟。一个好的DSL可以封装一个糟糕的程序库,把它变得更易用(如果可能的话,我更愿意修正程序库本身)。糟糕的DSL对于构建和维护而言,就是浪费 资源,但这种说法对任何代码都适用。
2.3.3集中营语言
集中营语言(ghetto language)问题与语言噪音问题正好相反。比如,一家公司用一种内部语言编写公司内的很多系统,这种语言在其他地方根本用不上。这种做法会让他们很难找到新人,跟上技术变化。
在分析这个问题时,首先要澄清一点,如果整个系统都是用一种语言编写的,那它就不是一种DSL(至少按我的定义),而是一种通用语言。虽然可以用许多DSL技术构建通用语言,但我强烈建议,不要这样做。构建和维护一种通用语言是一个巨大的负担,它会迫使你在这个集中营中做大量工作,甚至挣扎一生。不要这么做。
我相信,集中营语言问题并非空穴来风,它隐含了一些现实问题。首先是,一种DSL总是存在着无意中演化成一种通用语言的危险。我们有一种DSL,然后,逐步为它添加新功能;今天添加条件表达式,明天又添加循环,最终图灵完备了。
对此,唯一的抵御就是坚决防范。确保我们对DSL针对问题的受限范围有个清晰的认识。质疑任何不在此范畴内的新特性。如果想做得更多,可以考虑采用多种语言,综合运用,而非强求一种DSL不断膨胀。
框架也面临着同样的问题。好的程序库都有一个明确的目的。如果产品定价库包含HTTP协议的实现,从本质上说,我们也就要忍受同样错误之苦:未能分离关注点。
第二个问题是,自行构造本应从外部获得的东西。这个问题同样适用于DSL和程序库。比如,如今,很少有要自己构造对象–关系映射(object–relational mapping)系统。我有一条关于软件的通用规则,不是自己的业务,不要自己写─总要先看看是否从别的地方可以找到。特别是,随着开源工具的崛起,基于既有开源工作量进行扩展,肯定比从头打造更有意义。
2.3.4 “一叶障目”的抽象
DSL的有用之处在于,它提供了一种抽象,我们可以基于这种抽象来思考领域问题。这种抽象非常有价值,我们更容易表述领域行为,效果远胜于依据底层构造进行思考。
然而,任何抽象(包括DSL和程序库)总是伴随着风险─它可能让我们“一叶障目,不见泰山”。有了这种“一 叶障目”的抽象,我们就会苦苦思索,竭尽全力把外部世界塞入抽象之中,而非另寻它路。我们常常会见到这种情况:遇到一种不符合抽象的事物,殚精竭虑地让其符合,而不是修改抽象,让抽象更容易接纳新的行为。一旦我们满意了这个抽象,觉得尘埃落定,“一叶障目”也就随之而来。到这种时候,对于颠覆性的变化,难免心生忧虑。
“一叶障目”是任何抽象都会面临的问题,不仅是DSL,但DSL可能让这个问题变得更严重。因为DSL提供了一种更为舒适的方式操作抽象,一旦适应,更不愿意做出改变。如果采用DSL与领域专家交流,问题可能会更严重,通常,他 们在习惯之后更不愿意做出改变。
如同对待任何抽象一样,应该视DSL为一种“不断演化,尚未完结”的事物。