领域驱动设计 软件核心复杂性应对之道 修订版(txt+pdf+epub+mobi电子书下载)


发布时间:2020-06-15 07:56:31

点击下载

作者:[美] 埃里克·埃文斯(Eric Evans)

出版社:信息技术第一出版分社

格式: AZW3, DOCX, EPUB, MOBI, PDF, TXT

领域驱动设计 软件核心复杂性应对之道 修订版

领域驱动设计 软件核心复杂性应对之道 修订版试读:

前言

至少20年前,一些顶尖的软件设计人员就已经认识到领域建模和设计的重要性,但令人惊讶的是,这么长时间以来几乎没有人写出点儿什么,告诉大家应该做哪些工作或如何去做。尽管这些工作还没有被清楚地表述出来,但一种新的思潮已经形成,它像一股暗流一样在对象社区中涌动,我把这种思潮称为领域驱动设计(domain-driven design)。

过去10年中,我在几个业务和技术领域开发了一些复杂的系统。我在设计和开发过程中尝试了一些最佳实践,它们都是面向对象开发高手用过的领先技术。有些项目非常成功,但有几个项目却失败了。成功的项目有一个共同的特征,那就是都有一个丰富的领域模型,这个模型在迭代设计的过程中不断演变,而且成为项目不可分割的一部分。

本书为作出设计决策提供了一个框架,并且为讨论领域设计提供了一个技术词汇库。本书将人们普遍接受的一些最佳实践综合到一起,并融入了我自己的见解和经验。面对复杂领域的软件开发团队可以利用这个框架来系统性地应用领域驱动设计。

三个项目的对比

谈到领域设计实践对开发结果的巨大影响时,我的记忆中立即就会跳出三个项目,它们就是鲜活的例子。虽然这三个项目都交付了有用的软件,但只有一个项目实现了宏伟的目标——交付了能够满足组织后续需求、可以不断演进的复杂软件。

我要说的第一个项目完成得很迅速,它提供了一个简单实用的Web交易系统。开发人员主要凭直觉开发,但这并没有妨碍他们,因为简单软件的编写并不需要过多地注意设计。由于最初的这次成功,人们对未来开发的期望值变得极高。我就是在这个时候被邀请开发它的第二个版本的。当我仔细研究这个项目时,发现他们没有使用领域模型,甚至在项目中没有一种公共语言,而且项目完全没有一种结构化的设计。项目领导者对我的评价并不赞同,于是我拒绝了这项工作。一年后,这个项目团队陷入困境,无法交付第二个版本。尽管他们在技术的使用方面也值得商榷,但真正挫败他们的是业务逻辑。他们的第一个版本过早地变得僵化,成为一个维护代价十分高昂的遗留系统。

要想克服这种复杂性,需要非常严格地使用领域逻辑设计方法。在我职业生涯的早期,我幸运地完成了一个非常重视领域设计的项目,这就是我要说的第二个项目。这个项目的领域复杂性与上面提到的那个项目相仿,它最初也小获成功,为贸易机构提供了一个简单的应用程序。但在最初交付之后紧跟着又进行了连续的加速开发。每次迭代都为上一个版本在功能的集成和完善上增加了非常好的新选项。开发团队能够按照贸易商的要求提供灵活性和扩展性。这种良性发展直接归功于深刻的领域模型,它得到了反复精化,并在代码中得以体现。当团队对该领域有了新的理解后,领域模型也随之深化。开发人员之间、开发人员与领域专家之间的沟通质量都得到改善,而且设计不但没有加重维护负担,反而变得易于修改和扩展。

遗憾的是,仅靠重视模型并不会使项目达到这样的良性循环。我要说的第三个项目就是这种情况,它开始制订的目标很高,打算基于一个领域模型建立一个全球企业系统,但在经过了几年的屡战屡败之后,不得不降格以求,最终“泯然众人矣”。团队拥有很好的工具,对业务也有较好的理解,也非常认真地进行了建模。但团队却错误地将开发人员的角色独立出来,导致建模与实现脱节,因此设计无法反映不断深化的分析。总之,详细的业务对象设计不能保证它们能够严丝合缝地被整合到复杂的应用程序中。反复的迭代并没有使代码得以改进,因为开发人员的技术水平参差不齐,他们没有认识到他们使用了非正式的风格和技术体系来创建基于模型的对象(这些对象也充当了实用的、可运行的软件)。几个月过去了,开发工作由于巨大的复杂性而陷入困境,而团队对项目也失去了一致的认识。经过几年的努力,项目确实创建了一个适当的、有用的软件,但团队已经放弃了当初的宏伟抱负,也不再重视模型。

复杂性的挑战

很多因素可能会导致项目偏离轨道,如官僚主义、目标不清、资源缺乏等。但真正决定软件复杂性的是设计方法。当复杂性失去控制时,开发人员就无法很好地理解软件,因此无法轻易、安全地更改和扩展它。而好的设计则可以为开发复杂特性创造更多机会。

一些设计因素是技术上的。软件的网络、数据库和其他技术方面的设计耗费了人们大量的精力。很多书籍都介绍过如何解决这些问题。大批开发人员很注意培养自己的技能,并紧跟每一次技术进步。

然而很多应用程序最主要的复杂性并不在技术上,而是来自领域本身、用户的活动或业务。当这种领域复杂性在设计中没有得到解决时,基础技术的构思再好也无济于事。成功的设计必须系统地考虑软件的这个核心方面。

本书有两个前提:(1)在大多数软件项目中,主要的焦点应该是领域和领域逻辑;(2)复杂的领域设计应该基于模型。

领域驱动设计是一种思维方式,也是一组优先任务,它旨在加速那些必须处理复杂领域的软件项目的开发。为了实现这个目标,本书给出了一整套完整的设计实践、技术和原则。

设计过程与开发过程

设计书就是讲设计,过程书只是讲过程。它们之间很少互相参考。设计和过程本身就是两个足够复杂的主题。本书是一本设计书,但我相信设计与过程这二者是密不可分的。设计思想必须被成功实现,否则它们就只是纸上谈兵。

当人们学习设计技术时,各种可能性令他们兴奋不已,然而真实项目的错综复杂又会为他们泼上一盆冷水。他们无法用所使用的技术来贯彻新的设计思想,或者不知道何时应该为了节省时间而放弃某个设计方面,何时又应该坚持不懈直至找到一个干净利落的解决方案。开发人员可以抽象地讨论设计原则的应用,而且他们也确实在进行着这样的讨论,但更自然的做法应该是讨论如何完成实际工作。因此,虽然本书是一本有关设计的书,但我会在必要的时候穿越这条人为设臵的边界,进入过程的领域。这有助于将设计原则放到一个适当的语境下进行讨论。

虽然本书并不局限于某一种特定的方法,但主要还是面向“敏捷开发过程”这一新体系。特别地,本书假定项目必须遵循两个开发实践,要想应用书中所讲的方法,必须先了解这两个实践。(1)迭代开发。人们倡导和实践迭代开发已经有几十年时间了,而且它是敏捷开发方法的基础。在敏捷开发和极限编程(XP)的文献中有很多关于迭代开发的精彩讨论,其中包括Surviving Object-Oriented Projects[Cockburn 1998][1]和Extreme Programming Explained[Beck 1999]。(2)开发人员与领域专家具有密切的关系。领域驱动设计的实质就是消化吸收大量知识,最后产生一个反映深层次领域知识并聚焦于关键概念的模型。这是领域专家与开发人员的协作过程,领域专家精通领域知识,而开发人员知道如何构建软件。由于开发过程是迭代式的,因此这种协作必须贯穿整个项目的生命周期。

极限编程的概念是由Kent Beck、Ward Cunningham和其他人共同提出的[Beck 2000],它是敏捷过程最重要的部分,也是我使用得最多的一种编程方法。为了使讨论更加具体,整本书都将使用XP作为基础讨论设计和过程的交互。本书论述的原则很容易应用于其他敏捷过程。

近年来,反对“精细开发方法学”(elaborate development methodology)的呼声渐起,人们认为无用的静态文档以及死板的预先规划和设计加重了项目的负担。相反,敏捷过程(如XP)强调的是应对变更和不确定性的能力。

极限编程承认设计决策的重要性,但强烈反对预先设计。相反,它将相当大的精力投入到促进沟通和提高项目快速变更能力的工作中。具有这种反应能力之后,开发人员就可以在项目的任何阶段只利用“最简单而管用的方案”,然后不断进行重构,一步一步做出小的设计改进,最终得到满足客户真正需要的设计。

这种极端的简约主义是解救那些过度追求设计的执迷者的良方。那些几乎没有价值的繁琐文档只会为项目带来麻烦。项目受到“分析瘫痪症”的困扰,团队成员十分担心会出现不完美的设计,这导致他们根本没法取得进展。这种状况必须得到改变。

遗憾的是,这些有关过程的思想可能会被误解。每个人对“最简单”都有不同的定义。持续重构其实是一系列小规模的重新设计,没有严格设计原则的开发人员将会创建出难以理解或修改的代码,这恰好与敏捷的精神相悖。而且,虽然对意外需求的担心常常导致过度设计,但试图避免过度设计又可能走向另一个极端——不敢做任何深入的设计思考。

实际上,XP最适合那些对设计的感觉很敏锐的开发人员。XP过程假定人们可以通过重构来改进设计,而且可以经常、快速地完成重构。但重构本身的难易程度取决于先前的设计选择。XP过程试图改善团队沟通,但模型和设计的选择有可能使沟通更明确,也有可能会使沟通不畅。

本书将设计和开发实践结合起来讨论,并阐述领域驱动设计与敏捷开发过程是如何互相增强的。在敏捷开发过程中使用成熟的领域建模方法可以加速开发。过程与领域开发之间的相互关系使得这种方法比任何“纯粹”真空式的设计更加实用。

本书的结构

本书分为4个部分。

第一部分“运用领域模型”提出领域驱动开发的基本目标,这些目标是后面几部分中所讨论的实践的驱动因素。由于软件开发方法有很多,因此第一部分还定义了一些术语,并给出了用领域模型来驱动沟通和设计的总体含义。

第二部分“模型驱动设计的构造块”将面向对象领域建模中的一些核心的最佳实践提炼为一组基本的构造块。这一部分主要是消除模型与实际运行的软件之间的鸿沟。团队一致使用这些标准模式就可以使设计井然有序,并且使团队成员更容易理解彼此的工作。使用标准模式还可以为公共语言贡献术语,使得所有团队成员可以使用这些术语来讨论模型和设计决策。

但这一部分的主旨是讨论一些能够保持模型和实现之间互相协调并提高效率的设计决策。要想达到这种协调,需要密切注意个别元素的一些细节。这种小规模的仔细设计为开发人员提供了一个稳固的基础,在此基础上就可以应用第三部分和第四部分讨论的建模方法了。

第三部分“通过重构来加深理解”讨论如何将构造块装配为实用的模型,从而实现其价值。这一部分没有直接讨论深奥的设计原则,而是着重强调一个发现过程。有价值的模型不是立即就会出现的,它们需要对领域的深入理解。这种理解是一步一步得到的,首先需要深入研究模型,然后基于最初的(可能是不成熟的)模型实现一个初始设计,再反复改进这个设计。每次团队对领域有了新的理解之后,都需要对模型进行改进,使模型反映出更丰富的知识,而且必须对代码进行重构,以便反映出更深刻的模型,并使应用程序可以充分利用模型的潜力。这种一层一层“剥洋葱”的方法有时会创造一种突破的机会,使我们得到更深刻的模型,同时快速进行一些更深入的设计修改。

探索本身是永无止境的,但这并不意味着它是随机的。第三部分深入阐述一些指引我们保持正确方向的建模原则,并提供了一些指导我们进行探索的方法。

第四部分“战略设计”讨论在复杂系统、大型组织以及与外部系统和遗留系统的交互中出现的复杂情况。这一部分探讨了作为一个整体应用于系统的3条原则:上下文、提炼和大型结构。战略设计决策通常由团队制定,或者由多个团队共同制定。战略设计可以保证在大型系统或应用程序(它们应用于不断延伸的企业级网络)上以较大规模去实现第一部分提出的目标。

本书通篇讨论使用的例子并不是一些过于简单的“玩具式”问题,而是全部选自实际项目。

本书的大部分内容实际上是作为一系列的“模式”编写的。但读者无需顾忌这一方法也应该能够理解本书,对模式的风格和格式感兴趣的读者可以参考附录。

补充材料可以参考http://domaindrivendesign.org,该网站提供了示例代码和社区讨论内容。

本书面向的读者

本书主要是为面向对象软件开发人员编写的。软件项目团队的大部分成员都能够从本书的某些部分获益。本书最适合那些正在项目上尝试这些实践的人员,以及那些已经在这样的项目上积累了丰富经验的人员。

要想从本书受益,掌握一些面向对象建模知识是非常必要的,如UML图和Java代码,因此一定要具备基本读懂这些语言的能力,但不必精通细节。了解极限编程的知识有助于从这个角度来理解开发过程的讨论,但不具备这一背景知识也能读懂这些内容。

一些中级软件开发人员可能已经了解面向对象设计的一些知识,也许读过一两本软件设计的书,那么本书将填补这些读者的知识空缺,向他们展示如何在实际的软件项目上应用对象建模技术。本书将帮助这些开发人员学会用高级建模和设计技巧来解决实际问题。

高级软件开发人员或专家可能会对书中用于处理领域的综合框架感兴趣。这种系统性的设计方法将帮助技术负责人指导他们的团队保持正确的方向。此外,本书从头至尾所使用的明确术语将有助于高级开发人员与他们的同行沟通。

本书采用记叙体,读者可以从头至尾阅读,也可以从任意一章的开头开始阅读。具有不同背景知识的读者可能会有不同的阅读方式,但我推荐所有读者从第一部分的引言和第1章开始阅读。除此之外,本书的核心是第2、3、9和14章。已经掌握一定知识的读者可以采取跳跃式阅读的方式,通过阅读标题和粗体字内容即可掌握要点。一些高级读者则可以跳过前两部分,重点阅读第三部分和第四部分。

除了这些主要读者以外,分析员和相关的技术项目经理也可以从阅读本书中获益。分析员在掌握了领域与设计之间的联系之后,能够在敏捷项目中作出更卓越的贡献,也可以利用一些战略设计原则来更有重点地组织工作。

项目经理感兴趣的重点是提高团队的工作效率,并致力于设计出对业务专家和用户有用的软件。由于战略设计决策与团队组织和工作风格紧密相关,因此这些设计决策必然需要项目领导者的参与,而且对项目的路线有着重要的影响。

领域驱动团队

尽管开发人员个人能够从理解领域驱动设计中学到有价值的设计技术和观点,但最大的好处却来自团队共同应用领域驱动设计方法,并且将领域模型作为项目沟通的核心。这样,团队成员就有了一种公共语言,可以用来进行更充分的沟通,并确保围绕软件来进行沟通。他们将创建出一个与模型步调一致的清晰的实现,从而为应用程序的开发提供帮助。所有人都了解不同团队的设计工作之间的互相联系,而且他们会一致将注意力集中在那些对组织最有价值、最与众不同的特性的开发上。

领域驱动设计是一项艰巨的技术挑战,但它也会带来丰厚的回报,当大多数软件项目开始僵化而成为遗留系统时,它却为你敞开了机会的大门。[1].这种表述指这是本书参考文献中提到的图书。——编者注致谢

本书的创作历时4年多,其间经历了诸多工作形式的变化,在这个过程中很多人为我提供了帮助和支持。

感谢那些阅读本书书稿并提出意见的人。没有这些人的反馈意见,本书将不可能出版。其中有几个团队和一些人员对本书的评阅给予了特别的关注。由Russ Rufer和Tracy Bialek领导的硅谷模式小组(Silicon Valley Patterns Group)花费了几周时间详细审阅了本书完整的第一稿。由Ralph Johnson领导的伊利诺伊大学的阅读小组也花费了几周时间审阅了本书的第二稿。这些小组长期、精彩的讨论对本书产生了深远的影响。Kyle Brown和Martin Fowler提供了细致入微的反馈意见和宝贵的建议,也给了我无价的精神支持(在我们坐在一起钓鱼的时候)。Ward Cunningham的意见帮助我弥补了一些重大的缺陷。Alistair Cockburn在早期给了我很多鼓励,并和Hilary Evans一起帮助我完成了整个出版过程。David Siegel和Eugene Wallingford帮助我避免了很多技术上的错误。VibhuMohindra和Vladimir Gitlevich不厌其烦地检查了所有代码示例。

Rob Mee看了我对一些素材所做的早期研究,并在我尝试表达这种设计风格的时候与我进行了头脑风暴活动,帮我产生了很多新的想法。他后来又与我一起仔细探讨了后面的书稿。

本书在写作过程中经历了一次重大转折,这完全归功于JoshKerievsky。他劝说我在写作本书时借鉴“亚历山大”模式[1],后来本书正是按这种方式组织的。在1999年PLoP会议临近时的忙碌时刻,Josh还帮我收集第二部分的材料,首次将它们组织为更严密的形式。这些材料成了一粒种子,本书大部分后续内容都是围绕这些内容创作的。

还要感谢Awad Faddoul,我有数百个小时坐在他的咖啡厅中写作。咖啡厅宁静优雅,窗外的湖面上总有片片风帆,我正是这样才坚持写下去。

此外还要感谢Martine Jousset、Richard Paselk和Ross Venables,他们拍摄了一些非常精美的照片,用来演示一些关键概念(参见本书后面的图片说明)。

在构思本书之前,我必须先要形成我自己对软件开发的看法和理解。这个过程得到了一些杰出人员的无私帮助,他们是我的良师益友。David Siegel、Eric Gold和Iseult White各自从不同方面帮助我形成了对软件设计的思考方式。同时,Bruce Gordon、RichardFreyberg和Judith Segal博士也从不同角度帮助我找到了项目的成功之路。

我自己的观念就是从那时的思想体系中自然而然发展形成的。有些内容我在正文中清楚地列了出来,并且在可能的地方标明了出处。还有些可能是十分基础的知识,我甚至自己都没有意识到它们对我产生了影响。

我的硕士论文导师Bala Subramanium博士是我在数学建模方面的引路人,当时我们用数学建模来进行化学反应动力学方面的研究。虽说建模本身没什么稀奇,但那时的工作是引导我创作本书的一部分原因。

在更早之前,我的母亲Carol和父亲Gary对我思维模式的形成产生了很大影响。还有几位特别值得一提的教师激发了我的兴趣,帮助我打下坚实的基础,在此感谢Dale Currier(我的高中数学老师)、Mary Brown(我的高中英文写作老师)和Josephine McGlamery(我上6年级时的自然科学老师)。

最后,感谢我的朋友和家人,以及Fernando De Leon,感谢他们一直以来给我的鼓励。[1].克里斯托弗〃亚历山大(Christopher Alexander),1936年10月4日出生于奥地利的维也纳,是一名建筑师,以其设计理论和丰富的建筑设计作品而闻名于世。亚历山大认为,建筑的使用者比建筑师更清楚他们需要什么,他创造并以实践验证了“模式语言”,建筑模式语言赋予所有人设计并建造建筑的能力。亚历山大的代表作是《建筑模式语言》,该书对计算机科学领域中的“设计模式”运动产生了巨大的影响。亚历山大创立的增量、有机和连贯的设计理念也影响了“极限编程”运动。——编者注第一部分运用领域模型

上面这张图是18世纪中国描绘的世界地图。图中央最大的部分是中国,其周围散布着其他国家,但这些国家只是草草地表示了一下。这是适用于当时中国社会的世界模型,它意在关注中国自身。然而,这幅地图所呈现的世界观对于处理外交事务并无助益。当然,它对现代中国也毫无用处。地图就是模型,而模型被用来描绘人们所关注的现实或想法的某个方面。模型是一种简化。它是对现实的解释——把与解决问题密切相关的方面抽象出来,而忽略无关的细节。

每个软件程序是为了执行用户的某项活动,或是满足用户的某种需求。这些用户应用软件的问题区域就是软件的领域。一些领域涉及物质世界,例如,机票预订程序的领域中包括飞机乘客在内。有些领域则是无形的,例如,会计程序的金融领域。软件领域一般与计算机关系不大,当然也有例外,例如,源代码控制系统的领域就是软件开发本身。

为了创建真正能为用户活动所用的软件,开发团队必须运用一整套与这些活动有关的知识体系。所需知识的广度可能令人望而生畏,庞大而复杂的信息也可能超乎想象。模型正是解决此类信息超载问题的工具。模型这种知识形式对知识进行了选择性的简化和有意的结构化。适当的模型可以使人理解信息的意义,并专注于问题。

领域模型并非某种特殊的图,而是这种图所要传达的思想。它绝不单单是领域专家头脑中的知识,而是对这类知识严格的组织且有选择的抽象。图可以表示和传达一种模型,同样,精心书写的代码或文字也能达到同样的目的。

领域建模并不是要尽可能建立一个符合“现实”的模型。即使是对具体、真实世界中的事物进行建模,所得到的模型也不过是对事物的一种模拟。它也不单单是为了实现某种目的而构造出来的软件机制。建模更像是制作电影——出于某种目的而概括地反映现实。即使是一部纪录片也不会原封不动地展现真实生活。就如同电影制片人讲述故事或阐明观点时,他们会选择素材,并以一种特殊方式将它们呈现给观众,领域建模人员也会依据模型的作用来选择具体的模型。

模型在领域驱动设计中的作用

在领域驱动的设计中,3个基本用途决定了模型的选择。

(1) 模型和设计的核心互相影响。正是模型与实现之间的紧密联系才使模型变得有用,并确保我们在模型中所进行的分析能够转化为最终产品(即一个可运行的程序)。模型与实现之间的这种紧密结合在维护和后续开发期间也会很有用,因为我们可以基于对模型的理解来解释代码。(参见第3章)

(2) 模型是团队所有成员使用的通用语言的中枢。由于模型与实现之间的关联,开发人员可以使用该语言来讨论程序。他们可以在无需翻译的情况下与领域专家进行沟通。而且,由于该语言是基于模型的,因此我们可借助自然语言对模型本身进行精化。(参见第2章)

(3) 模型是浓缩的知识。模型是团队一致认同的领域知识的组织方式和重要元素的区分方式。透过我们如何选择术语、分解概念以及将概念联系起来,模型记录了我们看待领域的方式。当开发人员和领域专家在将信息组织为模型时,这一共同的语言(模型)能够促使他们高效地协作。模型与实现之间的紧密结合使来自软件早期版本的经验可以作为反馈应用到建模过程中。(参见第1章)

接下来的3章分别考查上述3种基本用途的意义和价值,以及它们之间的关联方式。遵循这些原则使用模型可以很好地支持具有丰富功能的软件的开发,否则就需要耗费大规模投资进行专门开发。

软件的核心

软件的核心是其为用户解决领域相关的问题的能力。所有其他特性,不管有多么重要,都要服务于这个基本目的。当领域很复杂时,这是一项艰巨的任务,要求高水平技术人员的共同努力。开发人员必须钻研领域以获取业务知识。他们必须磨砺其建模技巧,并精通领域设计。

然而,在大多数软件项目中,这些问题并未引起足够的重视。大部分有才能的开发人员对学习与他们的工作领域有关的知识不感兴趣,更不会下力气去扩展自己的领域建模技巧。技术人员喜欢那些能够提高其技能的可量化问题。领域工作很繁杂,而且要求掌握很多复杂的新知识,而这些新知识看似对提高计算机科学家的能力并无裨益。

相反,技术人才更愿意从事精细的框架工作,试图用技术来解决领域问题。他们把学习领域知识和领域建模的工作留给别人去做。软件核心的复杂性需要我们直接去面对和解决,如果不这样做,则可能导致工作重点的偏离。

在一次电视访谈节目中,喜剧演员John Cleese讲述了电影《巨蟒和圣杯》(Monty Python and the Holy Grail)在拍摄期间发生的一个小故事。有一幕他们反复拍了很多次,但就是感觉不够滑稽。最后,他停下来,与另一位喜剧演员Michael Palin(该幕中的另一位演员)商量了一下,他们决定稍微改变一下。随后又拍了一次,终于令他们满意了,于是收工。

第二天早上,Cleese观看了剪辑人员为前一天工作所做的粗剪。到了那个令他们颇费周章的场景时,Cleese发现剪辑人员竟然使用了先前拍摄的一个镜头,影片到这里又变得不滑稽了。

他问剪辑人员为什么没有按要求使用最后拍的那个镜头,剪辑人员回答说:“那个镜头不能用,因为有人闯入了镜头。”Cleese连看了两遍,仍未发现有什么不妥。最后,剪辑人员将影片暂停,并指出在屏幕边缘有一只一闪而过的大衣袖子。

影片的剪辑人员专注于准确完成自己的工作。他担心其他看到这部电影的剪辑人员会给他挑错。在这个过程中,镜头的核心作用被忽略了(“The Late Late Show with Craig Kilborn”,CBS,2001年9月)。

幸运的是,该剧的导演很懂喜剧,他最终使用了那个镜头。同样,在一个团队中,反映了对领域深层次理解的模型开发有时也会在混乱中迷失方向,此时,理解领域核心的领导者能够将软件项目带回到正确的轨道上来。

本书将展示领域开发中蕴藏的巨大机会,它能够培养精湛的设计技巧。大多数混乱的软件领域其实是一项充满乐趣的技术挑战。事实上,在许多科学领域中,“复杂性”都是当前最热门的话题之一,因为研究人员都在想办法解决真实世界中的复杂性。软件开发人员在面对尚未规范的复杂领域时,也会有同样的期望。创建一个克服这些复杂性的易懂模型会带来巨大的成就感。

开发人员可以采用一些系统性的思考方法来透彻地理解领域并开发出有效的模型。还有一些设计技巧可以使毫无头绪的软件应用变得井井有条。掌握这些技能可以令开发人员的价值倍增,即使是在一个最初不熟悉的领域中也是如此。第1章消化知识

几年前,我着手设计一个用于设计印制电路板(PCB)的专用软件工具。但有一个问题,我对电子硬件一无所知。当然,我也曾拜访过一些PCB设计师,但用不了3分钟,他们就令我晕头转向。如何才能了解足够多的知识,以便开始编写这个软件呢?当然,我并不打算在交付期限到来之前成为电子工程师。

我们试着让PCB设计师说明软件具体应该做些什么,但我们错了。虽然他们是优秀的电路设计师,但软件知识却太有限了,往往只知道如何读取一个ASCII文件、对它排序,然后添加一些注释并将它写回文件中,再生成一个报告。这些知识显然无法帮助他们大幅度提高效率。

最初的几次会面令人气馁,但我们在他们要求的报告中也看到了一丝希望。这些报告中总是涉及net这个词以及与其相关的各种细节。在这个领域中,net实质上是一种导线,它可以连接PCB上任意数量的元件,并向它连接的所有元件传递电子信号。这样,我们就得到了领域模型的第一个元素,如图1-1所示。图1-1

就这样,我们一边讨论所需的软件功能,一边开始画图。我使用一种非正式的、稍加变化的对象交互图来走查[1]各种场景,如图1-2所示。图1-2

PCB专家1:元件不一定就是芯片(chip)。

开发人员(我):那它们是不是只应该叫做“元件”?

专家1:我们将它们称作“元件实例”(component instance)。相同的元件可能有很多。

专家2:他把“net”画成和元件实例一样的框了。

专家1:他没有使用我们的符号。我猜想,他要把每一项都画成方框。

开发人员:很抱歉,是这样的。我想我最好对这个符号稍加解释。

他们不断地纠正我的错误,在这个过程中我开始学习他们的知识。我们共同消除了术语上的不一致和歧义,也消除了他们在技术观点上的分歧,在这个过程中,他们也得到了学习。他们的解释更准确和一致了,然后我们开始共同开发一个模型。

专家1:只说一个信号到达一个ref-des是不够明确的,我们必须知道信号到达了哪个引脚。

开发人员:什么是ref-des?

专家2:它就是一个元件实例。我们用的一个专门工具中用ref-des这个名称。

专家1:总之,net将一个实例的某个引脚与另一个实例的某个引脚相连。

开发人员:一个引脚是不是只属于一个元件实例,而且只与一个net相连?

专家1:对,是这样。

专家2:还有,每个net都有一个拓扑结构,也就是电路的布局,它决定了net内部各元件的连接方式。

开发人员:嗯,这样画如何(如图1-3所示)?图1-3

为了让讨论更集中,接下来的一段时间我们探讨了一个特定的功能:探针仿真(probe simulation)。探针仿真跟踪信号的传播,以便检测在设计中可能出现特定类型问题的位臵。

开发人员:现在我已经明白了Net是如何将信号传播给它所连接的所有Pin的,但如何将信号传送得更远呢?这与拓扑结构(topology)有关系吗?

专家2:没有,是元件推送信号前进。

开发人员:我们肯定无法对芯片的内部行为建模,因为这太复杂了。

专家2:我们不必这样做。可以使用一种简化形式。只需列出通过元件可从某些Pin将信号推送到其他引脚即可。

开发人员:类似于这样吗?(经过反复的尝试和修改,我们终于共同绘制出了一个草图,如图1-4所示。)图1-4

开发人员:但你想从这种计算中知道什么呢?

专家2:我们要查找较长的信号延迟,也就是说,查找超过2或3跳的信号路径。这是一条经验法则。如果路径太长,信号可能无法在时钟周期内到达。

开发人员:超过3跳……这么说我们需要计算路径长度。那么怎样算作一跳呢?

专家2:信号每通过一个Net,就称为1跳。

开发人员:那么我们可以沿着电路来计算跳数,每遇到一个net,跳数就加1,如图1-5所示。图1-5

开发人员:现在我唯一不明白的地方是“推动”是从哪里来的。是否每个元件实例都需要存储该数据?

专家2:一个元件的所有实例的推动行为都是相同的。

开发人员:那么元件的类型决定了推动行为,而每个实例的推动行为都是相同的(如图1-6所示)?图1-6

专家2:这个图的意思我没完全明白,但我猜想每个元件存储的推动行为就差不多是这样的吧。

开发人员:抱歉,这个地方我可能问得有点过细了。我只是想考虑得全面一些……现在,拓扑结构对它有什么影响吗?

专家1:拓扑结构不影响探针仿真。

开发人员:那么可以暂不考虑它,是吗?等用到这些特性时再回来讨论它。

就这样,我们的讨论一直进行下去(其中遇到的困难比上面显示的多得多)。我们一边进行“头脑风暴”式的讨论,一边对模型进行精化,边提问边回答。随着我对领域理解的加深,以及他们对模型在解决方案中作用的理解的加深,模型不断发展。图1-7显示了那个早期模型的类图。图1-7

随后,我们又拿出一部分工作时间进行了几轮这样的讨论,我觉得自己已经理解了足够多的知识,可以试着编写一些代码了。我写了一个非常简单的原型,并用一个自动测试框架来测试它。我避开了所有的基础设施。这个原型没有持久化机制,也没有用户界面(UI)。这样我就可以专注于代码的行为。只不过几天我就能够演示简单的探针仿真了。虽然它使用的是虚拟数据,而且向控制台输出的是原始文本,但确实是使用Java对象对路径长度执行实际的计算。这些Java对象所反映的模型正是我和领域专家们一起开发出来的。

这个具体的原型使得领域专家们更清楚地理解了模型的含义,以及它与最终软件之间的联系。从那时起,我们的模型讨论越来越具有互动性了,因为他们可以看到我如何将新学到的知识融合到模型中,然后反映到软件上。他们也可以从原型得到具体的反馈,从而印证自己的想法。

模型中包含与我们要解决的问题有关的PCB领域知识,这些知识远远比我们在这里演示的复杂。模型将很多同义词和语言描写中的微小差别做了统一,并排除了数百条与问题没有直接关系的事实(虽然工程师们都理解这些事实),如元件的实际数字特性。像我这样的软件专业人员看到这张图后,几分钟内就能明白软件是做什么的。这个模型就相当于一个框架,开发人员可以借助它来组织新的信息并更快地学习,从而更准确地判断哪些部分重要,哪些部分不重要,并更好地与PCB工程师进行沟通。

当PCB工程师提出新的功能需求时,我就让他们带我走查对象交互的场景。当模型对象无法清楚地表达某个重要场景时,我们就通过头脑风暴活动创建新的模型对象或者修改原有的模型对象,并消化理解这些模型对象中的知识。在我们精化模型的过程中,代码也随之一步步演进。几个月后,PCB工程师们得到了一个远远超乎他们期望的功能丰富的工具。1.1 有效建模的要素

以下几方面因素促使上述案例得以成功。

(1) 模型和实现的绑定。最初的原型虽然简陋,但它在模型与实现之间建立了早期链接,而且在所有后续的迭代中我们一直在维护该链接。

(2) 建立了一种基于模型的语言。最初,工程师们不得不向我解释基本的PCB问题,而我也必须向他们解释类图的含义。但随着项目的进展,双方都能够直接使用模型中的术语,并将它们组织为符合模型结构的语句,而且无需翻译即可理解互相要表达的意思。

(3) 开发一个蕴含丰富知识的模型。对象具有行为和强制性规则。模型并不仅仅是一种数据模式,它还是解决复杂问题不可或缺的部分。模型包含各种类型的知识。

(4) 提炼模型。在模型日趋完整的过程中,重要的概念不断被添加到模型中,但同样重要的是,不再使用的或不重要的概念则从模型中被移除。当一个不需要的概念与一个需要的概念有关联时,则把重要的概念提取到一个新模型中,其他那些不要的概念就可以丢弃了。

(5) 头脑风暴和实验。语言和草图,再加上头脑风暴活动,将我们的讨论变成“模型实验室”,在这些讨论中可以演示、尝试和判断上百种变化。当团队走查场景时,口头表达本身就可以作为所提议的模型的可行性测试,因为人们听到口头表达后,就能立即分辨出它是表达得清楚、简捷,还是表达得很笨拙。

正是头脑风暴和大量实验的创造力才使我们找到了一个富含知识的模型并对它进行提炼,在这个过程中,基于模型的语言提供了很大帮助,而且贯穿整个实现过程中的反馈闭环也对模型起到了“训练”作用。这种知识消化将团队的知识转化为有价值的模型。1.2 知识消化

金融分析师要消化理解的内容是数字。他们筛选大量的详细数字,对其进行组合和重组以便寻求潜在的意义,查找可以产生重要影响的简单表示方式——一种可用作金融决策基础的理解。

高效的领域建模人员是知识的消化者。他们在大量信息中探寻有用的部分。他们不断尝试各种信息组织方式,努力寻找对大量信息有意义的简单视图。很多模型在尝试后被放弃或改造。只有找到一组适用于所有细节的抽象概念后,工作才算成功。这一精华严谨地表示了所发现的最为相关的知识。

知识消化并非一项孤立的活动,它一般是在开发人员的领导下,由开发人员与领域专家组成的团队来共同协作。他们共同收集信息,并通过消化而将它组织为有用的形式。信息的原始资料来自领域专家头脑中的知识、现有系统的用户,以及技术团队以前在相关遗留系统或同领域的其他项目中积累的经验。信息的形式也多种多样,有可能是为项目编写的文档,有可能是业务中使用的文件,也有可能来自大量的讨论。早期版本或原型将经验反馈给团队,然后团队对一些解释做出修改。

在传统的瀑布方法中,业务专家与分析员进行讨论,分析员消化理解这些知识后,对其进行抽象并将结果传递给程序员,再由程序员编写软件代码。由于这种方法完全没有反馈,因此总是失败。分析员全权负责创建模型,但他们创建的模型只是基于业务专家的意见。他们既没有向程序员学习的机会,也得不到早期软件版本的经验。知识只是朝一个方向流动,而且不会累积。

有些项目使用了迭代过程,但由于没有对知识进行抽象而无法建立起知识体系。开发人员听专家们描述某项所需的特性,然后开始构建它。他们将结果展示给专家,并询问接下来做什么。如果程序员愿意进行重构,则能够保持软件足够整洁,以便继续扩展它;但如果程序员对领域不感兴趣,则他们只会了解程序应该执行的功能,而不去了解它背后的原理。虽然这样也能开发出可用的软件,但项目永远也不会从原有特性中自然地扩展出强大的新特性。

好的程序员会自然而然地抽象并开发出一个可以完成更多工作的模型。但如果在建模时只是技术人员唱独角戏,而没有领域专家的协作,那么得到的概念将是很幼稚的。使用这些肤浅知识开发出来的软件只能做基本工作,而无法充分反映出领域专家的思考方式。

在团队所有成员一起消化理解模型的过程中,他们之间的交互也会发生变化。领域模型的不断精化迫使开发人员学习重要的业务原理,而不是机械地进行功能开发。领域专家被迫提炼自己已知道的重要知识的过程往往也是完善其自身理解的过程,而且他们会渐渐理解软件项目所必需的概念严谨性。

所有这些因素都促使团队成员成为更合格的知识消化者。他们对知识去粗取精。他们将模型重塑为更有用的形式。由于分析员和程序员将自己的知识输入到了模型中,因此模型的组织更严密,抽象也更为整洁,从而为实现提供了更大支持。同时,由于领域专家也将他们的知识输入到了模型中,因此模型反映了业务的深层次知识,而且真正的业务原则得以抽象。

模型在不断改进的同时,也成为组织项目信息流的工具。模型聚焦于需求分析。它与编程和设计紧密交互。它通过良性循环加深团队成员对领域的理解,使他们更透彻地理解模型,并对其进一步精化。模型永远都不会是完美的,因为它是一个不断演化完善的过程。模型对理解领域必须是切实可用的。它们必须非常精确,以便使应用程序易于实现和理解。1.3 持续学习

当开始编写软件时,其实我们所知甚少。项目知识零散地分散在很多人和文档中,其中夹杂着其他一些无关信息,因此我们甚至不知道哪些知识是真正需要的知识。看起来没什么技术难度的领域很可能是一种错觉——我们并没意识到不知道的东西究竟有多少。这种无知往往会导致我们做出错误的假设。

同时,所有项目都会丢失知识。已经学到了一些知识的人可能干别的事去了。团队可能由于重组而被拆散,这导致知识又重新分散开。被外包出去的关键子系统可能只交回了代码,而不会将知识传递回来。而且当使用典型的设计方法时,代码和文档不会以一种有用的形式表示出这些来之不易的知识,因此一旦由于某种原因人们没有口头传递知识,那么知识就丢失了。

高效率的团队需要有意识地积累知识,并持续学习[Kerievsky 2003]。对于开发人员来说,这意味着既要完善技术知识,也要培养一般的领域建模技巧(如本书中所讲的那些技巧)。但这也包括认真学习他们正在从事的特定领域的知识。

那些善于自学的团队成员会成为团队的中坚力量,涉及最关键领域的开发任务要靠他们来攻克(有关这方面的更多内容,参见第15章)。这个核心团队头脑中积累的知识使他们成为更高效的知识消化者。

读到这里,请先停一下来问自己一个问题。你是否学到了一些PCB设计知识?虽然这个示例只对该领域作了些表面处理,但当讨论领域模型时,仍会学到一些知识。我学习了大量知识,但并没有学习如何成为一名PCB工程师,因为这不是我的目的。我的目的是学会与PCB专家沟通,理解与应用有关的主要概念,并学会检查所构建的内容是否合理。

事实上,我们的团队最终发现探针仿真并不是一项重要的开发任务,因此最后彻底放弃了这个功能。连同它一起删除的还有模型中的一些部分,这些部分只是帮助我们理解如何通过元件推动信号以及如何计算跳数。这样,应用程序的核心就转移到了别处,而且模型也随之改变,将新的重点作为核心。在这个过程中,领域专家们也学到了很多东西,而且更加清楚地理解了应用程序的目标(第15章会更深入地讨论这些问题)。

尽管如此,那些早期工作还是非常重要的。关键的模型元素被保留下来,而更重要的是,早期工作启动了知识消化的过程,这使得所有后续工作更加高效:团队成员、开发人员和领域专家等都学到了知识,他们开始使用一种公共的语言,而且形成了贯穿整个实现过程的反馈闭环。这样,一个发现之旅悄然开始了。1.4 知识丰富的设计

通过像PCB示例这样的模型获得的知识远远不只是“发现名词”。业务活动和规则如同所涉及的实体一样,都是领域的核心,任何领域都有各种类别的概念。知识消化所产生的模型能够反映出对知识的深层理解。在模型发生改变的同时,开发人员对实现进行重构,以便反映出模型的变化,这样,新知识就被合并到应用程序中了。

当我们的建模不再局限于寻找实体和值对象时,我们才能充分吸取知识,因为业务规则之间可能会存在不一致。领域专家在反复研究所有规则、解决规则之间的矛盾以及以常识来弥补规则的不足等一系列工作中,往往不会意识到他们的思考过程有多么复杂。软件是无法完成这一工作的。正是通过与软件专家紧密协作来消化知识的过程才使得规则得以澄清和充实,并消除规则之间的矛盾以及删除一些无用规则。

示例 提取一个隐藏的概念

我们从一个非常简单的领域模型开始学习,基于此模型的应用程序用来预订一艘船在一次航程中要运载的货物,如图1-8所示。图1-8

我们规定这个应用程序的任务是将每件货物(Cargo)与一次航程(Voyage)关联起来,记录并跟踪这种关系。现在看来一切都还算简单。应用程序代码中可能会有一个像下面这样的方法:

由于总会有人在最后一刻取消订单,因此航运业的一般做法是接受比其运载能力多一些的货物。这称为“超订”。有时使用一个简单的容量百分比来表示,如预订110%的载货量。有时则采用复杂的规则——主要客户或特定种类的货物优先。

这是航运领域的一个基本策略,从事航运业的业务人员都知道它,但在软件团队中可能不是所有技术人员都知道这条规则。

需求文档中包含下面这句话:

允许10%的超订。

现在,类图就应该像图1-9这样,代码如下:图1-9

现在,一条重要的业务规则被隐藏在上面这段方法代码的一个卫语句[2]中。第4章将介绍LAYERED ARCHITECTURE,它会帮助我们将超订规则转移到领域对象中,但现在我们主要考虑如何把这条规则更清楚地表达出来,并让项目中的每个人都能了解到它。这将使我们得到一个类似的解决方案。

(1) 如果业务规则如上述代码所写,不可能有业务专家会通过阅读这段代码来检验规则,即使在开发人员的帮助下也无法完成。

(2) 非业务的技术人员很难将需求文本与代码联系起来。

如果规则更复杂,情况将更糟。

我们可以改变一下设计来更好地捕获这个知识。超订规则是一个策略,如图1-10所示。策略(policy)其实是STRATEGY模式[3][Gamma et al.1995]的别名。我们知道,使用STRATEGY的动机一般是为了替换不同的规则,虽然在这里并不需要这么做。但我们要获取的概念的确符合策略的含义,这在领域驱动设计中是同等重要的动机(参见第12章)。图1-10

修改后的代码如下:

新的Overbooking Policy类包含以下方法:

现在所有人都清楚超订是一个独特的策略,而且超订规则的实现即明确又独立。

现在,我并不建议将这样的精细设计应用到领域的每个细节中。第15章将深入阐述如何关注重点以及如何隔离其他问题或使这些问题最小化。这个例子的目的是说明领域模型和相应的设计可用来保护和共享知识。更明确的设计具有以下优点:

(1) 为了实现更明确的设计,程序员和其他各位相关人员都必须理解超订的本质,明白它是一个明确且重要的业务规则,而不只是一个不起眼的计算。

(2) 程序员可以向业务专家展示技术工件,甚至是代码,但应该是领域专家(在程序员指导下)可以理解的,以便形成反馈闭环。1.5 深层模型

有用的模型很少停留在表面。随着对领域和应用程序需求的理解逐步加深,我们往往会丢弃那些最初看起来很重要的表面元素,或者切换它们的角度。这时,一些开始时不可能发现的巧妙抽象就会渐渐浮出水面,而它们恰恰切中问题的要害。

前面的例子大体上是基于一个集装箱航运项目,这是本书列举的几个项目之一,本书还有几个示例会引用这个项目。本书所举的示例都很简单,即使不是航运专家也能理解它们。但在一个需要团队成员持续学习的真实项目中,要想建立实用且清晰的模型则要求团队成员

试读结束[说明:试读内容隐藏了图片]

下载完整电子书


相关推荐

最新文章


© 2020 txtepub下载