第2章 交流与语言的使用

领域模型可成为软件项目通用语言的核心。该模型是一组得自于项目人员头脑中的概念,以及反映了领域深层含义的术语和关系。这些术语和相互关系提供了模型语言的语义,虽然语言是为领域量身定制的,但就技术开发而言,其依然足够精确。正是这条至关重要的纽带,将模型与开发活动结合在一起,并使模型与代码紧密绑定。

这种基于模型的交流并不局限于UML(统一建模语言)图。为了最有效地使用模型,需要充分利用各种交流手段。基于模型的交流提高了书面文档的效用,也提高了敏捷过程中再度强调的非正式图表和交谈的效用。它还通过代码本身及对应的测试促进了交流。

在项目中,语言的使用很微妙,但却至关重要……

2.1 模式:UBIQUITOUS LANGUAGE

首先写下一个句子,

然后将它分成小段,

再将它们打乱并重新排序。

仿佛是巧合一样,

短语的顺序对意思完全没有影响。

——Lewis Carroll,“Poeta Fit,Non Nascitur”

要想创建一种灵活的、蕴含丰富知识的设计,需要一种通用的、共享的团队语言,以及对语言不断的试验——然而,软件项目上很少出现这样的试验。

虽然领域专家对软件开发的技术术语所知有限,但他们能熟练使用自己领域的术语——可能还具有各种不同的风格。另一方面,开发人员可能会用一些描述性的、功能性的术语来理解和讨论系统,而这些术语并不具备领域专家的语言所要传达的意思。或者,开发人员可能会创建一些用于支持设计的抽象,但领域专家无法理解这些抽象。负责处理问题不同部分的开发人员可能会开发出各自不同的设计概念以及描述领域的方式。

由于语言上存在鸿沟,领域专家们只能模糊地描述他们想要的东西。开发人员虽然努力去理解一个自己不熟悉的领域,但也只能形成模糊的认识。虽然少数团队成员会设法掌握这两种语言,但他们会变成信息流的瓶颈,并且他们的翻译也不准确。

在一个没有公共语言的项目上,开发人员不得不为领域专家做翻译。而领域专家需要充当开发人员与其他领域专家之间的翻译。甚至开发人员之间还需要互相翻译。这些翻译使模型概念变得混淆,而这会导致有害的代码重构。这种间接的沟通掩盖了分裂的形成——不同的团队成员使用不同的术语而尚不自知。由于软件的各个部分不能够浑然一体,因此这就导致无法开发出可靠的软件(参见第14章)。翻译工作导致各类促进深入理解模型的知识和想法无法结合到一起。

如果语言支离破碎,项目必将遭遇严重问题。领域专家使用他们自己的术语,而技术团队所使用的语言则经过调整,以便从设计角度讨论领域。

日常讨论所使用的术语与代码(软件项目的最重要产品)中使用的术语不一致。甚至同一个人在讲话和写东西时使用的语言也不一致,这导致的后果是,对领域的深刻表述常常稍纵即逝,根本无法记录到代码或文档中。

翻译使得沟通不畅,并削弱了知识消化。

然而任何一方的语言都不能成为公共语言,因为它们无法满足所有的需求。

所有翻译的开销,连带着误解的风险,成本实在太高了。项目需要一种公共语言,这种语言要比所有语言的最小公分母健壮得多。通过团队的一致努力,领域模型可以成为这种公共语言的核心,同时将团队沟通与软件实现紧密联系到一起。该语言将存在于团队工作中的方方面面。

UBIQUITOUS LANGUAGE(通用语言)的词汇包括类和主要操作的名称。语言中的术语,有些用来讨论模型中已经明确的规则,还有一些则来自施加于模型上的高级组织原则(如第14章和第16章要讨论的CONTEXT MAP和大型结构)。最后,团队常常应用于领域模型的模式名称也使这种语言更为丰富。

模型之间的关系成为所有语言都具有的组合规则。词和短语的意义反映了模型的语义。

开发人员应该使用基于模型的语言来描述系统中的工件、任务和功能。这个模型应该为开发人员和领域专家提供一种用于相互交流的语言,而且领域专家还应该使用这种语言来讨论需求、开发计划和特性。语言使用得越普遍,理解进行得就越顺畅。

至少,我们应该将它作为目标。但最初,模型可能不太好,因此无法很好地履行这些职责。它可能不会像领域的专业术语那样具有丰富的语义。但我们又不能直接使用那些术语,因为它们有歧义和矛盾。模型可能缺乏开发人员在代码中所创建的更为微妙和灵活的特性,这要么是因为开发人员认为模型不必具备这些特性,要么是因为编码风格是过程式的,只能隐含地表达领域概念。

尽管模型和基于模型的语言之间的次序像是循环论证,但是,能够产生更有用模型的知识消化过程依赖于团队投身于基于模型的语言。持续使用UBIQUITOUS LANGUAGE可以暴露模型中存在的缺点,这样团队就可以尝试并替换不恰当的术语或组合。当在语言中发现缺失时,新的词语将被引入到讨论中。这些语言上的更改也会在领域模型中引起相应的更改,并促使团队更新类图并重命名代码中的类和方法,当术语的意义改变时,甚至会导致行为也发生改变。

通过在实现的过程中使用这种语言,开发人员能够指出不准确和矛盾之处,并和领域专家一起找到有效的替代方案。

当然,为了解释和给出更广泛的上下文,领域专家的语言会超出UBIQUITOUS LANGUAGE的范围。但在模型应对的范围内,他们应该使用UBIQUITOUS LANGUAGE,并在发现不合适、不完整或错误之处后要引起注意。通过大量使用基于模型的语言,并且不达流畅绝不罢休,我们可以逐步得到一个完整的、易于理解的模型,它由简单元素组成,并通过组合这些简单元素表达复杂的概念。

因此:

将模型作为语言的支柱。确保团队在内部的所有交流中以及代码中坚持使用这种语言。在画图、写东西,特别是讲话时也要使用这种语言。

通过尝试不同的表示方法(它们反映了备选模型)来消除难点。然后重构代码,重新命名类、方法和模块,以便与新模型保持一致。解决交谈中的术语混淆问题,就像我们对普通词汇形成一致的理解一样。

要认识到,UBIQUITOUS LANGUAGE的更改就是对模型的更改。

领域专家应该抵制不合适或无法充分表达领域理解的术语或结构,开发人员应该密切关注那些将会妨碍设计的有歧义和不一致的地方。

有了UBIQUITOUS LANGUAGE,模型就不仅仅是一个设计工件了。它成为开发人员和领域专家共同完成的每项工作中不可或缺的部分。语言以动态形式传递知识。使用这种语言进行讨论能够呈现图和代码背后的真实含义。

我们在这里讨论的UBIQUITOUS LANGUAGE假设只有一个模型在起作用。第14章将讨论不同模型(和语言)的共存,以及如何防止模型分裂。

UBIQUITOUS LANGUAGE是那些以非代码形式呈现的设计的主要载体,这些包括把整个系统组织在一起的大尺度结构(参见第16章)、定义了不同系统和模型之间关系的限界上下文(参见第14章),以及在模型和设计中使用的其他模式。

示例 制定货运路线

下面这两段对话有着微妙但重要的差别。在每个对话场景中,注意观察讲话者有多少内容是谈论软件的业务功能,有多少内容是从技术上谈论软件的工作机理的。用户和开发人员用的是同一种语言吗?它的表达是否丰富,足以应对应用程序功能的讨论?

场景1:最小化的领域抽象

第2章 交流与语言的使用 - 图1 图2-1

用户:那么,当更改清关(customs clearance)[4]地点时,需要重新制定整个路线计划啰。

开发人员:是的。我们将从货运表(shipment table)中删除所有与该货物id相关联的行,然后将出发地、目的地和新的清关地点传递给Routing Service,它会重新填充货运表。Cargo中必须设立一个布尔值,用于指示货运表中是否有数据。

用户:删除行?好,就按你说的做。但是,如果先前根本没有指定清关地点,也需要这么做吗?

开发人员:是的,无论何时更改了出发地、目的地或清关地点(或是第一次输入),都将检查是否已经有货运数据,如果有,则删除它们,然后由Routing Service重新生成数据。

用户:当然,如果原有的清关数据碰巧是正确的,我们就不需要这样做了。

开发人员:哦,没问题。但让Routing Service每次重新加载或卸载数据会更容易些。

用户:是的,但为新航线制定所有支持计划的工作量很大,因此,除非非改不可,我们一般不想更改航线。

开发人员:哦,好的,当第一次输入清关地点时,我们需要查询表格,找到以前的清关地点,然后与新的清关地点进行比较,从而判断是否需要重做。

用户:这个处理不必考虑出发地和目的地,因为航线在此总要变更。

开发人员:好的,我明白了。

场景2:用领域模型进行讨论

第2章 交流与语言的使用 - 图2 图2-2

用户:那么,当更改清关地点时,需要重新制定整个路线计划啰。

开发人员:是的。当更改Route Specification(路线说明)的任意属性时,都将删除原有的Itinerary(航线),并要求Routing Service(路线服务)基于新的Route Specification生成一个新的Itinerary。

用户:如果先前根本没有指定清关地点,也需要这么做吗?

开发人员:是的,无论何时更改了Route Spec的任何属性,都将重新生成Itinerary。这也包括第一次输入某些属性。

用户:当然,如果原有的清关数据碰巧是正确的,我们就不需要这样做了

开发人员:哦,没问题。但让Routing Service每次重新生成一个Itinerary会更容易些。

用户:是的,但为新航线制定所有支持计划的工作量很大,因此,除非非改不可,我们一般不想更改路线。

开发人员:哦。那么需要在Route Specification添加一些功能。这样,当更改Route Specification中的属性时,查看Itinerary是否仍满足Specification。如果不满足,则需要由Routing Service重新生成Itinerary。

用户:这一点不必考虑出发地和目的地,因为Itinerary在此总是要变更的。

开发人员:好的,但每次只做比较就简单多了。只有当不满足Route Specification时,才重新生成Itinerary。

第二段对话表达了领域专家的更多意图。在这两段对话中,用户都使用了“itinerary”这个词,但在第二段中它是一个对象,这使得双方可以更准确、具体地进行讨论。他们明确讨论了“route specification”,而不是每次都通过属性和过程来描述它。

这两段对话有意使用了相似的结构。实际上,第一段对话显得更啰嗦,对话双方需要不断对应用程序的特性和表达不清的地方进行解释。第二段对话使用了基于领域模型的术语,因此讨论更简洁。

2.2 “大声地”建模

假如将交谈从沟通方式中除去的话,那会是巨大的损失,因为人类本身颇具谈话的天赋。遗憾的是,当人们交谈时,通常并不使用领域模型的语言。

可能开始时你并不认为上述论断是正确的,而且的确有例外情况。但下次你参加需求或设计讨论时,不妨认真听一下。你将听到人们用业务术语或者各种业余术语来描述功能。还会听到人们讨论技术工件和具体的功能。当然,你还会听到来自领域模型的术语;在人们共同使用的那部分业务术语中,那些显而易见的名词在编码时通常被用作对象名称,因此这些术语经常被人们提及。但你是否也听到一些使用当前领域模型中的关系和交互来描述的措辞呢?

改善模型的最佳方式之一就是通过对话来研究,试着大声说出可能的模型变化中的各种结构。这样不完善的地方很容易被听出来。

“如果我们向Routing Service提供出发地、目的地和到达时间,就可以查询货物的停靠地点,嗯……将它们存到数据库中。”(含糊且偏重于技术)

“出发地、目的地……把它们都输入到Routing Service中,而后我们得到一个Itinerary,它包含我们所需的全部信息。”(更具体,但过于啰嗦)

“Routing Service查找满足Route Specification的Itinerary。”(简洁)

使用单词和短语是极为重要的——其将我们的语言能力用于建模工作,这就如同素描对于表现视觉和空间推理十分重要一样。我们即要利用系统性分析和设计方面的分析能力,也要利用对代码的神秘“感觉”。这些思考方式互为补充,要充分利用它们来找到有用的模型和设计。在所有这些方式中,语言上的试验常常是最容易被忽视的(本书第三部分将深入探讨这种发现过程,并通过几段对话来显示它们之间的相互影响)。

事实上,我们的大脑似乎很擅长处理口语的复杂性(对于像我这样的门外汉,有本好书是Steven Pinker所著的The Language Instinct[Pinker 1994])。例如,当具有不同语言背景的人凑在一起做生意时,如果没有公共语言,他们就会创造一种称为“混杂语”(pidgin)的公共语言。混杂语虽然不像讲话者的母语那样详尽,但它适合当前任务。当人们交谈时,自然会发现词语解释和意义上的差别,而后会自然而然地解决这些差别。他们会发现这种语言中的简陋晦涩之处并把它们搞顺畅。

上大学时,我曾经修过西班牙语速成课。课堂上规定不准讲英语。起初,令人相当沮丧。这不仅感觉很别扭,而且需要很强的自制力。但最终我和同学们都达到了通过书面练习永远不可能达到的流利程度。

当我们在讨论中使用领域模型的UBIQUITOUS LANGUAGE时,特别是在开发人员和领域专家一起推敲场景和需求时,通用语言的使用会越来越流利,而且我们还可以互相指点一些细微的差别。我们自然而然地共享了我们所说的语言,而这种方式是图和文档无法做到的。

想要在软件项目上产生一种UBIQUITOUS LANGUAGE,说起来容易,做起来却难,我们必须充分利用自然赋予我们的才能来实现这个目标。正如人类的视觉和空间思维能力使我们能够快速传达和处理图形概述中的信息一样,我们也可以利用自己在基于语法的、有意义的语言方面的天赋来推动模型的开发。

因此,下面这段话可作为UBIQUITOUS LANGUAGE模式的补充:

讨论系统时要结合模型。使用模型元素及其交互来大声描述场景,并且按照模型允许的方式将各种概念结合到一起。找到更简单的表达方式来讲出你要讲的话,然后将这些新的想法应用到图和代码中。

2.3 一个团队,一种语言

技术人员通常认为业务专家最好不要接触领域模型,他们认为:

“领域模型对他们来说太抽象了。”

“他们不理解对象。”

“这样我们就不得不用他们的术语来收集需求。”

上面只列举了我从一个使用两种语言的团队中听到的少数几个原因。忘掉它们吧。

当然,设计中有一些技术组件与领域专家无关,但模型的核心最好让他们参与。过于抽象?那你怎么知道抽象是否合理?你是否像他们一样深入理解领域?有时,某些特定需求是从底层用户那里收集的,他们在描述这些需求时可能会用到一小部分更具体的术语,但领域专家应该能够更深入地思考他们所从事的领域。如果连经验丰富的领域专家都不能理解模型,那么模型一定出了什么问题。

最初,当用户讨论系统尚未建模的未来功能时,他们没有模型可供使用。但当他们开始与开发人员一起仔细讨论这些新想法时,探索共享模型的过程就开始了。最初的模型可能很笨拙且不完整,但会逐渐精化。随着新语言的演进,领域专家必须付出更多努力来适应它,并更新那些仍然很重要的旧文档。

当领域专家使用这种语言互相讨论,或者与开发人员进行讨论时,很快就会发现模型中哪些地方不符合他们的需要,甚至是错误的。另一方面,模型语言的精确性也会促使领域专家(在开发人员的帮助下)发现他们想法中的矛盾和含糊之处。

开发人员和领域专家可以通过一步一步地使用模型对象来走查场景,从而对模型进行非正式的测试。每次讨论都是开发人员和专家一起使用模型的机会,在这个过程中,他们可以加深彼此的理解,并对概念进行精化。

领域专家可以使用模型语言来编写用例,甚至可以直接利用模型来具体说明验收测试。

有时,有人会反对使用模型语言来收集需求。毕竟,难道需求不应该独立于实现它们的设计吗?这种观点忽视了所有语言都要基于某种模型这一事实。词的意义是不明确。领域模型通常是从领域专家自己的术语中推导出来的,但已经经过了“清理”,以便具有更明确、更严密的定义。当然,如果这些定义与领域公认的意义有较大差别,领域专家应该反对。在敏捷过程中,需求是随着项目的前进而演变的,因为几乎不存在现成的知识可以充分说明一个应用程序。用精化后的UBIQUITOUS LANGUAGE来重新组织需求应该是这种演变过程的一部分。

语言的多样性通常是必要的,但领域专家与开发人员之间不应该有语言上的分歧(第12章将讨论多个模型在同一个项目上共存的情况)。

当然,开发人员的确会使用领域专家无法理解的技术术语。开发人员有其所需的大量术语来讨论系统技术。几乎可以肯定的是,用户也会用开发人员无法理解的、超出应用程序范畴的专用术语。这些都是对语言的扩展。但在这些语言扩展中,同一领域的相同词汇不应该反映不同的模型。

第2章 交流与语言的使用 - 图3 图2-3 术语的交集产生了UBIQUITOUS LANGUAGE

有了UBIQUITOUS LANGUAGE之后,开发人员之间的对话、领域专家之间的讨论以及代码本身所表达的内容都基于同一种语言,都来自于一个共享的领域模型。

2.4 文档和图

每当我参加讨论软件设计的会议时,如果不在白板或画板上画图,我就很难讨论下去。我画的大部分是UML图,主要以类图和对象交互图为主。

有些人天生是视觉动物,图可以帮助人们掌握某些类型的信息。UML图在传达对象之间的关系上真是游刃有余,而且也很擅长表现交互。但它们却无法给出这些对象的概念定义。在会议中,我会一边画图一边用语言来丰富它们的意义,或者在与其他参与者讨论时进行解释。

简单、非正式的UML图能够维系整个讨论。绘制一幅包含当前问题最关键的3~5个对象的图,这样每个人都可以集中注意力。所有人就对象关系会达成一致的认识,更重要的是,他们将使用相同的对象名称。如此,口头讨论会更加高效。当人们尝试不同的想法时,图也随之改变,草图在某种程度上可以反映讨论的变化,这是讨论中真正重要的部分。毕竟,UML就是统一建模语言。

当人们必须通过UML图表示整个模型或设计时,麻烦也随之而来。很多对象模型图在某些方面过于细致,同时在某些方面又有很多遗漏。说它们过于细致是因为人们认为必须将所有要编码的对象都放到建模工具中。而细节过多的结果是“只见树木,不见森林”。

尽管存在所有这些细节,但属性和关系只是对象模型的一部分。这些对象的行为以及这些对象上的约束就不那么容易表示了。对象交互图可以阐明设计中的一些复杂之处,但却无法用这种方式来展示大量的交互,就是工作量太大了,既要制作图,还要学习这些图。而且交互图也只能暗示出模型的目的。要想把约束和断言包括进来,需要在UML图中使用文本,这些文本用括号括起来,插入到图中。

操作名称可能会暗示出对象的行为职责,对象交互图(或序列图)中也会隐含地展示出这些职责,但无法直接表述。因此,这项任务就要靠补充文本或对话来完成。换言之,UML图无法传达模型的两个最重要的方面,一个方面是模型所表示的概念的意义,另一方面是对象应该做哪些事情。但是,这并不是大问题,因为通过仔细地使用语言(英语、西班牙语或其他任何一种语言)就可以很好地完成这项任务。

UML也不是一种十分令人满意的编程语言。我从未见过有人使用建模工具的代码生成功能达到了预期目的。如果UML的能力无法满足需要,通常人们就不得不忽略模型最关键的部分,因为有些规则并不适合用线框图来表示。当然,代码生成器也无法使用上面所说的那些文本注释。如果确实能使用UML这样的绘图语言来编写可执行程序,那么UML图就会退化为程序本身的另一种视图,这样,“模型”的真正含义就丢失了。如果使用UML作为实现语言,则仍然需要利用其他手段来表达模型的确切含义。

图是一种沟通和解释手段,它们可以促进头脑风暴。简洁的小图能够很好地实现这些目标,而涵盖整个对象模型的综合性大图反而失去了沟通或解释能力,因为它们将读者淹没在大量细节之中,加之这些图也缺乏目的性。鉴于此,我们应避免使用包罗万象的对象模型图,甚至不能使用包含所有细节的UML数据存储库。相反,应使用简化的图,图中只包含对象模型的重要概念——这些部分对于理解设计至关重要。本书中的图都是我在项目中使用过比较典型的图。它们很简单,而且具有很强的解释能力,在澄清一些要点时,还使用了一些非标准的符号。它们显示了设计约束,但它们不是面面俱到的设计规范。它们只体现了思想纲要。

设计的重要细节应该在代码中体现出来。良好的实现应该是透明的,清楚地展示其背后的模型(下一章及本书其他许多章节的主题就是阐述如何做到这一点)。互为补充的图和文档能够引导人们将注意力放在核心要点上。自然语言的讨论可以填补含义上的细微差别。这就是为什么我喜欢把典型的UML使用方法颠倒过来的原因。通常的用法是以图为主,辅以文本注释;而我更愿意以文本为主,用精心挑选的简化图作为说明。

务必要记住模型不是图。图的目的是帮助表达和解释模型。代码可以充当设计细节的存储库。书写良好的Java代码与UML具有同样的表达能力。经过仔细选择和构造的图可以帮助人们集中注意力,并起到指导作用,当然前提条件是不能强制用图来表示全部模型或设计,因为这样会削弱图的清晰表达的能力。

2.4.1 书面设计文档

口头交流可以解释代码的含义,因此可作为代码精确性和细节的补充。虽然交谈对于将人们与模型联系起来是至关重要的,但书面文档也是必不可少的,任何规模的团队都需要它来提供稳定和共享的交流。但要想编写出能够帮助团队开发出好软件的书面文档却是一个不小的挑战。

一旦文档的形式变得一成不变,往往会从项目进展流程中脱离出来。它会跟不上代码或项目语言的演变。

书面文档有很多编写方法。本书第四部分将介绍几种满足特定需要的具体文档,但不会列出项目需要使用的所有文档,而是给出两条用于评估文档的总体原则。

文档应作为代码和口头交流的补充

每种敏捷过程在编写文档方面都有自己的理念。极限编程主张完全不使用(多余的)设计文档,而让代码解释自己。实际运行的代码不会说谎,而其他文档则不然。运行代码所产生的行为是明确的。

极限编程只关注对程序及可执行测试起作用的因素。由于为代码添加的注释并不影响程序的行为,因此它们往往无法与当前代码及其模型保持同步。外部文档和图也不会影响程序的行为,因此它们也无法保持同步。另一方面,口头交流和临时在白板上画的图不会长久保留而产生混淆。依赖代码作为交流媒介可以促使开发人员保持代码的整洁和透明。

然而,将代码作为设计文档也有局限性。它可能会把读代码的人淹没在细节中。尽管代码的行为是非常明确的,但这并不意味着其行为是显而易见的。而且行为背后的意义可能难以表达。换言之,只用代码做文档与使用大而全的UML图面临着差不多相同的基本问题。当然,团队进行大量的口头交流能够为代码提供上下文和指导,但是,口头交流很短暂,而且范围很小。此外,开发人员并不是唯一需要理解模型的人。

文档不应再重复表示代码已经明确表达出的内容。代码已经含有各个细节,它本身就是一种精确的程序行为说明。

其他文档应该着重说明含义,以便使人们能够深入理解大尺度结构,并将注意力集中在核心元素上。当编程语言无法直接明了地实现概念时,文档可以澄清设计意图。我们应该把书面文档作为代码和口头讨论的补充。

文档应当鲜活并保持最新

我在为模型编写书面文档时,会仔细选择一个小的模型子集来画图,然后让文字放臵在这些图周围。我用文字定义类及其职责,并且像自然语言那样把它们限定在一个语义上下文中。而图显示了在将概念形式化和简化为对象模型的过程中所做的一些选择。这些图可以随意一些,甚至是手绘的。手绘图除了节省工作量,也让人们一看就知道它们是不正式、临时的。这些优点都非常有利于交流,因为它们适用于我们的模型思想。

设计文档的最大价值在于解释模型的概念,帮助在代码的细节中指引方向,或许还可以帮助人们深入了解模型预期的使用风格。根据不同的团队理念,整个设计文档可能会十分简单,如只是贴在墙上的一组草图,也可能会非常详尽。

文档必须深入到各种项目活动中去。判断是否做到这一点的最简单方法,是观察文档与UBIQUITOUS LANGUAGE之间的交互。文档是用人们(当前)在项目上讲的语言编写的吗?它是用嵌入到代码中的语言编写的吗?

注意听UBIQUITOUS LANGUAGE,观察它是如何变化的。如果设计文档中使用的术语不再出现在讨论和代码中,那么文档就没有起到它的作用。或许是文档太大或太复杂了,或许是它没有关注足够重要的主题。人们要么不阅读文档,要么觉得它索然无味。如果文档对UBIQUITOUS LANGUAGE没有影响,那么一定是出问题了。

相反,我们会注意到UBIQUITOUS LANGUAGE随着文档渐渐过时而自然地改变。显然,要么人们不再关心文档,要么认为它不重要而不再去更新它。这时可以将它作为历史文件安全地归档,如果继续使用这样的文档可能会产生混淆并损害项目。如果文档不再担负重要的作用,那么纯粹靠意志和纪律保持其更新就是浪费精力。

UBIQUITOUS LANGUAGE可以使其他文档(如需求规格说明)更简洁和明确。当领域模型反映了与业务最相关的知识时,应用程序的需求成为该模型内部的场景,而UBIQUITOUS LANGUAGE可直接用MODEL-DRIVEN DESIGN(模型驱动设计)的方式描述此类场景(参见第3章)。结果就是规格说明的编写更简单,因为它们不必传达模型背后隐含的业务知识。

通过将文档减至最少,并且主要用它来补充代码和口头交流,就可以避免文档与项目脱节。根据UBIQUITOUS LANGUAGE及其演变来选择那些需要保持更新并与项目活动紧密交互的文档。

2.4.2 完全依赖可执行代码的情况

现在,我们来考查一下XP社区和其他一些人为何选择几乎完全依赖可执行代码及其测试。本书主要讨论了如何通过MODEL-DRIVEN DESIGN使代码表达出设计的含义(参见第3章)。良好的代码具有很强的表达能力,但它所传递的信息不能确保是准确的。一段代码所产生的实际行为是不会改变的。但是,方法名称可能会有歧义、会产生误导或者因为已经过时而无法表示方法的本质含义。测试中的断言是严格的,但变量和代码组织方式所表达出来的意思未必严格。好的编程风格会尽力使这种联系直接化,但其仍然主要靠开发人员的自律。编码时需要一丝不苟的态度,只有这样才能编写出“言行全部正确”的代码。

消除这些差异是诸如声明式设计(参见第10章)这样的方法的最大优点,在这类方法中,程序元素用途的陈述决定了它在程序中的实际行为。从UML生成程序的部分动机就来源于此,虽然目前看来这通常不会得到好的结果。

尽管代码可能会产生误导,但它仍然比其他文档更基础。要想利用当前的标准技术使代码所传达的消息与它的行为和意图保持一致,需要纪律和思考设计的特定方式(第三部分将详细讨论这些问题)。要有效地交流,代码必须基于在编写需求时所使用的同一种语言,也就是开发人员之间、开发人员与领域专家之间进行讨论时所使用的语言。

2.5 解释性模型

本书的核心思想是在实现、设计和团队交流中使用同一个模型作为基础。如果各有各的模型,将会造成危害。

模型在帮助领域学习方面也具有很大价值。对设计起到推动作用的模型是领域的一个视图,但为了学习领域,还可以引入其他视图,这些视图只用作传递一般领域知识的教学工具。出于此目的,人们可以使用与软件设计无关的其他种类模型的图片或文字。

使用其他模型的一个特殊原因是范围。驱动软件开发过程的技术模型必须经过严格的精简,以便用最小化的模型来实现其功能。而解释性模型则可以包含那些提供上下文的领域方面——这些上下文用于澄清范围更窄的模型。

解释性模型提供了一定的自由度,可以专门为某个特殊主题定制一些表达力更强的风格。领域专家在一个领域中所使用的视觉隐喻通常呈现了更清晰的解释,这可以教给开发人员领域知识,同时使领域专家们的意见更一致。解释性模型还可以以一种不同的方式来呈现领域,并且各种不同角度的解释有助于人们更好地学习。

解释性模型不必是对象模型,而且最好不是。实际上在这些模型中不使用UML是有好处的,这样可以避免人们错误地认为这些模型与软件设计是一致的。尽管解释性模型与驱动设计的模型往往有对应关系,但它们并不完全类似。为了避免混淆,每个人都必须知道它们之间的区别。

示例 航运操作和路线

考虑一个用来追踪航运公司货物的应用程序。模型包含一个详细的视图,它显示了如何将港口装卸和货轮航次组合为一次货运的操作计划(“路线”)。如图2-4所示。但对外行而言,类图可能起不到多大的说明作用。

第2章 交流与语言的使用 - 图4 图2-4 航运路线的类图

在这种情况下,解释性模型可以帮助团队成员理解类图的实际含义。图2-5是表示相同概念的另一种方式。

第2章 交流与语言的使用 - 图5 图2-5 航运路线的解释性模型

图中的每根线段都表示货物的一种状态——或者正在港口装卸(装货或卸货),或者停放在仓库里,或者正在运输途中。这个图并没有与类图中的细节一一对应,但强调了领域的要点。

这种图连同对它所表示的模型的自然语言解释,能够帮助开发人员和领域专家理解更严格的软件模型图。综合使用这两种图要比单独使用一种图更容易理解。