敏捷开发以用户的需求进化为核心,采用
迭代、循序渐进的方法进行
软件开发。在敏捷开发中,软件项目在构建初期被切分成多个子项目,各个子项目的成果都经过测试,具备可视、可集成和可运行使用的特征。换言之,就是把一个大项目分为多个相互联系,但也可独立运行的小项目,并分别完成,在此过程中软件一直处于
可使用状态。
原则
敏捷建模(AM)定义了一系列的
核心原则和辅助原则,它们为
软件开发项目中的建模实践奠定了基石。其中一些原则是从XP中借鉴而来,在Extreme Programming Explained中有它们的详细描述。而XP中的一些原则又是源于众所周知的
软件工程学。复用的思想随处可见。基本上,本文中对这些原则的阐述主要侧重于它们是如何影响着建模工作;这样,对于这些借鉴于XP的原则,我们可以从另一个角度来看待。
核心原则
◆主张简单
当从事开发工作时,主张最简单的解决方案就是最好的解决方案。不要过分构建(overbuild)软件。用AM的说法就是,如果现在并不需要这项额外功能,那就不要在模型中增加。要有这样的勇气:不必要对这个系统进行过分的
建模(over-model),只要基于现有的需求进行建模,日后需求有变更时,再来重构这个系统。尽可能的保持模型的简单。
需求时刻在变,人们对于需求的理解也时刻在变。项目进行中,Project stakeholder可能变化,会有新人加入,也会有旧人离开。Project stakeholder的观点也可能变化,努力的目标和成功标准也有
可能发生变化。这就意味着随着项目的进行,
项目环境也在不停的变化,因此开发方法必须要能够反映这种现实。
即便团队已经把一个能够运转的系统交付给用户,项目也还可能是失败的--实现项目投资者的需求,其中就包括系统应该要有足够的
鲁棒性(robust ),能够适应日后的扩展。就像Alistair Cockburn常说的,进行软件开发的竞赛时,第二个目标就是准备下一场比赛。可持续性可能指的是系统的下一个主要发布版,或是正在构建的系统的运转和支持。要做到这一点,不仅仅要构建高质量的软件,还要创建足够的文档和支持材料,保证下一场比赛能有效的进行。要考虑很多的因素,包括现有的团队是不是还能够参加下一场的比赛,下一场比赛的环境,下一场比赛对组织的重要程度。简单的说,在开发的时候,要能想象到未来。
◆递增的变化
和建模相关的一个重要概念是不用在一开始就准备好一切。实际上,就算想这么做也不太可能。而且,不用在模型中包容所有的细节,只要足够的细节就够了。没有必要试图在一开始就建立一个囊括一切的模型,只要开发一个小的模型,或是概要模型,打下一个基础,然后慢慢的改进模型,或是在不再需要的时候丢弃这个模型。这就是递增的思想。
◆令投资最大化
项目投资者为了开发出满足自己需要的软件,需要投入时间、金钱、设备等各种资源。投资者应该可以选取最好的方式投资,也可以要求团队不浪费资源。并且,他们还有最后的发言权,决定要投入多少的资源。
◆有目的的建模
对于自己的产出,例如模型、
源代码、文档,很多开发人员不是担心它们是否够详细,就是担心它们是否太过详细,或担心它们是否足够正确。不应该毫无意义的
建模,应该先问问,为什么要建立这个产出,为谁建立它。和建模有关,也许应该更多的了解软件的某个方面,也许为了
保证项目的顺利进行,需要和高级经理交流方法,也许需要创建描述系统的文档,使其他人能够操作、维护、改进系统。如果连为什么建模,为谁建模都不清楚,又何必继续烦恼下去呢?首先,要确定建模的目的以及模型的受众,在此基础上,再保证模型足够正确和足够详细。一旦一个模型实现了目标,就可以结束工作,把精力转移到其它的工作上去,例如编写代码以检验模型的运作。该项原则也可适用于改变现有模型:如果要做一些改变,也许是一个熟知的模式,应该有做出变化的正确理由(可能是为了支持一项新的需求,或是为了重构以保证简洁)。关于该项原则的一个重要暗示是应该要了解受众,即便受众是自己也一样。
◆多种模型
开发软件需要使用多种模型,因为每种模型只能描述软件的单个方面,“要开发现今的商业应用,我们该需要什么样的模型?”考虑到现今的软件的复杂性,你的建模
工具箱应该要包容大量有用的技术(关于产出的清单,可以参阅AM的建模工件)。有一点很重要,你没有必要为一个系统开发所有的模型,而应该针对系统的具体情况,挑选一部分的模型。不同的系统使用不同部分的模型。比如,和家里的修理工作一样,每种工作不是要求你用遍工具箱里的每一个工具,而是一次使用某一件工具。又比如,你可能会比较喜欢某些工具,同样,你可会偏爱某一种模型。有多少的建模工件可供使用呢,如果你想要了解这方面的更多细节,我在Be Realistic About the UML中列出了UML的相关部分,如果你希望做进一步的了解,可以参阅
白皮书The Object Primer -- An Introduction to Techniques for Agile Modeling。
◆高质量的工作
没有人喜欢烂糟糟的工作。做这项工作的人不喜欢,是因为没有成就感;日后负责重构这项工作(因为某些原因)的人不喜欢,是因为它难以理解,难以更新;
最终用户不喜欢,是因为它太脆弱,容易出错,也不符合他们的期望。
◆快速反馈
从开始采取行动,到获得行动的反馈,二者之间的时间至关紧要。和其他人一起开发模型,你的想法可以立刻获得反馈,特别是你的工作采用了共享建模技术的时候,例如白板、CRC卡片或
即时贴之类的基本建模材料。和你的客户紧密工作,去了解他们的需求,去分析这些需求,或是去开发满足他们需求的用户界面,这样,你就提供了快速反馈的机会。
◆软件是你的主要目标
软件开发的主要目标是以有效的方式,制造出满足投资者需要的软件,而不是制造无关的文档,无关的用于管理的工件,甚至无关的模型。任何一项活动(activity ),如果不符合这项原则,不能有助于目标实现的,都应该受到审核,甚至取消。
◆轻装前进
你建立一个工件,然后决定要保留它,随着时间的流逝,这些工件都需要维护。如果你决定保留7个模型,不论何时,一旦有变化发生(新需求的提出,原需求的更新,团队接受了一种新方法,采纳了一项新技术...),你就需要考虑变化对这7个模型产生的影响并采取相应的措施。而如果你想要保留的仅是3个模型,很明显,你实现同样的改变要花费的功夫就少多了,你的灵活性就增强了,因为你是在轻装前进。类似的,你的模型越复杂,越详细,发生的改变极可能就越难实现(每个模型都更“沉重”了些,因此维护的负担也就大了)。每次你要决定保留一个模型时,你就要权衡模型载有的信息对团队有多大的好处(所以才需要加强团队之间,团队和项目投资者之间的沟通)。千万不要小看权衡的
严重性。一个人要想过沙漠,他一定会携带地图,帽子,质地优良的鞋子,水壶。如果他带了几百加仑的水,能够想象的到的所有求生工具,一大堆有关沙漠的书籍,他还能过得去沙漠吗?同样的道理,一个开发团队决定要开发并维护一份详细的需求文档,一组详细的
分析模型,再加上一组详细的架构模型,以及一组详细的
设计模型,那他们很快就会发现,他们大部分的时间不是花在写源代码上,而是花在了更新文档上。
宣言原则
最重要的是通过尽早和不断交付有价值的软件满足客户需要。
我们欢迎需求的变化,即使在开发后期。敏捷过程能够驾驭变化,保持客户的竞争优势。
经常交付可以工作的软件,从几星期到几个月,
时间尺度越短越好。
业务人员和开发者应该在整个项目过程中始终朝夕在一起工作。
围绕斗志高昂的人进行软件开发,给开发者提供适宜的环境,满足他们的需要,并相信他们能够完成任务。
在开发小组中最
有效率也最有效果的信息传达方式是面对面的交谈。
可以工作的软件是进度的主要度量标准。
敏捷过程提倡可持续开发。出资人、开发人员和用户应该总是维持不变的节奏。
对
卓越技术与良好设计的不断追求将有助于提高敏捷性。
简单——尽可能减少工作量的艺术至关重要。
每隔一定时间,团队都要总结如何更有效率,然后相应地调整自己的行为。
成功
随机应变
要达到敏捷的成功—交付支撑业务的最佳软件—软件专家也可以引用这些规则。
自主权
专注于工作,交付正确的软件,而不是被他人的愤怒情绪所影响。
分享经验
构建完美
软件开发流程,并没有统一的模式。但是在这个领域,敏捷技术,加上持续的应用和改进,都能够达到敏捷的成功。
工具
Visual Studio Team Foundation Server (TFS)
TFS,即团队基础服务器(
Team Foundation Server),是
微软应用程序进行生命周期管理的服务器,用于帮助团队在
Visual Studio的协作开发。最近,它进行了升级,包括工作
项目执行改进、
富文本编辑器的改进,以及富文本编辑器中改善的
超链接体验。 TFS中的Kanban面板也做了改善,提升了可以录入和跟踪的项目数量。该服务器现在有一个“
利益相关者”许可,来规范服务器的
访问权限。
Atlassian Jira
Atlassian推出的
Jira是一个很流行的工具,主要用于跟踪
产品开发、帮助团队整理问题、安排事务,以及记录团队行为。它内置的Jira Agile插件使开发人员更容易部署关键敏捷策略,这包括用户故事开发、冲刺模块构建,以及可视化的
团队活动。
Axosoft
Axosoft以前被称为Axosoft OnTime Scrum,这一软件套件有四个
功能模块:Scrum、Bug追踪器、
帮助台和Wiki。它是基于
HTML5构建的,帮助开发
团队管理待办事项列表、发布和冲刺,带有
燃尽图功能,有一个管理
仪表板用于跟踪编码和修改BUG的时间。
LeanKit
使用 LeanKit的团队可以看到工作负载的分布并导出历史数据。最近 LeanKit 进行了一次升级,包含
单点登录功能和附加报告功能,从而提供更
细粒度的数据详细信息。
Planbox
Planbox 敏捷
管理工具通过燃尽图跟踪进程,集成客户反馈,它的
目标人群很广泛。最近它对应用的前端和后端都做了升级,添加了更强大的报告功能和新
仪表盘,来提升项目速度。它所具有的时间跟踪特性和工具允许用户得到所有他们在Planbox产生的数据。
实践
敏捷建模(AM)在AM原则的基础上定义了一组核心实践(practice)和补充实践,其中的某些实践已经是
极限编程(XP)中采用了的,并在 Extreme Programming Explained一书中有详细的论述,和AM的原则一样,我们在描述这组实践时,将会注重于建模的过程,这样你可以从另外一个角度来观察这些已或XP采用的素材。
核心实践
◆
Stakeholder的积极参与 我们对XP的现场客户(On-Site Customer)的概念做了一个扩充:开发人员需要和用户保持现场的接触;现场的用户要有足够的权限和能力,提供建构中的系统相关的信息;及时、中肯的做出和需求相关的决策;并决定它们的优先级。AM把XP的“现场客户”实践扩展为“使project stakeholder积极参与项目”,这个project stakeholder的概念包括了直接用户、他们的经理、高级经理、操作人员、支持人员。这种参与包括:高级经理及时的资源安排决策,高级经理的对项目的公开和私下的支持,需求
开发阶段操作人员和支持人员的积极参与,以及他们在各自领域的相关模型。
◆正确使用artifact 每个artifact都有它们各自的适用之处。例如,一个UML的
活动图(activity diagram)适合用于描述一个业务流程,反之,你数据库的静态结构,最好能够使用物理数据(physical data)或数据模型(persistence model)来表示。在很多时候,一张图表比源代码更能发挥作用,一图胜千言,同样,一个模型也比1K的源代码有用的多,前提是使用得当(这里借用了 Karl Wieger的Software Requirements中的词汇)。因为你在研究
设计方案时,你可和同伴们和在白板上画一些图表来讨论,也可以自己坐下来开发一些代码样例,而前一种方法要有效的多。这意味着什么?你需要了解每一种artifact的长处和短处,当你有众多的模型可供选择的时候,要做到这一点可没有那么容易。
◆
集体所有制 只要有需要,所有人都可以使用、修改项目中的任何模型、任何artifact。
◆测试性思维 当你在建立模型的时候,你就要不断的问自己,“我该如何测试它?”如果你没办法测试正在开发的软件,你根本就不应该开发它。在现代的各种
软件过程中,测试和
质保(quality assurance)活动都贯穿于整个
项目生命周期,一些过程更是提出了“在编写软件之前先编写测试”的概念(这是XP的一项实践:“测试优先”)。
◆并行创建模型 由于每种模型都有其长处和短处,没有一个模型能够完全满足建模的需要。例如你在收集需求时,你需要开发一些基本
用例或用户素材,一个基本用户界面原型,和一些
业务规则。
再结合实践切换到另外的Artifact,,敏捷建模者会发现在任何时候,同时进行多个模型的开发工作,要比单纯集中于一个模型要有效率的多。
◆创建简单的内容 你应该尽可能的使你的模型(需求、分析、架构、设计)保持简单,但前提是能够满足你的project stakeholder的需要。这就意味着,除非有充分的理由,你不应该随便在模型上画蛇添足--如果你手头上没有
系统认证的功能,你就不应该给你的模型增加这么一个功能。要有这样的勇气,一旦被要求添加这项功能,自己就能够马上做到。这和XP的实践“简单设计”的思想是一样的。
◆简单地建模 当你考虑所有你能够使用的图表(
UML图、
用户界面图、数据模型等)时,你很快会发现,大部分时候你只需要这些图表符号的一部分。一个简单的模型能够展示你想要了解的主要功能,例如,一个类图,只要能够显示类的主要责任和类之间的关系就已经足够了。不错,编码的标准告诉你需要在模型中加入框架代码,比如所有的get和set操作,这没有错,但是这能提供多少价值呢?恐怕很少。
◆公开展示模型 你应当公开的展示你的模型,模型的载体被称为“建模之墙”(modeling
wall)或“奇迹之墙(wall of wonder)”。这种做法可以在你的团队之间、你和你的project stakeholder之间营造出开放诚实的沟通氛围,因为当前所有的模型对他们都是举手可得的,你没有向他们隐藏什么。你把你的模型贴到建模之墙上,所有的开发人员和project stakeholder都可以看建模之墙上的模型,建模之墙可能是
客观存在的,也许是一块为你的架构图指定的白板,或是
物理数据模型的一份
打印输出,建模之墙也可能是虚拟的,例如一个存放扫描好的图片的internet网页。如果你想要多了解一些相关的资料,你可以看看Ellen Gottesdiener的Specifying Requirements With a Wall of Wonder。
◆切换到另外的Artifact 当你在开发一个artifact(例如
用例、CRC卡片、顺序图、甚至源码),你会发现你卡壳了,这时候你应当考虑暂时切换到另一个artifact。每一个artifact都有自己的长处和短处,每一个artifact都适合某一类型的工作。无论何时你发现你在某个artifact上卡壳了,没办法再继续了,这就表示你应该切换到另一个artifact上去。举个例子,如果你正在制作基本用例,但是在描述
业务规则时遇到了困难,你就该试着把你的注意力转移到别的artifact上去,可能是基本用户界面原型、CRC模型,可能是业务规则、系统用例、或变化案例。切换到另一个artifact上去之后,你可能就立刻不再卡壳了,因为你能够在另一个artifact上继续工作。而且,通过改变你的视角,你往往会发现原先使你卡壳的原因。
◆小增量建模 采用
增量开发的方式,你可以把大的工作量分成能够发布的小块,每次的增量控制在几个星期或一两个月的时间内,促使你更快的把
软件交付给你的用户,增加了你的敏捷性。
◆和他人一起建模 当你有目的建模时你会发现,你建模可能是为了了解某事,可能是为了同他人交流你的想法,或是为了在你的项目中建立起共同的愿景。这是一个
团体活动,一个需要大家有效的共同工作才能完成的活动。你发现你的开发团队必须共同协作,才能建立一组核心模型,这对你的项目是至关重要的。例如,为了建立系统的映像和架构,你需要和同组成员一起建立所有人都赞同的解决方案,同时还要尽可能的保持它的简单性。大多数时候,最好的方法是和另一些人讨论这个问题。
◆用代码验证 模型是一种抽象,一种能够正确反映你正在构建的系统的某个方面的抽象。但它是否能运行呢?要知道结果,你就应该用代码来验证你的模型。你已经用一些
HTML页面建立了接受付款地址信息的草图了吗?编码实现它,给你的用户展示最终的用户界面,并获取反馈。你已经做好了表示一个复杂
业务规则逻辑的UML顺序图了吗?写出测试代码,
业务代码,运行测试以保证你做的是对的。永远也别忘了用迭代的方法开发软件(这是大多数项目的标准做法),也别忘了建模只是众多任务中的一个。做一会儿建模、做一会儿编码、做一会儿测试(在其它的活动之中进行)。
◆使用最简单的工具 大多数的模型都可以画在白板上,纸上,甚至纸巾的背面。如果你想要保存这些图标,你可以用
数码相机把它们拍下来,或只是简单的把他们转录到纸上。这样做是因为大多数的图表都是可以扔掉的,它们只有在你画出模型并思考一个问题的时候才有价值,一旦这个问题被解决了它们就不再有意义了。这样,白板和标签往往成为你建模工具的最佳选择:使用画图工具来创建图表,给你重要的project stakeholder看。只有建模工具能够给我们的编程工作提供价值(例如代码自动生成)时才使用建模工具。你可以这样想:如果你正在创建简单的模型,这些模型都是可以抛弃的。你建模的目的就是为了理解,一旦你理解了问题,模型就没有存在的必要了,因此模型都是可以丢弃的,这样,你根本就不必要使用一个复杂的建模工具。
补充实践
◆使用建模标准 这项实践是从XP的编码标准改名而来,基本的概念是在一个软件项目中开发人员应该同意并遵守一套共同的建模标准。遵守共同的编码惯例能够产生价值:遵守你选择的编码指南能够写出干净的代码,易于理解,这要比不这么做产生出来的代码好得多。同样,遵守共同的建模标准也有类似的价值。可供选择的建模标准有很多,包括
对象管理组织(OMG)制定的
统一建模语言ML,它给通用的面向对象模型定义了符号和语义。UML开了一个好头,但并不充分-就像你在Be Realistic About The UML中看到的,UML并没有囊括所有可能的的建模artifact。而且,在关于建立清楚可看的图表方面,它没有提供任何建模风格指南。那么,风格指南和标准之间的差别在何处呢。对源代码来说,一项标准可能是规定属性名必须以attributeName的格式,而风格指南可能是说在一个单元中的一段
控制结构(一个
if语句,一段循环)的代码缩进。对模型来说,一项标准可能是使用一个长方形对类建模,一项风格指南可能是图中子类需要放在父类的下方。
◆逐渐应用模式 高效的建模者会学习通用的
架构模式、
设计模式和分析模式,并适当的把它们应用在模型之中。然而,就像Martin Fowler在Is Design Dead中指出的那样,开发人员应当轻松的使用模式,逐渐的应用模式。这反映了简单的价值观。换言之,如果你猜测一个模式可能适用,你应当以这样的方式建模:先实现目前你需要的最小的范围,但你要为日后的重构留下伏笔。这样,你就以一种可能的最简单的方式实现了一个羽翼丰满的模式了。就是说,不要超出你的模型。举一个例子,在你的设计中,你发现有个地方适合使用
GoF的Strategy模式,但这时候你只有两个算法要实现。最简单的方法莫过于把算法封装为单独的类,并建立操作,能够选择相应的算法,以及为算法传递相关的输入。这是Strategy模式的部分实现,但你埋下了伏笔,日后如有更多的算法要实现,你就可以重构你的设计。并没有必要因为Strategy模式需要,就建立所有的框架。这种方法使你能够轻松的使用模式。
◆丢弃临时模型 你创建的大部分的模型都是临时使用的模型--
设计草图,低精度原型,索引卡片,可能架构/设计方案等等--在它们完成了它们的目的之后就再不能提供更多的价值了。模型很快就变得无法和代码同步,这是正常的。你需要做出决定:如果“同步更新模型”的做法能够给你的项目增添价值的话,那就同步更新模型;或者,如果更新它们的投入将抵消它们能够提供的所有价值(即负收益),那就丢弃它们。
◆合同模型要正式 在你的系统需要的信息资源为外部组织所控制的时候,例如数据库,旧有系统和
信息服务,你就需要合同模型。一个合同模型需要双方都能同意,根据时间,根据需要相互改变。合同模型的例子有
API的细节文档,存储形式描述,XML DTD或是描述共享数据库的物理数据模型。作为法律合同,合同模型通常都需要你投入重要资源来开发和维护,以确保它的正确、详细。你的目标是尽量使你系统的合同模型最少,这和XP的原则traveling light是一致的。注意你几乎总是需要电子工具来建立合同模型,因为这个模型是随时需要维护的。
◆为交流建模 建模的次要原因是为了和团队之外的人交流或建立合同模型。因为有些模型是给团队之外的客户的,你需要投入时间,使用诸如文字处理器,画图
工具包,甚至是那些“
被广告吹得天花乱坠”的CASE工具来美化模型。
◆为理解建模 建模的最重要的应用就是探索
问题空间,以识别和分析系统的需求,或是比较和对照可能的设计
选择方法,以识别可能满足需求的、最简单的解决方案。根据这项实践,你通常需要针对软件的某个方面建立小的、简单的图表,例如类的生命周期图,或屏幕顺序,这些图表通常在你完成目的(理解)之后就被丢弃。
◆重用现有的资源 这是
敏捷建模者能够利用的信息财富。例如,也许一些分析和设计模式适合应用到系统上去,也许你能够从现有的模型中获利,例如
企业需求模型,业务
过程模型,物理数据模型,甚至是描述你用户团体中的系统如何部署的模型。但是,尽管你常常搜索一些比较正确的模型,可事实是,在大多数组织中,这些模型要么就不存在,要么就已经过期了。
◆非到万不得已不更新 你应当在你确实需要时才更新模型,就是说,当不更新模型造成的代价超出了更新模型所付出的代价的时候。使用这种方法,你会发现你更新模型的数量比以前少多了,因为事实就是,并不是那么完美的模型才能提供价值的。我家乡的街道图已经使用了5年了,5年我自己街道并没有改变位置,这张地图对我来说还是有用的。不错,我可以买一张新地图,地图是每年出一次的,但为什么要这么麻烦呢?缺少一些街道并没有让我痛苦到不得不投资买一份新地图。简单的说,当地图还管用的时候,每年花钱买新地图是没有任何意义的。为了保持模型、文档和源代码之间的同步,已经浪费了太多太多的时间和金钱了,而同步是不太可能做到的。时间和金钱投资到新的软件上不是更好吗?
确实不错的主意
以下的实践虽然没有包括在AM中,但是可以做为AM的一份补充:
◆重构 这是一项编码实践。重构,就是通过小的变化,使你的代码支持新的功能,或使你的设计尽可能的简单。从AM的观点来看,这项实践可以保证你在编码时,你的设计干净、清楚。重构是XP的一个重要部分。
◆测试优先设计 这是一项开发实践。在你开始编写你的业务代码之前,你要先考虑、编写你的
测试案例。从AM的观点来看,这项实践强制要求你在写代码之前先通盘考虑你的设计,所以你不再需要细节设 计建模了。测试优先设计是XP的一个重要部分。
敏捷开发方法论
AM是一种态度,而不是一个
说明性的过程。AM是
敏捷建模者们坚持的价值观、敏捷建模者们相信的原则、敏捷建模者们应用的实践组成的集合。AM描述了一种建模的风格。当它应用于敏捷的环境中时,能够提高开发的质量和速度,同时能够避免过度简化和不切实际的期望。AM可不是开发的“食谱”,如果你寻觅的是一些细节的指导,如建立
UML顺序图或是画出
用户界面流图,你可以看看在建模Artifacts中列出的许多建模书籍,我特别推荐我的书The Object Primer 2/e(尽管这有失公允)。
AM是对已有方法的补充,而不是一个完整的方法论。AM的主要焦点是在建模上,其次是文档。也就是说,AM技术在你的团队采用
敏捷方法(例如eXtreme Programming,Dynamic Systems Development Method (DSDM),Crystal Clear)的基础上能够提高建模的效果。AM同样也可以用于那些传统过程(例如Unified Process),尽管这种过程较低的
敏捷性会使得AM不会那么成功。
AM是一种有效的
共同工作的方法,能够满足Project Stakeholder的需要。敏捷开发者们和Project Stakeholder进行团队协作,他们轮流在
系统开发中扮演着直接、主动的角色。在“敏捷”的字典中没有“我”这个单词。
AM是有效的,而且也已开始有效。当你学习到更多的AM知识时,有件事对你来说可能不好接受,AM近乎无情的注重
有效性。AM告诉你:要使你的 Project Stakeholder的投资最大化;当有清晰的目的以及需要了解受众的需要时要建立模型或文档;运用合适的工件来记录手头的情形;不论何时都尽可能创建简单的模型。
AM不是灵丹妙药。
敏捷建模是改进众多专家
软件开发成果的有效技术,充其量也就是这样了。它并不是什么了不得的灵丹妙药,能够解决你开发中的所有问题。如果你努力的工作;如果你专注其上;如果打心眼儿里接受它的价值观、它的原则、它的实践;你就可以改进你做为一个开发人员的效果。
AM是面向一般的开发人员的,但并不是要排斥有能力的人。AM的价值观、原则和实践都简单易懂,其中的很多内容,可能你都已经采用或期待多年了。应用AM技术并不是要你去练水上飘,但你需要有一些基本的软件开发技能。AM最难的就是它逼着你去学习更广泛的
建模技术,这是个长期的、持续性的活动。学习建模在一开始可能很难,但你可以试着一次学习一样技术来完成你的学习。
AM并不是要反对文档。文档的创建和维护都会增大
项目涉众的投资。敏捷文档尽可能的简单,尽可能的小,目的只集中在和开发的系统有直接关系的事情上,充分了解受众的需要。
AM也不是要反对CASE工具。
敏捷建模者使用那些能够帮助开发人员提高效果,提升价值的工具。而且,他们还尽力使用那些能够胜任工作的最简单的工具。
敏捷模型
要想了解AM,你需要了解模型和敏捷模型之间的区别。模型是一个抽象的概念,它描述了问题的一个或多个方面,或是处理这个问题可能的解决方案。传统意义上,模型被认为是图表加上相应的文档。然而那些不够直观的artifact,也可以被视为模型,例如CRC卡片集,单条或多条
业务规则的文字描述,或是
业务流程的一段结构化英文描述。一个敏捷模型就是一个刚刚足够好的模型。但是你怎么知道什么时候模型才是刚刚足够好呢?当敏捷模型显现出如下的特性时,它就是刚刚足够好的:
敏捷模型实现了它们的目的。有时你为沟通而建模,或许你需要把你工作的范围告诉高级经理;有时你为理解而建模,或许你需要确定一个
设计策略,实现一组
Java类。一个敏捷模型是否足够好,要看它是不是满足了创建它时的初衷。
敏捷模型是可理解的。敏捷模型要能为其预期听众所理解。使用用户能够理解的业务语言来描述需求模型,反之,技术架构模型则需要使用开发人员熟悉的技术术语。你所使用的建模符号会影响易懂性--如果你的用户不了解UML
用例图中的符号的含义,那用例图对用户就没有任何价值。这样的话,要么使用另一种方法,要么教授用户学习建模技术。风格问题同样也会影响易懂性,例如避免
交叉线。杂乱的图表比清晰的图表难懂。模型的细节程度(见下文),也会影响易懂性,因为相较一个不那么详细的模型来说,一个过于详细的模型要难于理解。简单(见下文)同样是影响易懂性的一个因素。
敏捷模型是足够正确的。模型通常都不需要100%正确,只要足够正确就行了。举个例子,如果一张街道地图漏画了一条街道,或是它标示某条街道是通行的,但你发现它已经关闭维修了,那你会不会扔掉你的地图开始在城里飙车犯罪呢?不太可能。你会考虑更新你的地图,你可能会拿出笔来自己做修改或是去当地的商店买一张最新版的地图(你原来的那张过期了)。也许你还是会接受那张虽不完美但仍可使用的地图,因为它对你来说已经足够好了。你还是可以用这张地图四处转转,因为它还是个正确的模型,标记出了大部分街道的位置。你在发现这张地图不正确的时候,你没有立刻扔掉它,原因是你根本不在乎它是否完美。类似的,当你在需求模型、
数据模型中发现错误的时候,你也会选择更新或是接受--虽不完美但已经足够好了。有些
项目成员能够容忍这种不正确而有些则不能:这取决于项目的特性,每个团队成员的特性,组织的特性。充分正确性既和模型的听众有关,也和你要处理的问题有关。
敏捷模型是足够一致的。一个敏捷模型并不需要和自己(或其它有用的artifact)保持完全的一致。如果一个
用例在它的一个步骤中显式的调用了另一个用例,那么相应的用例图需要用UML的 <> 版型来标记这两个用例之间的关系。然而,你看了看图表,发现它们并没有这样做,天哪!用例和图之间不一致!危险!太危险了!红色警报!快逃命呀!等一下,你的
用例模型是有不一致的地方,但也没到世界末日啊。是的,理想情况下,你的所有artifact最好是能够完全一致,但这通常是不可能的。当我开发一个简单的商用系统时,我通常都可以容忍部分的不一致。但有时我是不能容忍这种不一致的。最有力的佐证就是1999年
NASA发射火星
太空探测器时采用了精密的
测量系统。要树立一个观点,敏捷模型只要足够一致就行了,你通常不需要使用那么完美的模型。
关于正确性和一致性,很明显要考虑权衡问题。如果你要维护一个artifact(我们称之为“保管”),随着时间的流逝,你需要投入资源来更新它。否则它很快会就会过期,对你就没用了。例如,我可以容忍一张地图标错了一两条街道,但是我绝对无法容忍一张地图中四分之三的街道都标错了。这就需要权衡了,进行足够的努力,保证artifact足够正确。过多不必要的努力反而会减缓项目的进度,而投入不足就没有办法保证artifact的有效性。
敏捷模型有足够的细节。一张
路线图并不需要标记出每条街道上的每栋房子。那会有太多的细节,使得地图难以使用。然而,在修路的时候,我想施工人员一定会有这条街道的详细地图,包括每幢建筑、
下水道、电线盒等足够的细节,这样的地图才是有用的。但是这张地图并不用标记出每个院子和通向它们的路线。因为这样又太繁琐了。足够的细节和听众有关,也和他们使用模型的目的有关--司机需要的是显示道路的地图,施工人员需要的是显示
土木工程细节的地图。
考虑一个架构模型,可能一组画在白板上的图表就足够了--项目的进行中再对它们更新,也许你需要用
CASE 工具来生成一些图表,也许这些图表还需要有详细的文档,这依赖于环境。不同的项目有不同的需要。在每一个例子中,实际上你都是在开发、维护一个有足够的细节的架构模型,只是这个“足够的细节”的概念和环境有关。
敏捷模型能提供正面价值。对项目中的任一artifact,一个基本的要求是它们能够提供正面价值。一个架构模型给你的项目带来的价值是不是能够超过开发它、维护它(可选)的
总成本?一个架构模型能够坚定你们团队为之努力的愿景,所以它当然是有价值的。但是,如果它的成本超过了这个价值,那就是说,它无法提供正面价值。投入100,000美元去开发一个详细的、重量级的文档化架构模型,而它的效用,只需一些画在白板上的图表就能够达到,这些图只需要花你 5,000美元,看看,这是多么轻率的做法。
敏捷模型要尽可能的简单。只要能够达到目的,你应当努力让你的模型尽可能保持简单。模型的详细程度会影响简单性,而所使用的符号范围也会影响简单性。例如,UML的
类图就包括了无数的符号,包括
对象约束语言 (Object Constraint Language OCL) ,但大多数的图使用符号的一部分就能够完成。所以你常常不需要使用所有的符号,你可以限制自己使用符号的一个子集,当然,这个子集是足够让你完成工作的。
因此呢,一个敏捷模型的定义就是一个实现它的目的,没有画蛇添足的模型;为你的预期听众所理解的模型;简单的模型;足够正确、足够一致、足够详细的模型;创建和维护它的投资能够给项目提供正面价值的模型。
一个普遍的哲学问题是
源代码是不是一个模型,更重要的,它是不是一个敏捷模型。如果你是在我们这篇文章之外问我这个问题,我会回答说,是,源代码是一个模型,虽然是一个高度细节化的模型,因为它是软件的一个抽象。同时我还认为,优秀的代码是一个敏捷模型。但在这里,我还需要把两者区分开来,源代码和敏捷模型还是有区别的——敏捷模型帮助你得到源代码。
建模者的个性
Alistair Cockburn指出:很多的
方法学都定义了软件开发项目中开发人员所担任的角色,同时还定义各个角色执行的任务,尽管入席,这些方法并没有定义这些角色最适合的人选。一个人要想成功的担任某个角色,他应当很好的适应它--虽然这并不需要人们掌握所有的技能,但人们必须要慢慢的熟悉这些技术。我的经验告诉我,要成为一个成功的敏捷建模者,下面的列出的个性是必要的:
团队竞赛
第一点,也是最重要的一点,敏捷建模者总是积极的寻求协作,因为他们意识到他们不是万事通,他们需要不同的观点,这样才能做到最好。软件开发可不是游泳,单干是非常危险的。在敏捷的字典中没有“我”这个词。
畅所欲言
敏捷建模者都有良好的
沟通技巧--他们能够表达出他们想法,能够倾听,能够主动获取反馈,并且能够把需要的写出来。
脚踏实地
敏捷建模者应当脚踏实地 他们的精力都集中在满足用户的需求上,他们不会在模型上画蛇添足,即便那双足是多么的好看。他们满足于提供可能的方案中最简单的一种,当然,前提是要能够完成工作。
好奇
凡事都问个为什么
敏捷建模者看问题从不会至于表面,而是会打破沙锅问到底。他们从不会就想当然的认为一个产品或一项技术和它们的广告上说的那样,他们会自己试一试。
实事求是
敏捷建模者都非常的谦逊,他们从不认为自己是个万事通,所以他们会在建立好模型之后,用代码来小心的证明模型的正确。
根据实验
敏捷建模者应当愿意尝试新的方法,例如一项新的(或是已有的)建模技术。一般而言,他们也会接受敏捷建模开发技术,必要时,为了验证想法,他们愿意同传统的思想做斗争,例如在一个项目中减少文档数量。
有纪律
要坚持不懈的遵循
敏捷建模的实践。对你来说,你可能会在不经意间说,“加上这个功能吧!无伤大雅。”或是,“我比project stakeholder更了解。”在AM的道路上要想不偏离方向,是需要一定的
纪律性的。
建模误区
无论你遵从的是重量级的方法,比如Enterprise Unified Process (EUP),还是轻量级的开发过程,如Extreme Programming (XP),建模在软件开发中都是不可或缺的。但不幸的是,其中充斥着各种谬误与迷思。这来自于各个方面:有理论家错误的研究、数十年来信息技术领域内的文化沉积、
软件工具开发商天花乱坠般的市场宣传以及象Object Management Group (OMG) 和
IEEE这类组织的标准。下面,我们要揭示建模中的误区,指出其相应的事实真相。
误区一
建模就等于是写文档
这很可能是其中最具
破坏力的一条,因为开发人员可以此为借口而完全放弃建模。许多优秀的软件开发人员会说他们不想把时间浪费在这些“无用的“文档上。他们沉溺于编码之中,制造着一些脆弱而劣质的系统。
事实分析:“模型”与“文档”这二者在概念上是风马牛不相及的——你可以拥有一个不是文档的模型和不是模型的文档。一幅
设计图就是一个模型,而不论这幅图是被画在餐巾纸的背面,或写在一块白板上,或在Class Responsibility Collaboration(CRC)卡片中,还是根据记录在报纸和
便签纸上的
流程图而生成的一个粗略的用户界面原型。虽然这些都不能说是文档,但他们却都是有价值的模型。
建模很象是作计划:作计划的价值在于计划编制的过程中,而非计划本身;价值体现在建模的活动中,而非模型本身。实际上,模型不是你系统中的一部分正式的文档,而且在完成它们的使命后可以被丢掉。你会发现值得保留的只有很少的模型,而且它一定非常完美。
误区二
从开始阶段你可以考虑到所有的一切
这种说法流行于二十世纪七十年代到八十年代早期,现今的许多经理都是在那个时候学习的软件开发。对这一点的迷信会导致在前期投入可观的时间去对所有的一切建模,以期把所有一切都弄正确,试图在编码开始前就“冻结”所有的需求(见误区四),以致于患上“分析期麻痹症”——要等到模型非常完美之后才敢向前进。基于这个观点,
项目组开发了大量的文档,而不是他们真正想要得到的——开发满足需要的软件。
事实分析:怎么才能走出这个误区呢?首先,你必须认识到你不能考虑到所有的细枝末节。第二,认识到编码员可能会对建模者的工作不以为然(这是可能的,事实上建模者所作的工作在
实际价值中只占很少的部分),他们或许会说模型没有反应出真实的情况。第三,认识到不管你最初所作的规格说明书有多好,但注定代码会很快地与之失去同步,即便是你自己建模自己编码。一个基本的道理就是代码永远只会和代码保持一致。第四,认识到
迭代法(小规模地建模,编一些代码,做一些测试,可能还会做一个小的工作版本)是软件开发的准则。它是现代重量级的
软件开发过程(如EUP),以及
轻量级(如XP)的基本原理。
误区三
建模意味着需要一个重量级的软件开发过程
走入这个误区(经常与误区一有联系)的项目组常常是连建模都彻底地放弃了,因为这样的软件开发过程对他们来说太复杂太沉重了。这不亚于一场天灾。
事实分析:你可以用一种敏捷的方式取而代之。关于用简单的工具进行简单地建模的详细内容,可参看Agile Modeling(AM)。而且,当使命完成之后,你可以丢弃你的模型。同样,你也可以采用很基本的方式进行建模(比如,从办公桌起来,来到白板前就开始构略草图)。只要你愿意,你就可以轻松地建模。
误区四
必须“冻结”需求
这个要求常常来自高级经理,他们确切地想知道他们从这个项目组能得到什么东西。这样的好处就是在开发周期的早期确定下需求,就可以确切地知道所要的是一个什么样的东西;缺点就是他们可能没有得到实际上所需要的。
事实分析:变化总会发生的。由于优先级的变化和逐渐对系统有了更进一步的理解,都会引起需求的变化。与冻结需求相反,估计项目成功的风险,尽量去接受变化而且相应地采取行动,就象XP所建议的一样。
误区五
设计是不可更改的
如同误区四,要求每一个开发人员必须严格遵从“设计“,导致开发人员为了符合“设计“而作了错误的事情或以错误的方式作正确的事情。或者是简单地忽略了设计,否定了所有设计可能带来的好处。冻结了设计,就不能从在项目进程中所学到知识进一步获益。另外一个很大的趋势就是开发出大量的文档而不是实际的软件,使用面向文档的CASE工具而不是能给项目带来实际价值的面向应用的工具。
事实分析:事实上,设计会经常根据开发人员和
数据库管理员的反馈进行修改,因为他们是最接近实际应用的人,通常他们对
技术环境的理解要好于建模者。我们必须的面对这样一个事实:人无完人,他们所作的工作也不可能尽善尽美。难道您真的想将一个并不完善的设计固定下来而不再去修改其中的错误吗?另外,如果需求并没有被冻结,其实就意味着不能冻结设计,因为任何需求的修改势必影响设计。对之,正确的态度是:只要代码还在改动,设计就没完。
误区六
建模常常被认为是一项复杂的工作,因此需要大量地使用CASE工具辅助进行。
事实分析:是的,建模可以是很复杂的。但完全可以建立一个有效而简单的模型表述其中关键的信息,而不是将一些无关紧要的细节包括进来。
误区七
建模是在浪费时间
许多新手都这样认为,这主要是因为他们所接受的教育仅仅局限于如何编写代码,对于完整的开发流程鲜有接触。而且他们的经验也仅限于如何实现代码,就如
初级程序员。他们放弃了提高效率和学习技能的机会,这些技能能够使他们很容易地适应不同的项目或组织。他们应该为此感到羞愧。
事实分析:在大多数情况下,在开始编码之前画一个草图、开发一个粗率的原型或者制作一些索引卡片都能提高
生产效率。高效的开发者在编码之前都要进行建模工作。另外,建模是一种很好的在项目组成员与项目负责人之间沟通的途径。在这个过程中探讨问题,从而对所要的是一个什么样的东西可以得到更好的理解,涉及到该项目中的每个成员也可得到对该项目有一个充分的了解。
误区八
数据模型(Data Model)就是一切
许多组织基于数据模型就蹒跚启动新的开发工作,也许正如所在的组织:IT部门对于数据有非常严格的规定,控制着开发项目;或者以前的数据库是一团糟,别无选择。
事实分析:数据模型是一个重要的但不是最重要的建模,它最好是建立在另外的模型之上。(参见“Extreme Modeling”,Thinking Objectively,Nov.2000)。这即使在象
数据仓库这类
面向数据的项目中也如此。如果没有很好的理解用户是如何使用该数据仓库的(在数据模型中没有表示出来),这些项目经常是以可悲的失败而告终。可以使用的模型有很多——使用案例(use cases),
业务规则(business rules),activity diagrams,类图(class diagrams),
component diagrams,
用户界面流程图(user interface flow diagrams)和CRC,等等。数据模型仅仅是其中的一种。每种模型都有其长处和短处,应该正确地使用。
误区九
所有的开发人员都知道如何建模
面临这样一个严重的问题:许多不是开发人员的人,不知道软件是如何建成的。其结果是,他们不能够区分开熟练的开发者和一般的程序员(当然也分不清高级程序员和一般程序员),他们想当然地认为所有的开发人员都具备从头到尾开发整个系统的技能。
事实分析:这肯定是不正确的。建模的技能,是只有当一个开发者通过学习它,并经过长期的实践才能够掌握。一些非常聪明的程序员常常相信自己无所不能,毕竟他们终究只是程序员。正因为这样的狂妄自大,他们承当的一些任务是他们根本就没有相应的技能去完成的。软件开发是如此的复杂,单单一个人是很难具备所有的技能去成功地进行开发,甚至也不可能去配置有一定复杂程度的系统。开发者应该有自知之明,明白他们自己的弱点,学无止境。通过互相取长补短,建模者可从程序员身上学到一项技术的具体细节,程序员也可从建模者那里学到有价值的设计和
体系结构的技术。我个人认为所有的人,包括我自己,都是新手。
开发宣言
个体和交互 胜过 过程和工具
可以工作的软件 胜过 面面俱到的文档
响应变化 胜过 遵循计划
虽然右项也有价值,但是我们认为左项具有更大的价值。
遵循原则
团队合作的原则
我们最优先要做的是通过尽早的、持续的交付有价值的软件来使
客户满意。
即使到了开发的后期,也欢迎改变需求。敏捷过程利用变化来为客户创造
竞争优势。
经常性地交付可以工作的软件,交付的间隔可以从几个星期到几个月,交付的
时间间隔越短越好。
在整个
项目开发期间,
业务人员和开发人员必须天天都在一起工作。
围绕被激励起来的个体来构建项目。给他们提供所需的环境和支持,并且信任他们能够完成工作。
在团队内部,最具有效果并富有效率的传递信息的方法,就是面对面的交谈。
工作的软件是首要的进度度量标准。
敏捷过程提倡可持续的开发速度。责任人、开发者和用户应该能够保持一个长期的、恒定的开发速度。
n 不断地关注优秀的技能和好的设计会增强敏捷能力。
简单是最根本的。
n 最好的构架、需求和设计出自己组织的团队。
n 每隔一定时间,团队会在如何才能更有效地工作方面进行反省,然后相应地对自己的行为进行调整。
软件设计的腐化味
当软件开发随需求的变化而变化时,软件设计会出现坏味道,当软件中出现下面任何一种气味时,表明软件正在腐化。
n 僵化性:很难对系统进行改动,因为每个改动都会迫使许多对系统其他部分的其它改动。
n
脆弱性:对系统的改动会导致系统中和改动的地方在概念上无关的许多地方出现问题。
n
牢固性:很难解开系统的纠结,使之成为一些可在其他系统中重用的组件。
不必要的复杂性:设计中包含有不具任何直接好处的
基础结构。
n 不必要的
重复性:设计中包含有重复的结构,而该重复的结构本可以使用单一的抽象进行统一。
晦涩性:很难阅读、理解。没有很好地表现出意图。
敏捷团队依靠变化来获取活力。团队几乎不进行预先设计,因此,不需要一个成熟的初始设计。他们更愿意保持设计尽可能的干净、简单,并使用许多
单元测试和验收测试作为支援。这保持了设计的灵活性、易于
理解性。团队利用这种灵活性,持续地改进设计,以便于每次迭代结束生成的系统都具有最适合于那次迭代中需求的设计。
面向对象设计原则
为了改变上面软件设计中的腐化味,敏捷开发采取了以下面向对象的
设计原则来加以避免,这些原则如下:
单一职责原则(SRP)
就一个类而言,应该仅有一个引起它变化的原因。
开放-封闭原则(OCP)
软件实体应该是可以扩展的,但是不可修改。
n Liskov替换原则(LSP)
抽象不应该依赖于细节。细节应该依赖于抽象。
不应该强迫客户依赖于它们不用的方法。接口属于客户,不属于它所在的
类层次结构。
n 重用发布等价原则(REP)
重用的粒度就是发布的粒度。
共同封闭原则(CCP)
包中的所有类对于同一类性质的变化应该是共同封闭的。一个变化若对一个包产生影响,则将对该包中的所有类产生影响,而对于其他的包不造成任何影响。
一个包中的所有类应该是共同重用的。如果重用了包中的一个类,那么就要重用包中的所有类。
环依赖原则(ADP)
稳定依赖原则(SDP)
朝着稳定的方向进行依赖。
稳定抽象原则(SAP)
上述中的包的概念是:包可以用作包容一组类的容器,通过把类组织成包,我们可以在更高层次的抽象上来理解设计,我们也可以通过包来管理软件的开发和发布。目的就是根据一些原则对应用程序中的类进行划分,然后把那些划分后的类分配到包中。
敏捷设计是一个过程,不是一个事件。它是一个持续的应用原则、模式以及实践来改进软件的结构和可读性的过程。它致力于保持
系统设计在任何时间都尽可能得简单、干净和富有
表现力。
敏捷开发团队原则
最大的分歧
最大的分歧在于开发人员和测试人员之间。作为敏捷团队的成员,测试人员被期望能编写一点代码,同时开发人员可以做一些测试。各自的强项还是很重要:新的角色要求每个成员成为大家所谓的“通才”。测试人员大多数时间作测试,开发人员大都编写代码,但所有人都分享他们的工作,而且有能力承担他们面前的任务。
发现中立点
团队决定作为一个团队需要做什么,如何最好地分配工作。第一步是让团队成员说说他们自己的技能集、优点和缺点。但却不希望他们根据以前角色(如,
软件测试员或开发员)来定义自己。所以找到一个中立点,她列出了小型离线会议,和每周工作之外的小时集体活动所需的事项。这样,该团队去当地的
农场采摘蓝莓。他们一起上瑜珈课。他们集体在厨房里
烤燕麦棒,做果沙。
正确执行应用程序
团队找到了让自此感到舒服的新水平。整个项目的
工作流程顺利进行,只做一个待办的事情,而不是四个。
分布式敏捷开发
分布式敏捷开发方法并不能在所有组织中都工作良好。拥有一个已经建立完成的分布式敏捷开发工作文化,对于分布式团队是很重要的。有些公司一直坚持“面对面”的工作文化,这给分布式地开展敏捷站立会议增加了难度。
但是如果分布式的文化一直存在于团队中,那么开展敏捷站立会议和其它会议就会很容易。其中的一个选择是使分散的团队成员按照同一计划表工作,即使他们所处的时区不一致。如果所有团队成员同意,且时差不超过几个小时的话,这才是有效的。
敏捷开发的原则
相对那种半年一次的大版本发布来说,小版本的需求、开发和测试更加简单快速。一些公司,一年发布仅2~3个版本,发布流程缓慢,它们仍采用
瀑布开发模式,更严重的是对
敏捷开发模式存在误解。
2. 让测试人员和开发者参与需求讨论
需求讨论以研讨组的形式展开最有效率。研讨组,需要包括测试人员和开发者,这样可以更加轻松定义可测试的需求,将需求分组并确定优先级。 同时,该种方式也可以充分利用团队成员间的互补特性。如此确定的需求往往比开需求讨论大会的形式效率更高,大家更活跃,参与感更强。
3. 编写可测试的需求文档
开始就要用“
用户故事”(User Story)的方法来编写需求文档。这种方法,可以让我们将注意力放在需求上,而不是
解决方法和实施技术上。过早的提及技术
实施方案,会降低对需求的注意力。
4. 多沟通,尽量减少文档
任何项目中,沟通都是一个常见的问题。好的沟通,是敏捷开发的
先决条件。在圈子里面混得越久,越会强调良好高效的沟通的重要性。
团队要确保日常的交流,面对面沟通比邮件强得多。
建议使用草图和模型来阐明用户界面。并不是所有人都可以理解一份复杂的文档,但人人都会看图。
6. 及早考虑测试
及早地考虑测试在敏捷开发中很重要。传统的软件开发,
测试用例很晚才开始写,这导致过晚发现需求中存在的问题,使得改进成本过高。较早地开始编写测试用例,当需求完成时,可以接受的测试用例也基本一块完成了。