领域语言的定义
领域模型可成为软件项目通用语言的核心。该模型是一组得自于项目人员头脑中的概念,以及反映了领域深层含义的术语和关系。这些术语和相互关系提供了模型语言的语义,虽然语言是为领域量身定制的,但就技术开发而言,其依然足够精确。正是这条至关重要的纽带,将模型与开发活动结合在一起,并使模型与代码紧密绑定。
由于语言上存在鸿沟,领域专家们只能模糊地描述他们想要的东西。开发人员虽然努力去理解一个自己不熟悉的领域,但也只能形成模糊的认识。虽然少数团队成员会设法掌握这两种语言,但他们会变成信息流的瓶颈,并且他们的翻译也不准确。在一个没有公共语言的项目上,开发人员不得不为领域专家做翻译。而领域专家需要充当开发人员与其他领域专家之间的翻译。甚至开发人员之间还需要互相翻译。这些翻译使模型概念变得混淆,而这会导致有害的代码重构。
所有翻译的开销,连带着误解的风险,成本实在太高了。项目需要一种公共语言,这种语言要比所有语言的最小公分母健壮得多。通过团队的一致努力,领域模型可以成为这种公共语言的核心,同时将团队沟通与软件实现紧密联系到一起。
UBIQUITOUS LANGUAGE(通用语言)的词汇包括类和主要操作的名称。语言中的术语,有些用来讨论模型中已经明确的规则,还有一些则来自施加于模型上的高级组织原则最后,团队常常应用于领域模型的模式名称也使这种语言更为丰富。
模型之间的关系成为所有语言都具有的组合规则。词和短语的意义反映了模型的语义。开发人员应该使用基于模型的语言来描述系统中的工件、任务和功能。这个模型应该为开发人员和领域专家提供一种用于相互交流的语言,而且领域专家还应该使用这种语言来讨论需求、开发计划和特性。语言使用得越普遍,理解进行得就越顺畅。
将模型作为语言的支柱。确保团队在内部的所有交流中以及代码中坚持使用这种语言。在画图、写东西,特别是讲话时也要使用这种语言。
通过尝试不同的表示方法(它们反映了备选模型)来消除难点。然后重构代码,重新命名类、方法和模块,以便与新模型保持一致。解决交谈中的术语混淆问题,就像我们对普通词汇形成一致的理解一样。
要认识到,UBIQUITOUS LANGUAGE的更改就是对模型的更改。领域专家应该抵制不合适或无法充分表达领域理解的术语或结构,开发人员应该密切关注那些将会妨碍设计的有歧义和不一致的地方。
UBIQUITOUS LANGUAGE是那些以非代码形式呈现的设计的主要载体,这些包括把整个系统组织在一起的大尺度结构、定义了不同系统和模型之间关系的限界上下文,以及在模型和设计中使用的其他模式。
区分是否使用领域语言的差别?
下面这两段对话有着微妙但重要的差别:
场景2:用领域模型进行讨论
二、通过交流建模
改善模型的最佳方式之一就是通过对话来研究,试着大声说出可能的模型变化中的各种结构。这样不完善的地方很容易被听出来。
“如果我们向Routing Service提供出发地、目的地和到达时间,就可以查询货物的停靠地点,嗯……将它们存到数据库中.含糊且偏重于技术)“出发地、目的地……把它们都输入到Routing Service中,而后我们得到一个Itinerary,它包含我们所需的全部信息。”(更具体,但过于啰嗦) “Routing Service查找满足Route Specification Itinerary。”(简洁)
其将我们的语言能力用于建模工作,这就如同素描对于表现视觉和空间推理十分重要一样。我们即要利用系统性分析和设计方面的分析能力,也要利用对代码的神秘“感觉”。这些思考方式互为补充,要充分利用它们来找到有用的模型和设计。在所有这些方式中,语言上的试验常常是最容易被忽视的。
当我们在讨论中使用领域模型的UBIQUITOUS LANGUAGE时,特别是在开发人员和领域专家一起推敲场景和需求时,通用语言的使用会越来越流利,而且我们还可以互相指点一些细微的差别。我们自然而然地共享了我们所说的语言,而这种方式是图和文档无法做到的。
想要在软件项目上产生一种UBIQUITOUS LANGUAGE,说起来容易,做起来却难,我们必须充分利用自然赋予我们的才能来实现这个目标。正如人类的视觉和空间思维能力使我们能够快速传达和处理图形概述中的信息一样,我们也可以利用自己在基于语法的、有意义的语言方面的天赋来推动模型的开发。
讨论系统时要结合模型。使用模型元素及其交互来大声描述场景,并且按照模型允许的方式将各种概念结合到一起。找到更简单的表达方式来讲出你要讲的话,然后将这些新的想法应用到图和代码中。
保证一个团队只有一种语言来沟通
技术人员通常认为业务专家最好不要接触领域模型,他们认为:
“领域模型对他们来说太抽象了。”
“他们不理解对象。”
“这样我们就不得不用他们的术语来收集需求。”
上面只列举了我从一个使用两种语言的团队中听到的少数几个原因。忘掉它们吧。当然,设计中有一些技术组件与领域专家无关,但模型的核心最好让他们参与。过于抽象?那你怎么知道抽象是否合理?你是否像他们一样深入理解领域?有时,某些特定需求是从底层用户那里收集的,他们在描述这些需求时可能会用到一小部分更具体的术语,但领域专家应该能够更深入地思考他们所从事的领域。如果连经验丰富的领域专家都不能理解模型,那么模型一定出了什么问题。
最初,当用户讨论系统尚未建模的未来功能时,他们没有模型可供使用。但当他们开始与开发人员一起仔细讨论这些新想法时,探索共享模型的过程就开始了。最初的模型可能很笨拙且不完整,但会逐渐精化。随着新语言的演进,领域专家必须付出更多努力来适应它,并更新那些仍然很重要的旧文档。
当领域专家使用这种语言互相讨论,或者与开发人员进行讨论时,很快就会发现模型中哪些地方不符合他们的需要,甚至是错误的。另一方面,模型语言的精确性也会促使领域专家发现他们想法中的矛盾和含糊之处。
开发人员和领域专家可以通过一步一步地使用模型对象来走查场景,从而对模型进行非正式的测试。每次讨论都是开发人员和专家一起使用模型的机会,在这个过程中,他们可以加深彼此的理解,并对概念进行精化。领域专家可以使用模型语言来编写用例,甚至可以直接利用模型来具体说明验收测试。
语言的多样性通常是必要的,但领域专家与开发人员之间不应该有语言上的分歧.
当然,开发人员的确会使用领域专家无法理解的技术术语。开发人员有其所需的大量术语来讨论系统技术。几乎可以肯定的是,用户也会用开发人员无法理解的、超出应用程序范畴的专用术语。这些都是对语言的扩展。但在这些语言扩展中,同一领域的相同词汇不应该反映不同的模型。
四、文档与图
文档与书面设计
简单、非正式的UML图能够维系整个讨论。绘制一幅包含当前问题最关键的3~5个对象的图,这样每个人都可以集中注意力。所有人就对象关系会达成一致的认识,更重要的是,他们将使用相同的对象名称。如此,口头讨论会更加高效。当人们尝试不同的想法时,图也随之改变,草图在某种程度上可以反映讨论的变化,这是讨论中真正重要的部分。毕竟,UML就是统一建模语言。
当人们必须通过UML图表示整个模型或设计时,麻烦也随之而来。很多对象模型图在某些方面过于细致,同时在某些方面又有很多遗漏。说它们过于细致是因为人们认为必须将所有要编码的对象都放到建模工具中。而细节过多的结果是“只见树木,不见森林”。
尽管存在所有这些细节,但属性和关系只是对象模型的一部分。这些对象的行为以及这些对象上的约束就不那么容易表示了。对象交互图可以阐明设计中的一些复杂之处,但却无法用这种方式来展示大量的交互,就是工作量太大了,既要制作图,还要学习这些图。而且交互图也只能暗示出模型的目的。要想把约束和断言包括进来,需要在UML图中使用文本,这些文本用括号括起来,插入到图中。
操作名称可能会暗示出对象的行为职责,对象交互图(或序列图)中也会隐含地展示出这些职责,但无法直接表述。因此,这项任务就要靠补充文本或对话来完成。换言之,UML图无法传达模型的两个最重要的方面,一个方面是模型所表示的概念的意义,另一方面是对象应该做哪些事情。但是,这并不是大问题,因为通过仔细地使用语言就可以很好地完成这项任务。
图是一种沟通和解释手段,它们可以促进头脑风暴。简洁的小图能够很好地实现这些目标,而涵盖整个对象模型的综合性大图反而失去了沟通或解释能力,因为它们将读者淹没在大量细节之中,加之这些图也缺乏目的性。鉴于此,我们应避免使用包罗万象的对象模型图,甚至不能使用包含所有细节的UML数据存储库。相反,应使用简化的图,图中只包含对象模型的重要概念。
设计的重要细节应该在代码中体现出来。良好的实现应该是透明的,清楚地展示其背后的模型互为补充的图和文档能够引导人们将注意力放在核心要点上。自然语言的讨论可以填补含义上的细微差别。
通常的用法是以图为主,辅以文本注释;而作者更愿意以文本为主,用精心挑选的简化图作为说明。
务必要记住模型不是图。图的目的是帮助表达和解释模型。代码可以充当设计细节的存储库。
书写良好的Java代码与UML具有同样的表达能力。经过仔细选择和构造的图可以帮助人们集中注意力,并起到指导作用,当然前提条件是不能强制用图来表示全部模型或设计,因为这样会削弱图的清晰表达的能力。
一、书面设计文档
虽然交谈对于将人们与模型联系起来是至关重要的,但书面文档也是必不可少的,任何规模的团队都需要它来提供稳定和共享的交流。
但要想编写出能够帮助团队开发出好软件的书面文档却是一个不小的挑战。一旦文档的形式变得一成不变,往往会从项目进展流程中脱离出来。它会跟不上代码或项目语言的演变。
书面文档有很多编写方法。本书第四部分将介绍几种满足特定需要的具体文档,但不会列出项目需要使用的所有文档,而是给出两条用于评估文档的总体原则:
1、文档应作为代码和口头交流的补充。
每种敏捷过程在编写文档方面都有自己的理念。极限编程主张完全不使用(多余的)设计文档,而让代码解释自己。实际运行的代码不会说谎,而其他文档则不然。运行代码所产生的行为是明确的。
极限编程只关注对程序及可执行测试起作用的因素。由于为代码添加的注释并不影响程序的行为,因此它们往往无法与当前代码及其模型保持同步。外部文档和图也不会影响程序的行为,因此它们也无法保持同步。另一方面,口头交流和临时在白板上画的图不会长久保留而产生混淆。依赖代码作为交流媒介可以促使开发人员保持代码的整洁和透明。
然而,将代码作为设计文档也有局限性。它可能会把读代码的人淹没在细节中。尽管代码的行为是非常明确的,但这并不意味着其行为是显而易见的。而且行为背后的意义可能难以表达。
换言之,只用代码做文档与使用大而全的UML图面临着差不多相同的基本问题。当然,团队进行大量的口头交流能够为代码提供上下文和指导,但是,口头交流很短暂,而且范围很小。此外,开发人员并不是唯一需要理解模型的人。
其他文档应该着重说明含义,以便使人们能够深入理解大尺度结构,并将注意力集中在核心元素上。当编程语言无法直接明了地实现概念时,文档可以澄清设计意图。我们应该把书面文档作为代码和口头讨论的补充。
2、文档应当鲜活并保持最新
UBIQUITOUS LANGUAGE可以使其他文档(如需求规格说明)更简洁和明确。当领域模型反映了与业务最相关的知识时,应用程序的需求成为该模型内部的场景,而UBIQUITOUS LANGUAGE可直接用MODEL-DRIVEN DESIGN(模型驱动设计)的方式描述此类场景.
结果就是规格说明的编写更简单,因为它们不必传达模型背后隐含的业务知识。通过将文档减至最少,并且主要用它来补充代码和口头交流,就可以避免文档与项目脱节。根据UBIQUITOUS LANGUAGE及其演变来选择那些需要保持更新并与项目活动紧密交互的文档。
二、完全依赖可执行代码的情况
现在,我们来考查一下XP社区和其他一些人为何选择几乎完全依赖可执行代码及其测试。
良好的代码具有很强的表达能力,但它所传递的信息不能确保是准确的。一段代码所产生的实际行为是不会改变的。但是,方法名称可能会有歧义、会产生误导或者因为已经过时而无法表示方法的本质含义。
测试中的断言是严格的,但变量和代码组织方式所表达出来的意思未必严格。好的编程风格会尽力使这种联系直接化,但其仍然主要靠开发人员的自律。编码时需要一丝不苟的态度,只有这样才能编写出“言行全部正确”的代码。
消除这些差异是诸如声明式设计(参见第10章)这样的方法的最大优点,在这类方法中,程序元素用途的陈述决定了它在程序中的实际行为。从UML生成程序的部分动机就来源于此,虽然目前看来这通常不会得到好的结果。
尽管代码可能会产生误导,但它仍然比其他文档更基础。要想利用当前的标准技术使代码所传达的消息与它的行为和意图保持一致,需要纪律和思考设计的特定方式。要有效地交流,代码必须基于在编写需求时所使用的同一种语言,也就是开发人员之间、开发人员与领域专家之间进行讨论时所使用的语言。
五、解释性模型
使用其他模型的一个特殊原因是范围。驱动软件开发过程的技术模型必须经过严格的精简,以便用最小化的模型来实现其功能。而解释性模型则可以包含那些提供上下文的领域方面——这些上下文用于澄清范围更窄的模型。
解释性模型提供了一定的自由度,可以专门为某个特殊主题定制一些表达力更强的风格。领域专家在一个领域中所使用的视觉隐喻通常呈现了更清晰的解释,这可以教给开发人员领域知识,同时使领域专家们的意见更一致。
解释性模型还可以以一种不同的方式来呈现领域,并且各种不同角度的解释有助于人们更好地学习。解释性模型不必是对象模型,而且最好不是。实际上在这些模型中不使用UML是有好处的,这样可以避免人们错误地认为这些模型与软件设计是一致的。尽管解释性模型与驱动设计的模型往往有对应关系,但它们并不完全类似。为了避免混淆,每个人都必须知道它们之间的区别。
考虑一个用来追踪航运公司货物的应用程序。模型包含一个详细的视图,它显示了如何将港口装卸和货轮航次组合为一次货运的操作计划(“路线”)。如图2-4所示。但对外行而言,类图可能起不到多大的说明作用。在这种情况下,解释性模型可以帮助团队成员理解类图的实际含义。图2-5是表示相同概念的另一种方式。
图中的每根线段都表示货物的一种状态——或者正在港口装卸(装货或卸货),或者停放在仓库里,或者正在运输途中。这个图并没有与类图中的细节一一对应,但强调了领域的要点。
这种图连同对它所表示的模型的自然语言解释,能够帮助开发人员和领域专家理解更严格的软件模型图。综合使用这两种图要比单独使用一种图更容易理解。