敏捷方法已经成为了当前软件开发的主流模式,可工作的代码(以及自动化测试)被认为是团队最重要的产出。
那么是否不再需要建模了呢?UML真的已死?我并不这么认为。
在本文中,我将探索在敏捷时代,建模方法依然适用并且扮演关键角色的所在。尤其在开发规模扩张到多个团队后,对整个系统的“Big Picture”达成共识将变得非常关键。
敏捷中的“设计”在哪里
虽然代码表现了事实,但它并没有表现事实的全部 – Grady Booch
在开篇部分,我将描述一个使用Scrum的敏捷团队的精简流程。图1展示的是一个经过有意简化的流程,它仅仅保留了关键的部分。
1、列举在“Product Backlog”中的“用户需求” 。
2、开发团队从列表中选取一些需求,并在一个较短的迭代(或者一个Sprint)时间内实现它们。
3、每个Sprint结束后,团队创建了“可工作的软件”(或者是“增量内容”),表现为“产品代码”与“测试代码”。
在这个最精简的框架中,团队所知的是“Product Backlog”上的“用户需求”,而产出的是代码(“产品代码”与“测试代码“)所呈现的“可工作的软件”。这里并未显式地描述两者间的设计成果。理想的情况是,这个Sprint所产生的设计意图作为团队产出的一部分,已经体现在最终发布的代码中了。但有些信息是无法用代码直接表达的。Scrum本身是一种流程框架,它并不有意图地表现任何设计的部分,但团队中仍然少不了各种各样的设计工作。
正如Grady Booch所说,“代码表现了事实,但它并没有表现事实的全部”。因此如果有些信息无法以代码形式进行表述或交流,我们将把这些知识财富保留在哪里呢?这正是本文尝试解答的疑问。
写文档就不是敏捷了吗?
建模是为了进行对话 – Craig Larman与Bas Vodde
对以上问题的一个回答也许是:“在我们的脑海中!”。每日例会、结对编程、设计研讨等等互动性的实践以一种同步的方式持续地将团队成员的思想进行归总。但是当团队开始扩张、或分布在不同地域、或有成员离开团队时,“脑海中的模型”的内容会被很快遗忘。我们需要以文档的形式将对系统的共识保存起来,以分享那些仅用代码形式不易保留及沟通的信息。
敏捷方法清晰地阐述了一个观点,即对话的价值更胜于文档,因此编写繁重的设计文档(它经常会重复代码中表述的信息)并非正确的途径。我们应该采取的途径是只编写那些使对话更有效的文档,它应该尽可能保持最简单的模型集合,与代码产生互补作用。
模型胜过代码的一个方面是它的可视化表达能力。换句话说,在某些情况下,文字是一种糟糕的交流媒介。图2表现了文字交流会失效的一种情况(感谢Jeff Patton为我推荐了这本书)。
我猜测,这个“悲剧的”蛋糕是在给蛋糕厂家的电话答录机留言时的误解造成的(译注:订购者的原意是在“Best wishes Suzanne”这一句下方(Underneath that)加上“We will miss you”)、如果订购者能够用一张简单的图片(配上文字)来表达他的意图,那绝对可以避免这个悲剧。有些时候,真的是“一图胜千言”。
那么,在敏捷团队中,该怎样为了实现目的而有效地使用模型呢?
敏捷建模以及两种模型
保留建模,但去掉那些官僚主义的东西 – Scott Ambler
“敏捷建模”是一系列可以应用在你的团队里以实现有效建模与文档的实践。这一方法遵循了敏捷价值与原则,并且使你依然可以从建模中受益。这里要强调的是,建模是为了更好地开展对话,而不是仅仅为了信息传递。
我们的软件开发团队已经在使用敏捷建模的实践与原则,并且发现模型起到的最主要作用是以可视化方式交流系统设计的“Big Picture”或者说是“鸟瞰图”,这一点是单凭代码难以表现的。缺乏模型的支持,整个团队的工作就好似盲人摸象,“四个盲人在摸象,每个人只能感觉到他所触摸的那一部分,最终花费了许多时间,才把这些部分统一成一个整体:一头大象!”
我的建议是,保持并维护一个反映“Big Picture”的模型,它应该包括以下内容:
1、系统的“架构”,以便于团队对于整个系统结构有一个初步的认识。
2、“领域模型”将帮助团队理解问题领域中的各种概念。
3、“关键用例”有助于发现系统的典型用户,并了解他们如何从系统中受益。
以上每一点对于建立对系统的理解都是非常关键的。缺少了模型的支持,你又怎样确保达到这种理解程度呢?如果你的代码库内容庞大,但只基于你所看到的一小部分不完整的内容推测系统的“Big Picture”,那么你将会对于如何维护整个代码库做出糟糕的选择。“Big Picture”不仅反映了团队对于系统的理解的模型,并且也构成了他们在对话中以及在代码中所用到的词汇,例如代码的结构,以及各种编程结构的命名方式,包括类、方法、变量、字段、数据、配置等等。换句话说,这些模型不仅对于团队建立起对系统整体的共识非常重要,同时也促使团队保持代码库的一致性与可维护性。
另一方面,某些临时模型的信息以代码形式记录下来之后,这些模型就会被丢弃。这一类别的模型包含画在白板上的松散类图,它通常包含一些类以及描述这些类的交互情况的时序图。这种模型将鼓励你开展有效的对话,其中产生的各种信息将以代码形式固定下来,并随后丢弃。
因此这一观点的核心是将模型分为两种类别 –应保留模型将作为资产保留维护,而临时模型则促进了有效的对话。我们将前者称为“应保留模型”或“保留模型”,而将后者称为“临时的模型”或“临时模型”(图3)。要注意的是,“保留模型”并不代表“已冻结的”,而是代表“应长期维护的”,并且将持续发展。在下一节中,我将为敏捷团队推荐三种“保留模型”。
应保留的模型
在不同的情况下(人员数量、系统的重要度、需求的稳定程度、是否企业级系统或者嵌入式系统),应保留的模型种类也不同。基于我的经验来看,以下几种模型可以成为应保留的模型:
1、架构的类图/包图
2、领域模型的类图/实体关系图
3、关键用例的用例图 + 时序图/协作图
虽然我们主要使用的是UML方式,但你也不必刻意遵守严格的UML规格。之所以选择它是因为它提供了种类丰富的标准图形,并且关于它的各种教材也已经很多了。另外,用于表现数据与流程的实体关系图(ERD)与数据流图(DFD)也会由于相同的原因而在某些地方使用到。
图4以图形方式显示了这三种应保留的模型的角色。简单地说,“架构”表现了系统结构,“领域模型”表现了问题领域中的关键概念,而“关键用例”则为系统的使用方式提供了示例。
接下来我会以我的某个团队中所使用的真实示例与图片,分别解释这三个模型。
1. 架构的类图/包图
架构是对整个系统的一种结构化展示,它经常以类图或者包图的典型方式显现全局的层次(tier或layer)。举例来说,一个包含用户界面与数据库的应用程序,经常按照从用户界面到数据库进行水平分层,用例将跨越这些层以实现它的功能。
像“MVC”(模型-视图-控制器)这样的架构模式也可以作为一个全局架构。图5是某个架构的包图的示例,很显然它是基于某种MVC架构设计的。
团队中的每个人都应该理解架构中的各个部件的角色与作用,这样才能保证团队成员所编写的代码处于架构中的正确位置。
在这张图的多个包之间显式地表现了“依赖项”的存在,这样做是为了避免不必要的耦合或循环依赖。因为从架构的视角来说,内部包的循环依赖是最糟糕的问题,它会提高测试的难度,并且增加编译的时间。
2. 领域模型的类图或实体关系图
领域模型描述了应用程序所在的领域中的概念分类。在人员交流的层面上,领域模型的词汇集将成为“Ubiquitous Language”,并且在全体项目相关人员之间使用,包括用户、领域专家、业务分析师、测试与开发者。
而在编码的层面上,领域模型对于为各种编程结构选择合适的命名也是非常重要的,包括类、数据、方法以及其它各种协定。概念分类(经常称为“实体”)中的很大一部分会被映射到数据库中的某个持久化数据结构,并且它们的生命周期往往会长于应用程序本身。通常来说,如果你的应用程序选择了某种“MVC”架构,那么你的领域模型(或者说实体)会驻留于你的逻辑架构中的“M”(模型)包内。而在Ruby on Rails类型的应用程序中,实体关系图将会更加适用于表达某个领域模型,因为模型与关系型数据库的关系更加直接与紧密了。
还要注意的一点是,领域模型是随着时间进化的。由于领域处于问题理解与通信的核心地带,因此怎样维护在团队中(或更大一些的范围,如整个社区)不断发展的领域模是一个很大的主题,这一点在Eric Evans的领域驱动设计(DDD)一书中进行了详细的讨论。
图6是某个以类图方式表现的领域模型示例,它用一张图表现了整个领域。
3. 关键用例的用例图与时序图/协作图
关键用例经常从用户角度表现系统的使用方式,有两个原因让我们决定将它作为保留模型的一部分。首先,开发者经常会钻进解决方案细节中,而遗忘了系统的用户是谁,以及他们想在系统中完成什么任务。用例能够帮助他们回到用户的视角,同时它也是与用户对话的一种良好的方式(其它文档更适合给技术人员看)。
其次,用时序图或协作图方式表现关键用例以及它们的运作过程对于开发者来说是一个很好的示范。它们描述了系统架构中不同层次的对象如何协同工作,以完成用户的目标。它表现了一个从用户界面到数据库的纵切面的实际示例,并且告诉你如何在整个架构中实现某个用例。
关键用例不必完整到覆盖所有的情况,只需挑选那些典型的用例,并使它们保持简单。
图7是用例图的一个示例,它使得系统的用户与经典场景更加清晰化。它不需要非常完整,但应该表现出系统的上下文情况。标为黄色的用例(“创建类图”)被选为某个用例示例,具体的设计分解体现在图8的协作图中。通过这一示例,团队能够将他们对架构图与领域模型图(表现在图5与图6中)如何完成关键用例中所描述的特性的理解进行分享。请再次看看图4中架构、领域模型与关键用例这三者的关联吧。
你可以在画这些图时使用工具,以简化维护工作,然后将它们打印到一张大纸上并粘贴到墙上。这面墙就会成为建模研讨会的讨论场所了(我在下一节中会很快讨论到这部分)。
扩张
极限编程团队先实现后分解(conquer and divide),而不是相反(divide and conquer) – Kent Beck
在一个不到10个开发者的小型团队中,你或许不需要在代码库之外再去维护某些模型了。但是当开发规模扩大到多个团队之后,你就会从建模中获得更大的好处。
但请记住,不要仅仅为了将知识传递给某个你不认识的人,而花费太多时间去准备繁重的文档(而没有编写任何代码)。即使团队的规模变得更大了,你也应该尝试着首先按照某种简单的垂直划分的方式实现某个关键用例,将它作为架构的一个示例。随后,你将这个示例的可工作代码以及“保留模型”的知识分享给某个子团队。换句话说,不要尝试“先分解后实现”某个用例(在桌面上分解问题,随后将贴在墙上的规格说明丢给某个子团队并让他们实现),而是让他们“先实现后分解”(关于这一话题的更多讨论,请阅读Craig Larman与Bas Vodde的文章——《大规模敏捷设计与架构》)。
这里,我将描述多个团队如何使用“保留模型”互相交流整个系统的“Big Picture”。首先有一个名为“Tiger”的团队,它有不足10名成员,他们将尝试实现第1部分内容。在首次成功的实现之后,他们就可以将之前所述的各种“保留模型”作为良好的文档,用以互相交流对系统的理解。在Sprint 1中,Tiger团队首先完成了第一个关键用例,它建立起了第一个可作为范例的架构设计,并以此建立了软件架构文档(SAD),作为保留模型的版本1.0。不要把这些模型当做一个规格说明,而是把它们当作建立理解和共识的基础平台。再一次记住,不要仅仅把这些文档的信息简单地传递给子团队。
沟通设计意图并达成共识的最好方式,就是与子团队一起开展一次建模研讨会(图9)
在建模研讨会上,Tiger团队的一名成员Ken(见图9)首先解释了SAD,并简单描述了各个模型。在问答阶段,他将核心思想与系统的结构关联在一起。接下来,他以关键用例为例,解释了各个系统组件如何协作以实现某个用户目标,并使用这些组件设计出一至两个关键用例,在实现时可以使用临时模型,并可实行结对编程。
无需使SAD显得非常完整,以研讨会的形式建立起共识,抛弃信息传递的做法,并开展内容详实的对话,这就非常好了。
反馈是子团队研讨会的一个重要部分。图9中,子团队1的Ken与子团队2的Tom将结果反馈给Tiger团队,并与其他成员一起讨论如何改善这些保留模型。图11显示了研讨会过程中众人的各种想法,包括了众人的理解与反馈。
这种研讨会的方式需要不断保持。并且以建模的过程,而不是最终的模型作为促进理解的方式。请记住,要将“模型”作为一个动词,通过建模以达到对话的目的。
人是知识的传送带
许多设计思想都存在于种族记忆(tribal memory)中——Grady Booch
对于开发与维护系统来说,尽管保留模型以及代码库能够覆盖大部分的必要知识,但仍有一部分隐藏的知识保留在团队成员的脑海中。Grady Booch将其称为种族记忆。
日本有一所神庙名叫伊势神宫。其中有一座神庙建筑,建立在两个相邻场地的其中一块,并且两块场地具有同样的大小。每隔20年,他们就会把建筑从一处移至另一处。不仅神庙经过了重新建造,并且它的庄严外表与宝库也得到了翻新。这一仪式将建筑的知识从这一代神庙传到下一代。虽然没有任何设计文档,但通过一起进行重新建造的过程,技术、工具以及实践知识都得以从这一代传递到下一代。记住,“经验是最好的老师”,并且只有在众人齐心协力时,才能够传递最丰富的设计知识。
建模的小提示
理解了我所介绍的思想与经验之后,在最后我将提供一些小提示,你可以在每日的建模会议或研讨会中用到它。
“逆向与模型”,许多UML工具支持“逆向工程”这一特性,它能够将代码即时转换为图形。其中某些工具支持从源代码中进行拖放操作,甚至直接支持Github代码库的URL。你还可以将从代码中逆向工程所得的包与类作为进一步建模的基础。这样,你就不仅仅可以从保留模型开始建模,还能以代码库所生成的模型作为建模的基础。
“打印与绘制”,如同之前所说的,一个具有良好互动性的建模研讨会应该在墙上(或者桌面上)贴上几张大纸,并使用这些纸张开展对话,将心得与反馈直接绘制在这些打印纸上(图11)。
“投影仪与白板”,在研讨会上分享模型的另一种方式是使用投影仪与白板,以模拟“打印与绘制”的情况。使用投影仪在白板上展示保留模型,并在白板上绘制各种留言,或者粘上便条贴。
“回顾”,我在之前推荐了一些我认为最简单的保留模型,但每个团队都可能有不同的情况。因此建议首先从我的建议,或者你觉得合适的模型作为第一部分的保留模型。并在每个Sprint之后为你选择的模型作一个回顾,讨论一下哪些模型起到了良好的作用而哪些没有,以及另外还需要哪些模型。总之,找到你的保留模型。
“思维导图建模“,在与用户交流时、做计划时以及其它一些氛围轻松但非常重要的工作时,UML以及其它软件工程图的作用并不理想,这时就可以使用思维导图了。具体细节请阅读《敏捷建模与思维导图和UML》。以下这个示例思维导图是我为某个用户创建的,名为“用户故事探索”。
结论
在本文中,我详细解释了建模工作将如何适应诸如Scrum这样的敏捷开发框架,并且建议了几种你可以在产品的整个生命周期中保留的模型。我还推荐你开展一个建模研讨会以交流设计意图,并建立起对系统的共识。尤其当团队扩张到多个子团队之后,这种实践会变得愈加重要。
CIO之家 www.ciozj.com 公众号:imciow