Microsoft.NET企业级应用架构设计(第2版)

978-7-115-41371-0
作者: 【意】Dino Esposito(埃斯波西托) Andrea Saltarello(索尔塔雷罗)
译者: 李永伦
编辑: 胡俊英陈冀康

图书目录:

详情

本书主要介绍了.net平台下企业级架构设计开发的指导原则、最佳实践和模式等。书中第一部分介绍了软件设计基本原则以及架构的相关概念;第二部分按照业务逻辑层、数据访问层、表现层和服务层进行了说明,并详细分析了各层中的常见模式。作者曾撰写多部.net相关的畅销著作,虽然本书涉及架构这个高端主题,但其文字生动活泼,行文一气呵成。本书适合中高级.net开发人员、软件架构师以及有志于成为软件架构师的读者阅读。

图书摘要

版权信息

书名:Microsoft.NET企业级应用架构设计(第2版)

ISBN:978-7-115-41371-0

本书由人民邮电出版社发行数字版。版权所有,侵权必究。

您购买的人民邮电出版社电子书仅供您个人使用,未经授权,不得以任何方式复制和传播本书内容。

我们愿意相信读者具有这样的良知和觉悟,与我们共同保护知识产权。

如果购买者有侵权行为,我们可能对该用户实施包括但不限于关闭该帐号等维权措施,并可能追究法律责任。

• 著    [意]Dino Esposito Andrea Saltarello

  译    李永伦

  责任编辑 陈冀康

  执行编辑 胡俊英

• 人民邮电出版社出版发行  北京市丰台区成寿寺路11号

  邮编 100164  电子邮件 315@ptpress.com.cn

  网址 http://www.ptpress.com.cn

• 读者服务热线:(010)81055410

  反盗版热线:(010)81055315


Dino Esposito是Crionet公司的CTO和联合创始人,这是一家初创公司,为专业网球和体育运动公司提供软件和IT服务。Dino还从事大量培训和咨询工作,也是好几本关于Web和移动开发书籍的作者。他最新出版的书是《Architecting Mobile Solutions for the Enterprise》和《Programming ASP.NET MVC》,都交给Microsoft Press出版。Dino经常出席行业会议并发表演讲,包括Microsoft TechEd和DevConnections,还在一些欧洲活动亮相,如Software Architect、DevWeek、SDD和BASTA。作为一名技术传道者,Dino为JetBrains传播Android和Kotlin开发技术,还加入了WUREF开发团队,这是ScientiaMobile提供的一个移动设备能力数据库,已为Facebook等大公司采用。

你可以关注他的Twitter——@despos和博客——http://software 2cents.wordpress.com

Andrea Saltarello是Managed Designs(http://www.manageddesigns.it)的CEO和创始人,这家公司提供软件设计和开发的咨询服务。

Andrea是一个解决方案架构师,渴望在现实项目里写代码,获取与他的架构决定有关的反馈。Andrea也是MvcMate的首席开发者,这是一个开源项目,为ASP.NET MVC工具套件提供有用扩展。

Andrea也是培训师和演讲者,在欧洲很多课程和会议都有演讲方面的合作,如意大利BASTA!、DevWeek和Software Architect。他还在“Politecnico of Milan”大学举办的Master in Editoria Multimediale课程上讲操作系统。

2011年,Andrea共同创办了UGIdotNET(http://www.ugidotnet.org),这是意大利首个.NET用户组,他担当主席和领导。

Andrea热衷于运动和音乐,在成长的过程中醉心于排球和Depeche Mode,他第一次听到Everything Counts时就爱上这个乐队了。

你可以关注他的Twitter——@andysal74和博客——http://blogs. ugidotnet.org/mrbrightside

Authorized translation from the English language edition, entitled: MICROSOFT .NET: ARCHITECTING APPLICATIONS FOR THE ENTERPRISE, 2nd Edition, 9780735685352 by ESPOSITO, DINO; SALTARELLO, ANDREA, published by Pearson Education, Inc, publishing as Microsoft Press, Copyright ©2015.

All rights reserved. No part of this book may be reproduced or transmitted in any form or by any means, electronic or mechanical,including photocopying, recording or by any information storage retrieval system, without permission from Pearson Education, Inc. CHINESE SIMPLIFIED language edition published by POSTS AND TELECOMMUNICATIONS PRESS, Copyright ©2016.

本书中文简体字版由Pearson Education Asia Ltd.授权人民邮电出版社独家出版。未经出版者书面许可,不得以任何方式复制或抄袭本书内容。

本书封面贴有Pearson Education(培生教育出版集团)激光防伪标签,无标签者不得销售。 版权所有,侵权必究。


软件架构是一系列相关的抽象模式,用于指导大型软件系统各个方面的设计。本书就是一个关于软件架构的坚实、可重用且易于访问的知识库。

本书分 4 个部分来介绍软件架构相关的内容。其中,基础知识部分为软件架构打下基础;设计架构部分关注表现层和业务层;支撑架构部分涵盖 3 个可用于构建各种子领域的支撑架构;基础设计部分介绍了多样化持久化、NoSQL数据存储、SQL、Entity Framework和关系型数据库等内容。

本书着重介绍软件架构相关的内容,非常适合软件架构师和想成为软件架构师的人阅读,而且首席开发者和各种.NET应用程序的开发者也能从本书获益。


好的判断源自经验,而经验源自坏的判断。

——Fred Brooks

我们认为前面那句名言包含了软件架构的本质和架构师角色的要旨。软件架构需要判断,因为并非所有场景都是一样的。为了做出正确判断,你需要经验,在这个不完美的世界里,经验通常源自犯错和糟糕的选择,也就是糟糕的判断。

然而,我们生活的这个世界通常不会让你有机会从你自己的经验中总结知识,并据此做出好的判断。更常见的是,管理层希望立刻从架构师那里得到正确的架构。

我们写这本书的主要目的是为你带来一个关于软件架构的坚实、可重用以及易于访问的知识库。在过去,我们使用Microsoft Windows DNA、分布式COM、多层CRUD、SOA、DDD、CQRS和事件溯源等技术完成了很多项目。我们用过Microsoft Visual Basic 6、C#、C++、Java和JavaScript。我们目睹了技术解决方案不断改变,对于这些方案的看法也进化了。

最终,我们和Fred Brooks得出的结论相同。我们不穿白袍,我们不是医生,我们不写处方。我们在这里的目的是汇聚各方观点,加入我们对这些观点的见解和评论,如实地总结这些事实和看法。

当开发者和架构师被要求立刻采取正确的方案时,我们提供一个知识快照——现成的软件架构师心得,可以用作进一步研究的起点,也可以用来形成你自己的判断。如果软件架构是一个定理,(我们希望)这本书将会提供一些必要的引理。

软件架构有一些前置条件(设计原则)和一个后置条件(实现一个产生预期结果的系统)。本书的第1部分是“基础”,为软件架构打下基础,关注架构师的角色、软件项目的固有机制以及提升软件品质方面,如可测试性和可读性。

第2部分是“设计架构”,关注构成典型企业系统的最上面两层:表现层和业务层。我们把标准的第3层放在后面:数据访问层。我们推行一个比较新的系统设计方案,叫作用户体验优先。它是一个基于任务的方法学,从达成共识的模型和屏幕引出命令和领域事件。就基于任务的设计理念而言,领域模型的角色不再是中心,数据访问层也只是基础设施的一部分了,而且不一定基于标准的关系型表。第2部分最给力的一章是第5章“发现领域架构”,我们推荐所有人阅读。简而言之,这章的观点是只有深刻理解领域才能发现合适架构。更重要的是,结果架构不一定是适用于整个应用程序的单个顶层架构。当识别出子领域时,你可以把它们建模成子应用程序,给予每个最有效的架构。听起来可能有点奇怪,但这就是领域驱动设计(DDD)的要旨。

第3部分是“支撑架构”,涵盖3个可以用来构建各种子领域的支撑架构。每个架构都有两章——介绍和实现。我们考虑的第一个支撑架构是领域模型。接着,我们会讲命令/查询责任分离(CQRS)和事件溯源。

最后,第4部分“基础设施”,只包含一章,它处理基础设施和持久层。有趣的是,这章不仅仅讲到SQL、Entity Framework和关系型数据库,还着重讲了多样化和持久化、NoSQL数据存储和用来隐藏存储细节的服务。

那么,这本书到底是关于什么的?

这是关于在.NET平台上更好地满足你的客户需要知道和做到什么的一本书。我们给出的模式、原则和技术一般来说都是有效的,并非针对复杂的行业系统。一个好的软件架构可以帮助控制项目的复杂性。控制复杂性和支持可维护性是我们应对技术领域的墨菲定理的最好策略:“没有什么能按时、按预算构建出来。”为了做到这点,只有一件事情是不允许失败的:(深刻)理解业务领域。

软件架构师是本书的理想受众,但首席开发者和各种.NET应用程序的开发者也会从本书获益。每个想成为架构师的人都应该发现这本书很有帮助、很值得。

这本书只适合.NET专家吗?虽然所有章节都牵涉.NET,但大多数内容都适合任何软件专家阅读。

熟练的面向对象编程技能是使用本书的前提。在使用.NET平台和数据访问技术方面有扎实的基础也会有帮助。我们尽力使本书通俗易懂,本书不是关于抽象设计概念的,也不是一本传统的架构书,到处都有交叉引用,通过方括号里的文字引用书本末尾参考文献里列出的论文。

如果你在找一本参考书告诉你如何使用给定模式,这本书可能不适合你。我们的目标是分享和传授知识,以便你在任何时候都知道要做什么。至少,你知道有两个人——Dino和Andrea,在类似的情况下会做什么。这本书最好从头到尾阅读,可以多读几遍,本书不是放在桌子上随便翻阅一下的。

在这本书里,我们展示了几个代码段,也讨论了一些示例应用程序,目的是阐明相关的原则和技术,让读者可以在他们自己的项目里应用。在某种程度上,我们授人以渔,而非授人以鱼。然而,我们想给你推荐一个CodePlex站点:

http://naa4e.codeplex.com/

你会在那里找到一些Visual Studio 2013项目,每个项目对应我们在本书里提到的一个支撑架构。有一个示例在线商店系统——“买买买”项目,它是根据领域模型架构写的,然后移植到CQRS。还有两个项目:一个现场比分应用程序和一个迷你ERP系统,用来示范事件溯源。

我们邀请你参与这个项目,因为我们计划将来添加更多示例。

这些示例代码只依赖于少数通用技术,如Visual Studio 2013和SQL Server。这些项目利用Entity Framework、ASP.NET MVC、RavenDB、Bootstrap和WURFL。一切都是通过NuGet关联到项目的。刷新这些包,你就可以重建这个示例。特别地,你不需要安装完整版的SQL Server,SQL Express已经足够了。

2008年的夏天,Andrea和我写这本书的第1版时是一个完全不同的世界。那个时候,冲击了美国和其他地方,至今仍然影响欧洲的电子商务大衰退才刚刚开始。那个时候,Entity Framework也刚刚出来。我们讲到的模式和技术与今天也没有多大联系,那时没有云、移动设备或NoSQL。但是,在出版之后长达四五年的时间里,我们好几次看到这本书在Amazon的某些分类里排名前十。5年前的技术书籍可以说是老古董了。我们被多次要求发布第2版,但直到2014年春天才有机会着手处理。感谢Devon Musgrave、Steve Sagman、Roger LeBlanc和Carol Dillingham,这是一个出色的团队。

到目前为止写了超过20本书,我从没给技术审校者留下多少工作,这听起来可能有点夸大和片面。我也没有从合作审校者那里学到很多东西,不管什么原因,但这次有所不同。Cesar De la Torre Llorente,我们的合作审校者,做得很出色。他及时发现大纲和内容上的问题,我甚至完全忽略了这些深层问题,而Andrea则认为它们只是很难详细解释和修复的小问题。Cesar好几次说服我们重新调整内容的结构,把这本书重塑成它需要变成的样子,最终使它变成我们认为它应该变成的样子。

最后,我有几句话想对那些分享过有价值见解和意见的人说,虽然这些人可能没有意识到这点。一个是Hadi Hariri,他不断改变人们对IT世界的看法。另一个是Jon Smith,他告诉我们架构师角色的各个方面。还有一个是Giorgio Garcia-Agreda,他告诉我解决问题应有的态度(尤其是在恶劣的条件下)。最后,但不是最不重要的,非常感谢Roberto Raschetti。他可能不知道为什么会得到这份赞誉,但毫无疑问,从刚毕业到数个月内完成一个大项目放在商店里销售,他给我指明了道路。

最后,妈妈,爸爸,你们的书架又有一本书了!以后还会有更大的一本。

PS:关注我们的Facebook(facebook.com/naa4e),发推文的时候请使用#naa4e。

——Dino

没有Dino,这本书不可能存在。在我看来,写书会给我带来了很大影响和很多压力,然而,Dino让我接受了这个艰难的任务。

现实不再是你之前坚持认为的那样。

Dino找过我几次,问我要不要写第2版,不过都被我拒绝了。后来,我答应之后,还得到他的理解,这次写作是一个“两部流程”,因为他可以在几个小时内很优雅、很深刻地写出来的东西,我却要花很长时间才写得出来。

我在写作上不但很慢,还很挑剔。但Dino对于我的折腾总是很支持,以便保证这本书至少和前一版一样好。

我和最好的朋友一起行动。

我很高兴有Cesar De la Torre Llorente作我们的合作审校者,他和我一样挑剔,他不但审校了内容,还就如何重新组织我们的内容提供了有价值的建议。谢谢你,Cesar。你真的帮了我们很多。

他知道引领我到何方,引领我到我想去的地方。

在我看来,为了让这本书可以出来,而且要是一本好书,还需要一个好的团队在背后支持我们,Devon Musgrave、Steve Sagman、Roger LeBlanc和Carol Dillingham对我们提供的支持让我们脱颖而出。谢谢你们!

这是真正的乐趣,这就是乐趣所在。

作为一个全职咨询师,写书意味着要花大量时间坐在电脑前,没时间和你爱的人一起,这对于两边来说都是一件很郁闷的事。尽管如此,Laura和妈妈都理解这本书对我很重要,给予我巨大的支持和爱。

你就像一个天使,你给我的爱,我永远不嫌多。

最后,我想感谢Managed Designs的所有人:如果没有我们努力得到的经验,这本书连现在的一半都不如。

我的秘密花园不再是个秘密!

最后的最后,但不是最不重要的,感谢Helen和Maruska在文字表达上对我的支持。女士们,我衷心感谢你们。

欢迎来到我的世界,走进这扇门。

——Andrea

我们尽了一切努力保证本书以及配套内容准确。如果你发现错误,请通过mspinput@microsoft.com发给我们。你也可以通过相同的别名(alias)向Microsoft Press Book Support团队寻求其他支持。请注意,Microsoft软件和硬件的产品支持并非是通过这个地址提供的。若要获得Microsoft软件或硬件方面的帮助,请访问http://support.microsoft.com

从技术概览到特别主题的深度信息,来自Microsoft Press的免费电子书涵盖了各种各样的主题。这些电子书以PDF、EPUB和适用于Kindle的Mobi格式提供,你可以到这里下载:

http://aka.ms/mspressfree

你可以经常回来看看有什么新的内容!

在Microsoft Press,您的满意是我们的第一要务,您的反馈是我们最有价值的资产。请通过以下地址把您的想法告诉我们:

http://aka.ms/tellpress

我们知道您很忙,所以我们只列了几个问题。你的回答会直接发到Microsoft Press的编辑(不会记录任何个人信息)。感谢你的反馈!

让交流持续下去!我们的Twitter是:http://twitter.com/MicrosoftPress

作者将会维护一个Facebook主页:facebook.com/naa4e。

请在关于本书的评论、帖子和推文前面加上#naa4e标签。



软件工程的目的是控制复杂性,而不是创造复杂性。

——Pamela Zave博士

在计算机的最初年代,硬件成本远远大于软件成本。数十年之后,我们发现情况有了根本的变化。整个工业有了显著的进步,而硬件成本也急剧下降。另一方面,软件成本却大幅上升,这主要是因为开发自定义企业软件的复杂性提升了。

这种情况催生了一系列的准则,并以此指导工程师设计这类系统。架构这个术语源自建筑行业,现已普遍用于描述规划、设计和实现软件密集型系统的艺术。当我们两个还是青少年时,《爱是……》这部漫画(http://www.loveiscartoon.com)正值流行。每期漫画都会为青少年推荐一则爱的名言。其中一期漫画说过这样的话:“爱是必需品,不是奢侈品。”是的,这对于软件架构也同样适用。

在第1章里,我们将会试着分享我们对架构的看法,以及确定并实现它的方法。其中,我们将会了解架构师在这个过程里扮演的角色,以及我们曾经遇到的控制软件项目运作的基本规律。毫无疑问,我们的经验仅仅是我们自己的。虽然我们曾在不同规模的有趣项目里工作过,但是我们的经验仍然受限于我们的见识以及教训。尽管如此,我们希望你把这本书看作一个出发点,和你的团队耐心、详细地探讨你们如何构建可以正常工作的东西。

注意:

你在本书里看到的一些定义来自于国际标准,其他则反映我们个人的见解、经验和感受。我们将会讲到架构的一些公认的最佳实践,但会辅以我们自己的经验作为补充。我们希望这种组合方式可以帮你把枯燥的国际标准和你在现实世界里看到的东西联系起来。

本书的其中一个作者曾经和一个架构工作室有着密切的联系。有一天,他们在讨论的时候突然提出这样一个问题:架构是什么?是艺术,还是仅仅为客户进行构建?

在软件里,架构这个术语恰到好处地指代为客户构建系统。仅此而已,不多也不少。

也就是说,详细描述为客户构建系统所涉及的东西是最难的部分。我们经常思索是否真的有定论。这个问题我们问过无数次,每次都得到相同的答案。虽然这条路不好走,但是我们也要尝试一下。最糟糕的结果是变成一场头脑风暴,但头脑风暴并没什么坏处。

那么,让我们试着理清软件架构是什么,至少搞清我们希望它是什么。

注意:

架构和架构师这两个术语常常形影不离。架构师是一个角色,我们将在本章稍后讨论架构师这个角色的职责。然而,架构师这个术语仍然让人认为是一种职业。不幸的是,这个术语背后的专业人士并非全能的。这个头衔通常带有修饰语,如企业、解决方案、安全等。除非我们特别指明,否则我们所说的专业人士通常是指软件架构师或者解决方案架构师。

我们之中的许多人在成为软件专家的过程中,一开始认为软件与现实世界的建筑有某种程度的关联,后来又反对这种观点。起初把这种类比引入软件行业是为了表达在构建计算机程序之前需要规划和设计。但是,设计和构建民用建筑与设计和构建可用的软件系统之间存在着本质的差异。

软件的存在是为了自动化商业流程和人类行为,而民用建筑则作为社区的服务而存在。一般地,民用建筑服务针对绝大多数人并提供最优的规划和实现。他们的目的并非为了满足少数利益相关者。此外,民用建筑的成本很高,造起来也很复杂,因此没人会对已经敲定的项目随意做出更改。至少,更改也只是偶然出现。

软件则不同。

软件通常只为小部分人构建,其中一些人自掏腰包,希望得到一些东西改善他们的组织运作。因此,需求会持续地提炼、添加、移除以及重新划分优先级。使用这种方式构建软件需要敏捷性,民用建筑采用大量前期设计的做法并不适用于此。

简而言之,今天的建筑架构和软件架构之间的距离已经没有20年前那么接近了。但是,许多字典仍在“架构”这个词语下面列出与软件相关的定义。软件架构被描述为“计算机系统内部组件的组合、整合和交互”。当然,大家都会认可这个定义。但是,我们认为它过于通用和抽象。

因此,它并不管用。

我们认为软件专家应该就一个更加细化的解释达成共识,它对那个定义进行细分,并把它们置于相应的上下文中。

1.根据标准定义架构

许多人好像忘记了软件架构有一个标准定义。更确切地说,自从美国国家标准学会/电器和电子工程师协会(ANSI/IEEE)于2000年9月发布了第1471号标准《软件密集型系统架构描述的推荐实践》以来,这个定义就一直存在了。经过这么多年,这份文档也变成国际标准化组织/国际电工委员会(ISO/IEC)的第42010号标准。有兴趣阅读完整的标准内容可以点击下面这个链接:http://www.iso.org/iso/iso_catalogue/catalogue_tc/catalogue_detail.htm?csnumber=45991

软件架构与软件密集型系统的组织有关,从利益相关者的角度解决问题和完成使命。这就是国际标准论文给软件架构这个术语的官方定义。

利益相关者指的是关注系统构建的所有个体。这包括系统的构建者(架构师、开发者、测试者),以及买家、最终用户、分析师、审核员和首席信息官。

关注点指的是利益相关者的兴趣所在,包括系统本身以及他们能在系统上施加的影响,不管是开发方面的、技术方面的、运维方面的、组织方面的、经济方面的,还是法律方面的。

注意:

我们知道很多人在遇到诸如ISO和ANSI等缩略词时都会感到害怕。我们有时倾向于避开这些引用,因为它们让人感到沉闷,让人想到大学时期的大量理论条文。不管怎样,标准论文有着大量我们认可的信息。在本章里,我们引用标准通常是为了说明在某些领域(如处理用户需求)大量工作已经完成得很好,可以重用。

2.软件密集型系统图表

图1-1所示是根据ISO、IEC和IEEE认证的论文描绘的软件架构官方图的核心部分。

图1-1 根据ISO、IEC和IEEE总结软件架构图

系统存在于环境之中,而环境则通过驱动一系列开发和运维的决策来影响系统的设计。虽然当前标准把系统看作相互连接的组件的组合,但是架构也产生了一些后续难以更改的地方。简而言之,通过架构表达软件开发可以归结为做出一些关键的决定,它们将会影响开发生命周期,最终也会影响产出系统的质量。

3.我们自己对软件架构的看法

当新人加入我们公司时,不管他们的技能和角色如何,我们都会向他们介绍我们自己对软件架构的看法。

我们是意大利人,意大利人对咖啡是非常重视的。就意式咖啡而言,并没有很多不同种类的咖啡。唯一认可的分类就是好的意式咖啡和不太好的意式咖啡。但当我们来到星巴克,我们也和其他人一样有着我们自己的口味偏好。

我们想把这个咖啡的类比延伸到软件架构上。

和意式咖啡一样,我们也把世上的架构分为好的架构和不太好的架构。为了确保这种做法有意义,我们只探讨有助于开发能够正常工作的系统的架构。我们不在这里考虑可能导致系统出问题的不良架构,就像我们不喝不好的意式咖啡一样!在喝到不好的意式咖啡时,意大利人不会抱怨,只会直接离开,并给予恰当的(也是尊重的)反馈。

正如我们在星巴克会有我们自己的口味偏好,我们在描述架构时也会有我们自己的看法。我们的看法可以通过一个简单示意图阐述,我们认为它能表达我们是如何思考和工作的。(见图1-2。)

图1-2 我们对软件架构的基本看法

系统的使命可以通过一组需求来描述。这些需求最终推动系统架构的形成。

抽象一点地说,需求就是系统的特征,它既可以是功能性的,也可以是非功能性的。功能性需求指的是为了实现特定场景系统必须提供的行为。非功能性需求指的是利益相关者明确提出的系统特性。

功能性和非功能性需求的定义是否标准并广为接受?事实上,用来确定软件系统质量特征的国际标准自1991年起就已经存在了。

1.ISO/IEC 9126标准概览

事实上,没有确认清楚需求是直接导致软件项目失败的常见原因之一,通常也是主要原因。ISO/IEC 9126标准定义了一组软件产品必须满足的质量特征。这个标准给出6个质量特征族群,并进一步细分为21个子特征。其主要族群包括功能、可靠性、可用性、效率、可维护性以及可移植性。表1-1详细解释这些族群以及与之相关的子特征。

表1-1 ISO/IEC 9126质量特征族群

族 群

描 述

功能

表示为了满足期望软件需要做的事情。它基于适合性、精确性、安全性和互操作性等需求,并符合标准和规定

可靠性

表示软件在特殊条件下使用时可以维持指定级别性能的能力。它基于成熟性、容错性和可恢复性等需求。成熟性是指软件在遇到内部错误时不会导致运行中断。容错性是指控制出错并维持指定级别行为的能力。可恢复性是指出错之后恢复的能力

可用性

表示软件的功能可被用户理解和使用,对用户有吸引力。它要求软件符合可用性标准和规定

效率

表示根据适时的回应和资源的有效利用提供指定级别性能的能力

可维护性

表示软件支持修改的能力,如修正、改善或调适。它基于对可测试性、稳定性和功能等需求的分析,以及对功能需求的改变

可移植性

表示软件可以从一个平台移植到另一个平台的能力,以及在一个公共环境里与其他软件共存并共享公共资源的能力

子特征分成两类:外部特征和内部特征。外部特征面向用户,是系统的外部视图。内部特征面向系统,是系统的内部视图。外部特征标识功能性需求;内部特征标识非功能性需求。

新的ISO/IEC 25010标准于2011年3月发布,它取代了ISO/IEC 9126。ISO 25010包含8个产品质量特征和31个子特征。

2.功能性需求

功能性需求定义了软件该有的功能。功能通过输入、行为和输出来描述。功能性需求的主要问题在于描述期望行为。不幸的是,这个描述通常没有足够清楚、易懂。

在ISO/IEC 9126文档里,功能性需求被描述为软件提供功能的能力,在特定情况下使用软件时,这些功能可以满足明示和暗示的需求。软件架构对应“什么”;软件设计对应“如何”。但是,我们相信,我们往往忘记了第3个维度——“何时”。

你可能还记得1996年Ariane 5运载火箭的灾难。简单地说,在Ariane 5运载火箭初次飞行的过程中,起飞后40秒就坠毁了。根据官方报告,火箭自行解体的原因是一个未处理异常——数值转换溢出引发的连锁反应。未处理异常不受控制地上抛,向自行解体模块传递了不协调的轨道数字。由于火箭的状态被“正确地”解读为不可挽回地迷失于太空,最终,自我解体只不过是一个正常的决策罢了。讽刺的是,出现未处理异常的模块不应该执行,因为在起飞阶段是没有必要的。工程师应该知道起飞阶段有可能出现不协调的数字,他们没有捕获异常,而是简单地认为没有必要,因为这个模块在起飞阶段不会工作!

因此,需求的“何时”维度是有影响的。

你的互联网应用程序在连接性较差的情况下应该如何反应?它应该出错吗?还是应该等待并恢复?它应该对用户友好吗?它应该使用缓存数据,然后悄悄地切换到离线模式吗?没有公认的答案,应该如何反应取决于可靠性和可恢复性的需求如何。作为一名架构师,你有责任找出答案,有时候需要提出更多的问题,做出更多的思考。有了那些答案就能更好地理解系统,继而减少出错或者漏掉所需特性的机会。

理想情况下,所有功能性需求都是在开发阶段开始之前收集的,一旦进入开发阶段就不会更改。但是,更多的情况是,许多功能性需求只在开发阶段才被发现和完全理解。这就需要重新修订实现了,有时连模块甚至子系统的架构也需要重新修订。做不到这点会导致臭名昭著的“大泥球”综合症,我们会在下一章讨论这个问题。

重要:

在我们职业生涯开始那段时间里,不管与客户面谈时听到的是什么,我们都倾向于将其认为是金科玉律。就像傻乎乎的Dilbert卡通,我们会责怪自己没有很好地处理需求,不管这些需求如何大量、没有条理、充满矛盾或者缺此漏彼(顺便说说,前面提到的Dilbert卡通在这里可以看到:http://dilbert.com/strip/2001-04-14)。“客户永远是对的”曾经是我们的座右铭,而客户的需求就像法律一样。这些年来,我们发现客户对于任何功能性决定总有最终解释,但是,我们有责任探求出一组明确的选择。我们需要成功确认从面谈得到的未经处理的需求,继而在此基础上制定出一组选择。

3.非功能性需求

非功能性需求是指利益相关者明确提出的系统特性。其常见特性包括可伸缩性、安全性,或者可访问性。功能性需求通常与代码和实现有关,而非功能性需求则通常与系统架构的选择和相关方面有关,这些东西往后难以更改。

举个例子,如何应对极致可伸缩性?

注意:

我们遇到的客户都声称,联系人应用程序的极致扩展性对于公司的生存和投资者的投资组合稳健而言是必不可少的。不管实际说的是什么,做的是什么,我们的陋见是,真正需要极致扩展性的情况是很少的。当然,除非你是在构建下一个Twitter或者Facebook!

扩展性是指随着连接用户的增长,产生了更多的工作和流量,但系统仍能提供相同性能的能力。要让系统更好地扩展,读写必须在最短时间内完成。这可能意味着读取时大量使用缓存,并且使用异步写入。

类似地,把系统设计成关键模块可以部署到独立的机器,能够有效地满足安全性需求。如果你使用ASP.NET MVC而不是ASP.NET Web Forms来实现网站,那就可以很好地满足可访问性需求,因为ASP.NET MVC在渲染HTML方面提供更多的控制。

一般来说,非功能性需求必须和功能性需求同时确认。后者产生软件规范;前者则在实现策略上提供帮助,并在技术决策上给予启发。功能性需求和非功能性需求本身是相关的,并且都会持续发生变化。不过,非功能性需求会迫使我们做出一些艰难决定。

注意:

一些在我们写书时提供过帮助的朋友在这点上有一些补充。虽然他们同意前面这节里的每一句话,但他们想澄清一点:在很多情况下,功能性需求也会影响系统架构的决策和相关方面,并且往后难以更改。完全正确!

4.收集需求

解释如何收集和记录需求的书很多。你可以从中学到,一个好的需求只针对一个东西,表达上没有歧义,可以轻易追溯到业务或者利益相关者的需求,不会作废等。

注意:

如果你想找一本解释软件需求理论的书,我们推荐Stephen Withall的《软件需求模式》(Microsoft Press,2007)。

不过,实际的情况是,业务人员描述的是他们认为自己想要的东西,开发人员构建的是他们认为业务人员想要的东西。不管你在收集需求上投入多少努力,总会出现隐瞒、忽略、忘记,或者被描述的东西只有一些人清楚,而另一些人并不清楚。这种沟通问题的根源是业务人员和开发人员使用不同的词汇。

我们坚信架构师和开发者应该和业务人员使用相同的语言。

不管代金券实际上如何编码才能完整实现,你都必须理解它是什么。更重要的是,你还需理解业务人员如何看待它。作为一名架构师,你不应该期望业务人员理解你的语言,例如数据库表、服务和协议之类的东西。相反,是你应该付出必要的努力去理解构成业务领域的实体含义。

重要:

我们在第5章“发现领域架构”介绍的一个关键概念促使技术人员和业务人员使用相同语言。这个概念就是统一语言,它是领域驱动设计方法学的支柱之一。

5.我们如何处理需求

处理功能性需求的一个简单、有效的办法是根据类别对它们进行分组,就像表1-1所示的那样。我们通常创建一个Microsoft Office Excel文档,一个标签对应一个类别。接着,我们把从面谈中收集到的需求放入对应的类别。

完成之后,我们回顾一遍,碰到空的或者只有一两个需求的标签就停下来。比如说,如果我们在可移植性里没找到需求,我们就应该停下来问一下,我们是否了解得足够多,我们是否提出了足够多的问题。没有明确的可移植性需求可能意味着可移植性并非一个需求,也可能意味着我们对它并不了解。

特别地,可移植性与软件可在不同的环境下使用的能力有关。如果服务后端没有这种需求,那么马上就会引出一个问题,这个后端是否应该只对Web客户端可用,或者是否也应该支持iOS和Android客户端?

当你考虑创建或定义软件系统的架构时,你首先试着标识出一组可能的交互组件,它们共同完成被赋予的任务。国际标准并没提到任何可以把系统细分成多个部分的方法。假设在第一步里你得到一个概念性的架构以及一些不同角度的视图。在第二步里,你需要逐步逼近功能和物理架构。如何做到是很主观的,即使自顶向下的做法看起来很合理。你把组件划分得越来越细,然后从这里开始构建。

分解过程的具体实现取决于项目选用的方法学,你越是敏捷,分解过程越是迭代,表达越是清楚,步骤也越细致和频繁。分解过程的输出是一组将会交给开发团队的规范。此外,规范的内容和格式取决于选用的方法学。你越是敏捷,你留给开发者实现这个架构的自由性和独立性就越多。

1.定义架构和实现之间的边界

你在分解系统时标识出来的构成组件代表了将以某种方式实现的功能。组件的设计,它们的接口、它们的职责,以及它们的行为毫无疑问都是架构的组成部分。但是,架构和实现之间是有一道物理边界把它们分隔开来的。

把这个边界标识出来很重要,因为在很大程度上它帮助我们定义开发团队里面的角色。尤其是它界定了架构师和开发者之间的边界。这些年来,我们了解到架构师与开发者之间的区别并没有苹果与橙子的那么大。如果把他们看作同类水果,那么,假设他们都是苹果,他们就是红苹果与青苹果,换句话说,只是不同口味,而不是不同类型的水果。并且,不存在哪种口味更好的说法。

架构和实现之间的边界会在你触及黑盒行为时显现出来。黑盒行为只是一块功能,可以轻易替换和重构而不会带来显著回归,并且对架构的其他部分造成很少影响,甚至没有影响。在黑盒行为之上的东西则可能与架构相关,并且可能需要你做出艰难决定。

那么,我们如何定义好的架构?答案是:架构里的所有艰难决策最后都被证实是对的。

2.艰难决定的科学

软件系统有些方面和特性,一旦进入开发阶段就难以更改(只是难,而不是不可能)。其他方面和特性则可以在任何时候毫不费力地更改,也不会对系统产生显著影响。

Martin Fowler在他的《企业应用架构模式》(Addison-Wesley,2002)里提到:

如果你发现有些东西比你想象的更易实现,那么它不再属于架构范畴了。最终,架构归结为重要的东西,不管这些东西是什么。

简而言之,我们认为架构这个词包含了所有在项目早期必须认真对待的东西。最终,架构就是找出需要正确处理的关键决策,这些决策在项目里应该尽早处理,但你却想尽可能押后处理。

3.艰难决定无处不在

当我们提及艰难的架构决定时,我们并不一定是指往后改起来很难或很昂贵的设计决定。难以更改的决定无处不在,从概念层的定义到构造函数的签名都有。

为了说明这点,我们来看几个架构问题的例子,如果它们在项目期间出现,你可能会陷入预算限制和期限问题。

第 1 个例子是改变业务逻辑的组织方式。业务逻辑的设计有几种方案:事务脚本(Transaction Script)、表模块(Table Module)、领域模型(Domain Model)、对命令和查询进行分离的领域模型,以及事件溯源(Event Sourcing)等。一旦你做出选择,比如说,表模块(这意味着你会把逻辑放入基于数据库的表构建的仓储组件,就像多年以来你对DataSet和DataTable做的那样),把它换成领域模型并非一个下午就能完成的事情。这种改变会对数据层以及应用程序(服务)层造成很大影响,对表现层也可能是这样。

第2个例子是换一个不同的库做相同的任务。假设你使用某个库开发了某个功能。某天,客户跟你说公司出了一个新的政策,IT部门不得从某个供应商采购产品。现在你要处理一个新的意料之外的非功能性需求,但代价是什么?在最好的情况下,你可以从授权供应商获取一个类似的工具,或者你可以自己构建一个类似的工具。此外,你也可以考虑渐进地改变架构,使得这个库变得不再必要。

注意:

如果你参与过Dino过去两年举办的研讨会,你可能已经听过这段经历。简单地说,Dino的公司投标了一个项目,为iOS、Android、Windows Phone和BlackBerry 4个平台构建一个时效性很强的移动应用。这个投标假设了PhoneGap是一个可行方案。不幸的是,使用PhoneGap构建的原型被客户拒绝了,团队之前的努力付诸东流了,而剩下的时间不足4周。

团队面临两个令人不爽的方案:在很短的时间内把事情做4遍,或者发明一些新的东西。团队设法在一个移动网站上重用PhoneGap的某些标记,然后在4个基础的、基本上静态的、只有图形的原生应用的窗体里整合Web视图。在功能方面,表现出色,甚至还有时间应对来自Apple的一次不合理的拒绝。然而,在压力方面,大家都知道后面会很痛苦。

第3个艰难决定的例子是改变类的成员的修饰符,这可能不常见。一般而言,当你使用sealed和virtual修饰符时,你承担的职责并不少。在C#里,每个类默认都不是密封的,类的每个方法都不是虚方法。在Java里,方法的情况有所不同,默认都是虚方法。从设计的角度来看,密封类更好。事实上,如果一开始你就知道一个类是密封的,你也据此创建你的代码,后来发现这个类支持继承更合理,你可以在不产生破坏性变化以及不损害兼容性的情况下把它改成非密封的。虚方法和类以及类的成员的可见性几乎也是这样。反过来的情况就不会那么顺利了。你通常不能在不破坏现有代码的情况下密封一个类或者把一个虚方法标记成非虚的。

4.环境使决策变难

大约10年前,在一个研讨会上,我们听到一个主讲者提到那段时间的一个大实话。他声称移动和云计算对CTO来说是最大的烦事,他们试图了解可以用两者来做什么。

这点记忆引出一种比较痛苦的架构艰难决定。当你知道某个技术或者某个模式可能有用却没有任何具体的证明时,你就会面临这种决定。你到处寻找案例分析,非常渴望找到它们,或者任何有助于确定采纳或排除某些东西的文献。

就众所周知的最佳实践做出决定很容易,因为有大量案例分析。然而,就新的技术(如NoSQL)做出决定相对来说比较困难,因为真实应用程序的案例分析少之又少,有时候甚至带有偏见。

这就是经典的青少年性思维:那个年龄的每个人都在谈论,但没人真正知道如何做。更过分的是,那个年龄的每个人都认为别人在做,所以每个人都认为自己也应该做。与真正的青少年之间真正的性一样,唯一的解决之道就是:放手去试!

需求经由首席架构师处理之后会交由开发团队实现。每个人都同意好的架构和好的设计可以加快模块的实际开发。但构建应用程序实际上应该怎么做?

原则上,构建软件是3个群体共同努力的结果:项目管理、开发以及测试/质量控制(QA)。任务的划分理论上堪称完美。项目管理团队负责产品规范、质量标准的定义,以及日程安排。开发团队确保使用理想的算法以及根据最合适的模式写代码。最后,QA团队对应用程序的行为进行压力测试,目的是找出它的缺陷,以便通过更新使之变得更加强大。

3个团队在这个过程里必须协同工作,虽然某些项目的管理工作需要在开发工作开始之前完成,并且测试也要在某些代码构建之后才能开始。软件开发的整个理念是为团队定义协作规则以及定义他们应该如何互动。软件开发方法学正是为了解决这个问题。

方法学基本上有两个主要类型:瀑布和敏捷。使用瀑布模型的项目倾向于按顺序经历一系列阶段,以软件的发布告终。使用敏捷模型的项目倾向于来回迭代任务多次,直到应用程序准备妥当为止,有时候是因为最后期限快到了。选择方法学在某种程度上决定了架构流程的类型。

1.前期架构

前期架构(upfront architecture)要求一切都在开始之前安排妥当。大量设计工作会在开始写代码之前完成。编码通常被看作把定义明确的想法实际翻译成编译指令。

瀑布模型可以追溯到20世纪70年代。在这种模型里,软件开发会按顺序从一个阶段走到下一个阶段。基本上,仅当第N步100%完成并且所有人都满意才会进入第N+1步。图1-3给出了瀑布模型的示例。

图1-3 瀑布模型

在现实世界里,阶段之间存在某种重合是可以接受的,不过通常是指制订计划、做预算以及安排工作日程同时进行。这个方案对于客户来说很棒,因为它提供了一些明显的确定性。然而,现实有所不同。项目最终超出预算和最后期限,有时候甚至不能按照预期的需求交付,这些都不是罕见的。这种糟糕的结果在很大程度上是因为要在前期花费大量精力进行设计,这与现代软件的实际运作并不相容。

软件设计的根本问题是需求变化很快,当开发结束时,一些需求可能已经不同了。没有客户愿意为他们明知不能满足所有需求的软件付款。此外,客户根本不喜欢改变和新增的东西带来的可变成本。

有鉴于此,瀑布模型已是明日黄花,你可以将它的死亡归咎于软件开发是一种工程学。

2.渐现架构

今天对架构流程的看法是,任何团队都应该尽快开始开发,然后获取早期反馈,在真实代码上改进软件。这意味着快速前进,接受甚至拥抱变化,尽早交付一些有价值的东西,以及欢迎反馈。

渐现架构(emerging architecture)是增量构建软件的流程。初始启动之后,项目会经过一系列迭代,包括设计、编码和测试。每次迭代产生这个系统的一个可交付但不完整的版本。每次迭代,团队会着手处理设计更改,添加新的功能,直到符合整个规范为止。图1-4所示给出了迭代流程的示意图。

图1-4 迭代模型

迭代开发形成敏捷方法学的基础。“敏捷”这个术语是特意挑选来表明与瀑布模型等重量级方法相反的立场。敏捷方法背后的原则在“敏捷宣言”里列出,首次发布于2001年,你可以在http://agilemanifesto.org找到。

在敏捷项目启动时,可能只有一些需求完全定义出来,但你知道在项目结束之前会有更多需求呈现出来或者要被澄清。凭借敏捷思维,这不是一个问题。开发过程会在迭代中变得清晰。在迭代开始时,你会和客户商谈现有需求应该实现的部分。在迭代过程里,你每次只会关注和实现单个需求。在迭代结束时,你交付一份可工作的软件。它可能是不完整的,但它可以工作。接着,你进入另一个迭代,关注另一组需求。如果在此期间某些东西发生改变或者被证明是错的,就会进行重构。这个过程会持续到没有更多东西要添加为止。迭代的长度以周为单位——通常是两周。总之,敏捷流程非常敏捷,足以应对变化。而变化在业务里是常态,不是例外。

敏捷方法学是一个概括性术语。当你提及敏捷方法学时,你并没有精确指明你实际想说的是哪种方法学。

软件开发最流行的敏捷方法学是极限编程(XP)。在XP里,各个阶段的执行都是极短的迭代,两周就会结束。编码和设计同时进行。若想更多了解XP,请到http://www.extremeprogramming.org

Scrum是另一个流行的敏捷方法学,但它针对的更多是管理项目而不仅仅是开发代码。Scrum并未指定任何软件开发模型,但它能与作为开发代码方法的XP很好地协同工作。若想更多了解Scrum,可以看一下Ken Schwaber的《Agile Project Management with Scrum》(Microsoft Press,2004)。

重要:

敏捷架构有时候会展现出矛盾的一面,就像在说,如果你使用敏捷,就不用做任何架构分析,你直接开始编码,关注功能,并且完全忽略建模。老实说,我们的观点是,虽然敏捷不排除任何这样的行为,但这种看法通常没有事实根据。根据我们的经验,敏捷的做法通常刚好相反,架构和建模问题的处理会贯穿整个开发的生命周期。

3.带有前期分析的渐现架构

值得一提的是还有第3种方案,介于瀑布和敏捷之间。这个过程包含大量初步的前期分析,然后开始采用经典的敏捷方法学。有些人把它称作非常有纪律的敏捷方案;也有些人把它称作Sprint Zero。

然而,更一般地说,我们发现所有软件开发方法学都有一些共同的特征:若干需要经历的阶段、若干将会产生软件的迭代,以及单个迭代通常的持续时间。所有阶段按顺序执行,至少有一个迭代以软件交付告终。不同方法学之间的区别是每个阶段进入的顺序、需要的迭代数量以及单个迭代的持续时间。接受这个假设之后,采用敏捷方法的步骤会比你原先所想的少很多。

如你所见,架构通常是关于难以更改的决定。需要有人做出这些决定。

架构设计基于需求分析。分析确定系统要做什么;架构决定如何去做。需要有人了解这个“什么”来确定这个“如何”。

架构师正是把需求和规范关联起来的专家。但架构师的职责是什么?需要哪些技能?

根据ISO/IEC 42010标准,架构师是负责系统架构的个人、团队或组织。架构师与分析师和项目经理互动,评估和提议系统方案,以及协调开发团队。

架构师参与开发流程的所有阶段,包括需求分析和架构设计、实现、测试、集成以及部署。

具体而言,架构师的主要职责是:确认需求,把系统分解成更小的子系统,识别和评估技术,以及制定规范。

1.确认需求

在软件项目里,有些事情是在架构师参与进来之前发生的。一群分析师、IT经理以及高管见面、讨论、评估以及谈判。一旦确认新增的或者更新系统的需求,而且也有预算,分析师就会引出需求,这通常基于他们对业务、公司流程、环境以及最终用户反馈的了解。

当需求列表准备好时,项目经理通常会与架构师见面,交付一堆东西,然后说:“这是我们(认为我们)想要的,现在你来构建它。”

架构师确认需求,尽力在设计里采用和满足它们。

重要:

刚刚我们提到另一个角色:项目经理。项目经理这个角色在不同公司可能有不同定义。我们认为这个角色负责选定方法学,安排工作,跟踪进度,回报情况,以及充当技术人员和业务人员之间的有效桥梁。充当这个角色的人与充当架构师角色的人可以是同一个人。当这种情况发生 时——而且并不罕见——需求就会从领域专家直接流向开发团队。如果中间有其他人,就会出现领域知识存在误差的风险,就像那个儿童游戏,一个孩子在另一个人的耳朵里小声地说一个词。第二个孩子告诉第3个,如此类推,直到无法猜到原词是什么为止。因此,通过统一语言表达的需求不经过中间渠道或只经过直通渠道(pass-through layer)从领域专家流向开发团队的过程非常关键。

2.分解系统

根据需求,架构师把整个系统描述成在进程里运作的更小的子系统和组件的组合。在这种情况下,架构师构想逻辑层、服务,或者两者都有。然后,根据环境,架构师决定各层的接口,与其他层的关系,以及系统所需的服务级别。

注意:

在这个阶段里,架构师评估各种架构模式。逻辑层是一个常见的选择,也是贯穿本书的一个选择。逻辑层要求垂直分布功能。分区(partitioning)是另一种方案,所有部件都在同一逻辑层次上,分布在一些共享实体(如对象模型或数据库)周围。面向服务架构(SOA)和六角架构(Hexagonal Architecture,HA)等模式倾向于让组件(SOA里的服务,HA里的适配器)在同一逻辑层次上运作和交互。微服务是另一个新近的架构模式,它的核心概念是专门和独立的组件。

整体设计要与企业目标和需求保持一致。特别地,整体设计是由需求驱动,而不是驱动需求。

理论上,产生的架构受到一般原则的启发,比如说,降低组件之间的耦合性,提高组件内部的内聚性,以及赋予每个组件一组清晰的职责。

产生的架构也会受到非功能性需求的驱动,比如说,安全、可伸缩性,以及允许或禁止的技术。所有这些方面都引入了进一步的限制,在一定程度上界定了架构师可以寻找解决方案的范围。

最后,架构师也会制定策略,根据系统的布局把每个组件的开发任务分配给个人开发者或者开发团队。

重要:

软件架构没有绝对真理。没有数学规律(或者建筑工程里的建筑条例)可以帮你做出选择。X公司可能发现A架构很成功,与此同时,Y公司却抛弃了它,采用B架构。事实上,两个都可能完全正确。上下文才是王道,要相信直觉。

3.识别和评估技术

确定需求并设计系统的各层之后,架构师接下来需要把逻辑组件关联到具体的技术和产品。

架构师通常了解可能与项目内容有关的产品和技术的代价和好处。架构师推荐使用的任何技术和产品都是他认为对项目有利和划算的。

架构师没有选择技术,只是根据自身技能提出建议。

架构师可能会建议使用Microsoft SQL Server 2014,因为它的新的聚集列存储索引(clustered column store indexes),或者可能选择由ASP.NET Web API后端支持的单页Web应用程序。类似地,架构师可能会提倡使用本地NoSQL文档存储而不是某种云端表存储。

谁对使用哪些技术和产品做最终决定?

一般是项目经理或者管理预算的人。架构师的建议可能被接受,也可能被拒绝。如果建议被拒绝,那么使用或者不使用某个产品或者技术就会变成一个新的非功能性需求,它甚至可能对架构产生重大影响。

4.制定规范

架构师最终负责系统的开发以及协调开发团队的工作。技术规范是架构师与开发者沟通架构决定的手段。

规范可以有多种形式:UML草图、Microsoft Word文档、Microsoft Visio图表,或者可工作原型。

沟通对架构师来说是非常关键的。沟通会发生在架构师与开发者之间,也会发生在架构师与项目经理和分析师之间,如果不提用户的话。架构师需要具备的一个重要特征是语言清晰。

架构师与开发者之间的互动会因为所选的方法学而有所不同。项目经理、分析师和用户的参与也会因为你所接受的敏捷级别而有所不同。

架构通常预示了一个被称为“架构师”的角色。根据ISO/IEC,架构师没有不同类型。架构师就是架构师。仅此而已。

但是,如果你环顾周围(以及查看简历),你会看到不少架构师的定义。最终,这是一个被过度使用的词,根据环境、公司,甚至国家,它真的会有非常不同的含义。

1.你知道多少种架构师

在美国,企业架构师(Enterprise Architect,EA)几乎与应用程序开发毫无关系,因为这个角色的人有90%的时间都在关注与IT相关的业务策略、业务编排以及基础设施。简单地说,EA是把业务和IT结合起来的人。这个角色的人选可能对软件架构知之甚少,甚至对分层架构或DDD一无所知。

在很多公司里,负责艰难决定以及建议方案和技术的角色甚至不被称作“架构师”,得到的头衔可能是首席开发者或者类似的名称。

因此,你可以找到企业架构师、基础设施架构师(Infrastructure Architect,IA)、特定技术架构师(Technology-Specific Architect,TSA)以及解决方案架构师(Solution Architect,SA)等称号。所有这些差异都是某种误导,因为它们试图把最终不可分割的复杂角色分解成不同部分。我们认为,它创造了不必要的分类,种下了困惑的祸根——“谁做什么”场景。

在这本书里,我们使用ISO/IEC的架构师定义,即“负责系统架构的个人、团队或者组织”。把这个概念对应到大多数公司就会发现我们所说的架构师是软件(或解决方案)架构师或者首席开发者。

2.架构师的角色

你可能已经有所了解,但如果你去Microsoft TechEd大会,你会发现架构这边几乎没有关于软件开发和架构的现实问题的会议。由于这个原因,我们在过去这些年里提交的大量DDD会议常常被拒绝。除了Microsoft TechEd大会的员工,架构师通常是关注企业架构的角色。而Microsoft TechEd大会上的所有DDD会议都属于开发这边!

在同一个项目组里有多个架构师是没问题的。同样地,不同的架构师拥有稍微不同的技能,即使不是最想要的,也是没问题的。但是,正如我们在本书里强调的,不管官方头衔是什么,架构师与代码有着密切的联系。他们构思系统的设计,然后与开发者密切合作,确保恰当实现。

我们认为,除了其他方面,架构师是一个更好的更有经验的开发者。我们不相信只会使用UML和Visio并把实现细节扔给开发者的架构师是有价值的。至少,当遇到这些人时,我们从未发现他们易于相处。

通常,由于架构师这个术语存在各种含义,大量私下的定义和解释也导致了误解的增长。下面来看其中的一些定义,我们也想对它们进行澄清。

1.架构师是分析师

这个说法不实。架构师并不是分析师。

有时候,架构师可能会在捕获需求的过程中协助分析师澄清复杂的需求,或者清除仅为“完整性”而添加进来的千奇百怪的需求。有时候,架构师可能会与利益相关者会面。但也仅此而已。

一般而言,分析师是领域方面的专家。架构师并不一定是这样的专家。分析师会与架构师分享关于系统应该如何工作以及系统应该做什么的发现。

这个常见的误解可能是因为分析师这个词被赋予了不正确的含义。如果这个词只是表明某人对系统做了某些分析,那就很难否定架构师与分析师之间的相似性了。大约30年前,系统分析师这个术语是用来表明在设计系统时做出相关考量的专业能力。但是,那时的软件并不如今天那么重要,只是一个基本上基于硬件的系统的一(小)部分。

今天,分析师与架构师的角色通常被认为是不同的。架构师也很难充当分析师的角色。

注意:

由于角色并不总是严格划分,尤其在小公司里,同一个人可能既是分析师又是架构师。这只是意味着这个公司里有一个人很熟悉业务和流程,可以找出功能性需求,并把它们转化成开发者可以使用的规范。这些角色和职责仍是不同的,只不过这些不同的技能恰好体现在同一个人身上。

2.架构师是项目经理

这是另一个不实的说法吗?看情况而定。

架构师负责系统架构,同时协调和指导系统的开发。项目经理代表利益相关者,通过在一开始选择开发方法学来管理项目。然后,项目经理负责确保项目在时间和预算都有限的情况下开展时能够遵循架构。

如果细看架构师的角色和项目经理的角色,我们会发现它们是不同的。仅此而已。

但是,同一个人最后扮演两个角色也并非罕见。就像演艺界一样,这种事情很少发生在大公司里,但经常发生在小公司里。

总之,如果你以后想成为一名架构师,你不一定要培养项目管理方面的技能。但是,如果你拥有两个角色的技能,你可以尝试拿两份薪水。

3.架构师从不写代码

这绝对是当下热议的问题:架构师应该写代码吗?基本上有两派观点。

一派认为架构师在楼上工作,或许是顶楼。架构师仅在需要通过图表展示他们对系统的看法时才会走下开发者所在的楼层。完事之后,他们乘坐电梯上去,收拾他们的东西,然后出去打高尔夫球了。到了球场,他们会关闭他们的手机,专心打球。打完球时,如果他们发现一两个未接来电,他们会打电话回去,向那些愚笨的开发者解释图表上已经清楚说明但开发者仍然未能理解的东西。根据这一派的观点,架构师从不亲手敲下哪怕最简单的C#语句。C#?噢,不,他们接触过的最新语言,在学校里可能是Pascal,在家里可能是Visual Basic。

相反,另一派认为每个架构师本身都是开发者。把这个比喻推而广之,我们可以说,Architect这个类继承自Developer这个类,添加了一些新的方法(技能),同时又重写了(特化)另一些方法。成为架构师在一些开发者的职业生涯里是很自然的发展。架构师和开发者之间的基本差别是经验和教育。你可以通过工作积累经验,可以通过读好的书和上好的课来获得教育。此外,与普通开发者相比,架构师有能力从更高的层次看待系统。而且,架构师拥有很好的客户应对技能。

架构师可能不写太多产品代码。但他会写很多代码;他每天都实践编码;他了解编程语言、代码技术、库、产品、工具、CTP;他还使用最新的Visual Studio。在某些编程领域,架构师甚至比很多开发者了解得多。架构师可能会写工具帮助开发者提高生产力。更常见的情况是,架构师只是开发团队的一员。比如说,架构师写产品代码在敏捷环境里绝对是正常现象。在小公司里,不管选择哪种开发方法学,这也是正常现象。与此同时,在某些大公司的场景里,让架构师写产品代码可能非常奇怪,尤其是采用了传统的非敏捷的开发方法学。

我们两个呢?我们属于哪一派?

嗯,Andrea比Dino更像架构师,因为他在5楼工作。另外,Dino更像开发者,因为他写过好几本技术含量很高的ASP.NET书,更重要的是,他在二楼工作。但是,我们不打高尔夫球。Dino平时会打网球,而Andrea更喜欢壁球。我们刚被禁止采访第一派的观点。

注意:

“设计的人”和“构建的人”之间的区别在软件领域里很模糊,没有其他工程领域和它一样。这种区别一般是假定的而不是基于公认技能的。

典型的对照物是民用建筑。泥水匠具备工程师没有的独特技能。但是,没有泥水匠会质疑设计和计算,因为他们缺乏独立做出决定的技能。他们尽最大的能力完成自己的工作,完全专注于委派给他们的建筑工作。

在软件领域里,情况有所不同,因为架构师和开发者有着共同的根源。开发者越有能力,就越觉得自己可以讨论设计选择——通常都有理由。架构师对日常编程越是生疏,就越容易失去其他开发者的尊重。这导致了某种程度上的恶性循环,而当你改用敏捷方法学时,情况又会奇迹般地好转。

架构对于现代软件来说是必需品而不是奢侈品。即使架构曾经只是附属品,现在也肯定不再是,其中的区别在于现代软件的复杂性。

我们通常会拿软件和土木工程比较,但是,当土木工程师要建一座桥时,这座桥将会建成。除此之外,这座桥总能正常使用,而且建筑成本和原先的预算相差无几。这对于很多软件项目来说并非如此。放到软件上,有时候很难确定利益相关者的承诺最终有哪些能够兑现。但可以确定的是,可能会超出原先的预算,交付的东西可能在某种程度上与预期的不同。

为什么软件会这样?

总的来说,软件开发很难像土木工程那样给出固定的规则,软件开发不是单纯的工程学,它涉及了大量的设计、创意,甚至心理学。此外,软件具有极高的动态性,它构建起来相对比较慢,却又需要和不断不断变化的业务需求保持同步。软件实践变化得如此之快,以至于任何时候都很难获得最先进的实践方法。

在本章里,我们着重探讨软件架构和架构师角色的本质。在下一章里,我们将会进一步探讨架构师在问题领域应用架构时实际要做的事情。在下一章里,我们将会更多地讨论软件项目的运作机制以及导致它们失败的可怕事情——大泥球。

以下是本章讨论的某些话题的冷幽默。


一个项目怎样才能拖上一年?每次拖一天。

——Fred Brooks

我们认为成功的软件项目是在充分了解业务需求的情况下采用靠谱解决方案的项目。我们认为成功设计的软件是在项目成功的前提下能够(在任何可能的地方)重用现有代码和基础设施,并根据可用的技术和广为人知的最佳实践不断改善的软件。

今天,成功设计的软件对于任何类型、任何规模的商业来说都是至关重要的,但更为关键的是避免质量低下的软件。烂的软件会使组织在很多地方遭受损失,比如说,响应很慢的页面会导致访问者离开你的网站,笨拙的用户界面会带来入口瓶颈,导致你提供的服务不得不面对处理队列,甚至未处理异常也会触发不可控的连锁反应,造成不可预测的后果。

软件项目很少符合预期。我们觉得每个手捧本书的读者都对这条表述有所了解。那么,什么妨碍了软件设计的成功?如果我们要对造成软件项目无法完全满足预期的原因寻根究底,我们将会无可避免地触及“大泥球”(BBM)。

BBM是一个用来描述“软件灾难”的优雅说辞。

在我们的定义里,软件灾难是指系统的发展出了问题,不受控制,并且很难修复。有时候,设计有问题的行业系统打上补丁也能勉强工作,但最终会变成遗留代码等候其他人处理。一般而言,我们认为团队应该总是把成功设计软件作为目标,即使“磁带盒”的故事能够名垂青史。事实上,根据Space.com的报道,1961年进入太空的第一个宇航员Yuri Gagarin在发射之前按照指示撕开了一个磁带盒,并对某个齿轮做了调整。

注意:

BBM的一个最新的绝佳案例是HealthCare.gov。它由超过50个供应商组成的集团构建,理论上归美国联邦政府管治。实际上,从外面来看,没有任何一个参与构建的供应商对整体质量进行负责。大多数组件没有经过集成测试,甚至不同组件之间的对接也没有及时测试。到最后,如果有人对项目的做法有所顾虑,要么被直接忽略,要么用商业理由或期限紧迫忽悠过去。但最终,他们还是千方百计让网站运行起来了。

大泥球(Big Ball of Mud,BBM)这个词几年前就有了,它是指一个系统几乎没有组织,到处都有隐藏的依赖关系以及大量重复的数据和代码,各层和关注点也没有清晰标识,也就是意大利面代码丛林。这个词是由伊利诺伊大学的Brian Foote和Joseph Yoder创造的,在他们的论文里有讨论,读者可以在这里读到这篇论文:http://www.laputan.org/pub/foote/mud.pdf

在这篇论文里,作者没有把BBM指责成最糟糕的实践;他们只是建议架构师和开发者时刻准备应对BBM风险,以及学习如何控制它。换句话说,几乎任何超过一定规模的软件项目都会面临BBM威胁。学习如何识别和处理BBM是避免软件灾难的唯一途径。

关于BBM入侵软件项目有几个基本事实。首先,也是最重要的,BBM并非一夜形成,起初也没有那么大。其次,没有单一开发者可以从头开始创建BBM。BBM总是团队的产物。

为了找到问题的根源,我们给出几个可能导致BBM的主要成因,通常发生在协作中。

1.未能捕获客户的所有需求

架构师和开发者构建软件,特别是企业软件,都有清晰的目的。软件的目的是通过高级声明来表达的,里面包含了客户想要达到的目标以及想要解决的问题。软件工程科学有一个完整的分支处理软件需求,并把需求划分成不同层次—业务需求、利益相关者需求、功能性需求、测试需求,或许还有更多。

关键是你怎么把一长串表述粗略的需求变成通过编程语言编码的具体特性。

在第1章“今天的架构师和架构”里,我们把确认需求列为架构师的主要职责之一。需求通常来自多种渠道,体现相同系统的不同视角。因此不必惊讶于某些需求相互矛盾,或者某些需求在不同的利益相关者的眼里有着明显不同的重要性。分析需求以及决定哪些需求直接对应某个特性只是架构师工作的第一阶段。

当入选的特性列表提交验证时就会进入第二阶段。建议的特性列表必须满足所有利益相关者的完整需求列表。某些利益相关者的某些需求被砍掉是可以接受的。

是的,可以接受,只要你可以合理地解释为什么砍掉那些需求。

为了设计系统解决问题,你必须完全理解这个问题及其领域。这不一定马上就能成事,也不是随便读一下需求就能成事。有时候,你不得不说“不”。大多数情况下,你不得不问“为什么”,然后讨论添加一个新的特性支持一组特定的需求有何利弊。

我们在过去几年里得到的教训是,根据不完整的需求理解来写的代码,只要能跑起来也比花几天时间寻找一个完美的解决方案更有帮助。就这点而言,敏捷开发方式更多是基于常识而不是理论。

确认需求需要沟通以及沟通技能。有时候沟通就是无法奏效。有时候双方相信的东西是都错的,然后双方最后都得尝试“救火”。于是,开发者学会抱怨没有得到足够的细节,而业务人员反驳说每一条都在文档里详细列明。

沟通问题的根源是业务人员和开发者使用不同的词汇,使用和期待不同精度的描述。此外,除了开发者,几乎每个人都认为编程远比实际的容易。添加一个新的需求对于业务和销售人员来说就像在文档里添加一行新的内容那么简单。实际上,系统的某些适应性是必要的,但会有附加代价。

因为新增或者修改需求产生的软件适应性是有代价的,但没人愿意付出这个代价,所以某些特性的适应性是通过删除其他特性来实现的,或者更常见的情况是,通过砍掉重构、测试、调试和文档来实现。当这种情况出现时,其实也经常出现,大泥球就会产生。

重要:

在上一章里,我们讨论了我们通常如何处理需求。我们想在这里简要回顾一下。基本上,我们把原始需求分门别类,使用ISO/IEC的分类作为起点。实际的分组流程就像在Microsoft Office Excel工作表里为每个分类创建一个标签那样简单。接着,我们检查各个标签,在里面添加或删除需求,这样做更有效。我们也会细心检查几乎是空的标签,尝试深入了解那些方面。最后,这个流程使我们更主动地寻求更多、更清晰的信息,从已知的数据里提取或者索要更多细节。

2.在系统发展时坚持RAD

一开始,项目看起来很容易管理。客户说这个项目不会发展成为一个大的复杂的系统。因此,你可能会选择某种形式的快速应用程序开发(Rapid Application Development,RAD),不太注意能让应用程序随规模更好向上扩展的设计方面。

如果事实证明系统会发展,RAD方案就会显示出它固有的局限性。

虽然RAD方案对于以数据为中心的小型简单应用程序(如CRUD应用程序)来说可能刚好合适,但事实证明它对于包含大量经常改变的领域规则的大型应用程序来说是一个危险的方案。

3.不准确的估算

业务人员总是想在他们确认委托和打开钱包之前准确地了解他们将会得到什么。但是,业务人员是根据高级特性和行为来理解的。一旦他们说他们想要网站只对验证用户开放,他们相信自己已经把这个问题说清楚了。他们不认为有需要指明用户应该可以通过一大堆社交网络登录。如果后面说这点没有提到,他们会发誓已经写在文档里了。

相反,开发者想要准确地了解他们需要构建什么,以便做出合理估算。在这个阶段,开发者根据具体细节思考,把业务特性分成更小的部分。问题是,只有清楚定义和确认所有需求才能准确估算。而估算会因需求改变而改变。

不确定性占主导地位。以下是一个常见的场景。

当你做估算时,指出哪里存在不确定性是极其重要的。

4.缺乏及时的测试

在软件项目里,测试会出现在各种层次。

单元测试和集成测试与开发团队有关,目的是让团队对软件的质量有信心。测试结果可以告诉团队是否做对以及做好。

就单元测试和集成测试而言,一个关键的方面是测试在什么时候写和运行。

就单元测试而言,有一个广泛的共识是,你应该一边写代码一边测试,并把测试的运行与构建流程整合起来。但是,运行单元测试通常比运行集成测试更快。集成测试可能需要更长时间来设置,每次运行之前也可能需要重设。

在一个项目里,你从其他个人开发者或者团队得到一些组件,这些组件一开始可能没有办法很好地协同工作。有鉴于此,最好把集成工作逐步摊分到整个项目,这样能使问题尽早显现。把集成测试放到最后会导致很大风险,因为这会导致你没有时间在不引入补丁以及在补丁之上再引入补丁的情况下修复问题。

5.不明确的项目所有权

HealthCare.gov这个案例告诉我们,很多供应商一起构建的系统必须有一个明确的项目所有权。

拥有这个项目的供应商或者个人需要对整体质量负责,他的职责包括检查每个部分是否达到最高质量以及兼容系统的其他部分。这号人物可以推动测试及时完成,以便尽早发现集成问题。与此同时,这号人物还可以协调团队与利益相关者之间的安排和需要,以便每个供应商都能在不损害其他合作伙伴的情况下完成自己的任务。

当项目的领导关系没有明确定义,或者像HealthCare.gov那样定义了但没有严格执行时,对项目负责就只能依靠个别供应商的美好意愿了,但每个供应商都没有理由处理他们合同以外的事情。尤其在压力之下,更容易只关注眼下可工作的东西,而不顾及整合和设计等方面。

当这一切发生时,即使只是几个迭代,意大利面代码也会产生。

6.忽略“危机”状态

技术困难在软件项目里是常见的事物,而不是新奇的事物。

面对这种困难,保持含糊和安抚客户都是没有意义的。即使项目顺利完成,隐藏问题也不会让你得到额外的奖励。但是,如果项目失败了,隐藏问题肯定会为你带来很多额外的麻烦。

作为一名架构师,你应该尽力做到开源软件那样开放、坦率。

如果你识别出某种危机,让他们知道,告诉他们你在做什么以及打算做什么。就修复提供详细的计划可能是工作的最难部分。但是,利益相关者需要知道发生什么事,以及明确团队朝着正确方向前进。有时候,详细计划的更新和已经完成的工作量足以避免你的压力增大到超出你能承受的范围。

如何识别出与大泥球有关的“危机”状态和成功项目?

再次说明,这是常识问题。对困难保持开放和坦率会为你敲响警钟,这样不好的事情就有可能在无可挽回之前被制止。

毫无疑问,作为一名架构师,项目经理,或者两者兼有,你应该尽最大努力避免BBM。但是,有没有一些清晰不含糊的征兆可以表明你正处在泥球滚动的轨道上呢?

下面给出一些普遍存在的迹象,它们会提醒你设计是否朝着有问题的方向发展。

1.僵硬,因而脆弱

你可以掰弯一块木头吗?如果你保持这样做会有什么后果?

一块木头通常是一种僵硬物质,具有抵抗变形的特点。当施加足够的力时,就会造成永久变形,木头也会断裂。

僵硬的软件呢?

僵硬软件具有某种抵抗变化的特点。抵抗程度是根据回归来衡量的。你对一个类做出改变,改变的影响会顺延到一组依赖的类。结果很难预计一个改变(任何改变,即使是最简单的)实际将会耗费多长时间。

如果你敲打一块玻璃,或者任何其他易碎物质,你会把它弄成碎片。类似地,当你在软件里引入一个更改并且把它分散到多个地方时,这个软件毫无疑问就会变得“易碎”。

就像在现实生活里一样,易碎性和僵硬性在软件里也是成对出现的。当改变软件里的一个类导致(很多)其他类因为(隐藏的)依赖遭到破坏时,你就很清楚地看到坏设计的征兆了,你需要尽快修复。

2.易于使用,但不易于重用

假设你有一个软件在一个项目里工作良好,你想在另一个项目里重用它。但是,在新的项目里复制这个类或者链接这个程序集并不管用。

为什么会这样?

当你把相同的代码移到另一个项目却不管用时,通常是因为依赖性或者它的设计没有考虑共享。二者之中,依赖性是最大问题。

真正的问题并不仅仅是依赖性,还有依赖的数量和深度。为了在另一个项目里重用一块功能,你不得不导入更多功能。到了最后,不再进行任何重用,代码会从头开始重写。

这也不是好设计的迹象。这种负面设计效果通常被称为不可移动性(Immobility)。

3.易于变通,不易于修复

在修改一个软件的类时,你通常会找到两种或更多方式来做。大多数情况下,一种方式绝妙、优雅,并且符合设计,但实现起来很困难,也很辛苦。相反,另一种方式编码起来很顺畅,也很快速,但它只是一种修补。

你应该怎么做?

事实上,两种方式都可以用来解决问题,取决于给定的期限以及你的经理的安排。

一般而言,变通方案(workaround)比正确的解决方案看起来更快更易实现并不是一个理想的情况。事实上,问题并不仅仅是单纯的额外劳动。有时候,你就是害怕基本变通方案之外的选择。回归才是真正让你感到害怕的东西。如果你有好的充足的单元测试,至少你可以肯定任何回归一旦出现都能很快捕获。但接着你又开始想,单元测试又不能修复代码,或许变通方案就是解决问题的最快方式了!

一个特性更易修补(hack)而不是修复(fix)对于你的整个设计来说并不是一个很好的评述。它只是意味着类之间存在太多没有必要的依赖,而你的类也没有形成特别有凝聚力的代码。因此,这已经足够吓唬你远离正确的解决方案了,它可能比快速修复更有强迫感,也需要更深层次的重构。

这种负面设计效果通常被称为粘稠性(Viscosity)。

文字上,另一个经常用来表达在烂代码上构建软件密集型系统的词语是技术债务(Technical Debt,TDBT)。

Ward Cunningham提出的债务比喻非常形象,正如财务债务,烂代码也会随着时间增长,累积需要偿付的利息。从长远来说,TDBT会变成一个重担,影响甚至妨碍后续的偿付措施。改编温斯顿·丘吉尔爵士的一句名言,我们可以说,“对于开发团队来说,TDBT就像一个站在木桶里的人尝试通过手柄把它抬起。”

TDBT是一个抽象的概念,要有工具来测量甚至去除,就像要求你会施展魔法一样。但是,仔细观察一些事实,并对此进行一些分析总结出一些指标。虽然结果不一定就是你想要的,但至少它们提高了你的警觉。

下面来看一些指标和工具,它们可以帮你判断BBM是否在试图咬你。

1.静态代码分析

一般而言,团队的人知道大多数问题出在哪里,但有时候你需要提供与代码有关的问题的证据,而不是仅仅在口头上提及它们。静态代码分析工具为你扫描和探测代码,为日后讨论提供一份有用的报告。

Microsoft Visual Studio 2013有它自己的静态代码分析器,可以计算代码覆盖率和圈复杂度(Cyclomatic Complexity)。其他类似的框架包含在CodeRush和ReSharper等产品(本章稍后会有讨论)里,或者同一个供应商也提供独立的产品。

一个有趣的静态分析工具是NDepend,它也可以为你创建依赖图,便于查看最有问题的区域的数量和位置。

重要:

不管看起来怎么样,静态代码分析工具既不会告诉你技术债务的根源是什么,也不会告诉你需要做什么才能减少它。它们只是为你的决定提供输入,而不是为你做决定。

2.知识孤岛

另一组可以用来识别某种TDBT的有趣指标是团队的瓶颈技能的数量。如果团队只有一个人拥有某些技能,或者负责某个子系统或者模块,一旦这个人离开公司或者突然不可用了就会出大问题。

知识孤岛或者信息孤岛通常用来描述代码的所有权落在个人的肩膀上。这对于个人来说可能不是问题,但对于团队来说绝对是个问题。

术语:

我们刚才使用的两个术语在不同的团队里可能有不同的含义。这些术语是模块和子系统。在这里,我们只是用这些术语来表示代码块,除此之外没有其他特别的含义。子系统通常是指整个系统的一个子集;模块则是子系统的一个部分。

如果你问:“什么导致项目失败?”,你得到的最常见的回答可能会把失败归咎到与业务有关的问题,比如说,缺少需求,项目管理不到位,成本估算不正确,缺少沟通,甚至各个团队的人员相互不配合。你很难看到坏代码可能导致问题这种情况。

有鉴于此,我们认为未被发现的BBM可以严重损害软件项目,但未能处理的BBM却可以真的毁了它。

最终,个体以及个体之间的实际互动才能真的决定软件项目的成功或失败。但是,组织结构及其整体文化也会影响最终结果。

Apple公司的组织看起来很受“一人秀”(One-man-show)创意的启发,至少在史蒂夫·乔布斯时代是这样。一个人推动创意和策略,所有团队支持和实现这个策略。只要这个创意是伟大的,策略是合适的,成功就会到来。

史蒂夫·鲍尔默在2013年夏天宣布公司重新采用的Microsoft模式(你可以读一下这篇评价:http://www.mondaynote.com/2013/07/14/microsoft-reorg-the-missing-answer)是基于分部(Division)的,各个分部之间经常相互竞争,并且难以交流。

你可能还记得发生在2008年的一件事,当时Microsoft内部的两个组出品了两个几乎等同的框架—LINQ to SQL和Entity Framework。外界很难理解是什么原因导致了这样的情况。

注意:

在Walter Isaacson写的史蒂夫·乔布斯传记里,你可以读到关于按部门划分公司的一段非常有趣的见解。史蒂夫·乔布斯分享了他对为什么是Apple而不是Sony在iPod的创意上取得成功的看法。为了实现创意,Apple首先要构建硬件和软件,然后就音乐的版权进行谈判。Sony这样的公司在硬件和软件上拥有的经验至少与Apple一样,此外,自家已经拥有音乐和电影的版权。那么,为什么Sony没有建立iPod业务呢?

根据乔布斯的看法,Sony的文化是在公司里拥有多个分部,每个分部都有自己的盈利/亏损账户。或许,从音乐版权获得盈利的分部认为从MP3播放器赚钱的分部是一个威胁。两个分部互相打架而不是为了公司的成功共同努力。这本书是Walter Isaacson写的《Steve Jobs》(Simon & Schuster,2011)。

1.团队和队员

有一个笑话是关于一个意大利队和一个德国队参加八人划船比赛的。德国队只有一个领队,其余都是队员,他们赢了这场比赛。意大利队调查输掉的原因,发现他们的队只有一个队员,其余都是领队。

团队就是让在技能上互补的人们互相合作。

注意:

划船队笑话的后续是讨论意大利队怎样计划报复赛,但这可能超出本书的范围了。不管怎样,如果你好奇我可以告诉你,意大利队解雇了队员,重组队伍,里面有4个领队、两个领队的上司、一个总司令以及一个新的老队员(因为他更有经验)。

在软件项目里,管理者和开发者都有自己的目标。管理者的做法比大多数开发者的做法更有压迫感。开发者向管理者回报,这有时会使开发者更倾向于直接接受任何任务和期限。不应该低估大多数开发者想要成为英雄的本能。开发者期望成为超级英雄,来到这个地球就是为了拯救世界。

举个例子,假设经理打算在星期一早上给一个潜在的客户做一场演示。他想确保他的演示可以给人留下深刻的印象。所以经理找到开发团队,要求在星期一准备一个演示。这可能打破当前冲刺(sprint),甚至影响了原来计划的工作。在公司或者团队里处理这种不可避免的利益冲突使用哪种方式最好?以下是几个可能的场景。

根据我们的经验,第一个场景是最常见的,而最后一个则是最令人满意的。就开发者尝试适应任何安排改动而言,我们发现Uncle Bob的看法特别有价值。承诺尝试适应新的期限,开发团队似乎在暗示自己留了一手,就像在说:“是的,我们可以接受更多工作,但出于某种原因我们一直没有这样做;现在是时候用尽我们所有精力了。”但是,如果团队没有保留精力,尝试适应更紧的期限会强迫成员加班,牺牲他们自己的私人时间。这对开发者来说是不公平的,他们也该有自己的生活;同样对管理层来说也是不公平的,他们听到了谎言。

我们有过多少次尝试适应更紧的安排?如果我们做过这样的事,我们可能是为了避免潜在的不礼貌的对抗。成为一个团队的好队员的唯一金科玉律是成为一个好的沟通者,永远坦诚,永不说谎。

这里的关键字是协商,目的是分享合理的目标,在各自需要和安排之间寻求合理的折中方案。就前面的例子而言,一个好的折中方案可能是为某些工作创建分支,使用虚构代码创建一个仅适用于演示的专门构建。这不会从主分支删除很多内容,不会导致当前冲刺出现明显延迟,也不需要团队成员加班或砍掉特性。

2.Scrum救火员

尤其在敏捷环境里,每个没有预见的事件都是潜在的危机,都需要恰当地、及时地处理。

在Scrum里,常见的做法是赋予团队的一个或多个成员救火员头衔。

Scrum救火员负责迭代之外保护其他队员工作所需的任何额外工作。就像现实世界的救火员有时候会空闲一样,Scrum救火员也会空闲,或者在迭代的过程中,在项目上保持最低活跃度。

因为成为Scrum救火员可能非常无聊,所以这个角色应该让团队的所有成员轮流来做。根据我们的经验,20%的开发精力应该是你腾出来救火的最大值。由此看来,你可能会想,你的生产力将会缩减20%;实际上,你可能会得到更高的产出。

3.领导与老板

我们都知道成本是软件项目的痛点。成本是实现所有特性所需的时间的函数,包括测试、调试、文档以及一些其他周边工作。开发团队领导会负责这些,他通常向项目经理汇报。

有时候,这两号人物相互之间缺乏信任:经理认为开发团队保留精力,开发团队认为经理只想付出更少而得到更多。

毋庸置疑,领导艺术是关键的技能。

经理腰斩估算然后抱怨项目延迟的情况并不罕见。他们跑到老板那里指责开发团队,并要求更多资源。在这种情况下,他们会体验到Brooks法则的效果,即“向已经延迟的软件项目增加人手会使之更加延迟。”

领导和老板之间有着巨大区别。

首先,也是最重要的,老板期望团队为他们服务,而不是他们为团队服务。老板位于商业等级制度的最高点,可以命令其他人执行他们不愿意做或者不会做的任务。相反,领导专注于业务以及带领开发团队走出深沟。简单来说,这个区别就是老板培养跟班,而领导培养其他领导。

有一幅很流行的图片可以很好地说明领导与老板之间的区别,这幅图片你可能已在Facebook上看过无数次了,你可以在这里找到它:http://www.lolwall.co/lol/264722

我们发现很多开发者似乎认为烂代码最终并没有带来太多伤害。

如果你数一下有记录在案的因代码问题而失败的项目个数,那么,我们认同这个数字并不会很大。但是,你不必创造真正的灾难导致软件项目损失大量金钱。

作为一名架构师,你可以做什么来帮助团队更好地写代码呢?

1.烂代码真的比好代码更昂贵

我们不清楚你的情况,但我们肯定认为写烂代码真的比写好代码更加昂贵。至少,我们认为在生命周期比较长,业务影响比较大的项目里是更加昂贵的。

听起来可能很简单,当使用烂代码(即创建、测试和维护它)的成本超过业务模型可以忍受的代价时,项目才会因它而败。同样地,如果公司设法使代码的成本保持在极低水平,没有项目会因为代码问题而失败。

这就是痛点。

你如何定义最终影响代码成本的因素?哪些动作组成了“写代码”:编码、构建、调试?你应该把测试当作一个附加的按需的特性吗?文档呢?缺陷修复呢?

有时候,管理者只是投机取巧,通过雇佣廉价开发者或砍掉测试和文档等缩减开发成本的手段来解决问题。

不幸的是,这些管理者没有意识到他们只是缩减了产生可能(但不一定)工作的代码的成本。产生刚好可以工作的代码只是问题的一面。现在,需求经常改变,复杂性不断增长,更糟糕的是,复杂性通常仅在行进的过程中才能完全了解。在这种情况下,产生代码只是影响总体成本的一个因素。代码维护和进化才是最大因素。

好的架构师都很清楚,只有写得好的代码,对软件原则和语言特性有很好的了解,恰当使用模式和实践,以及注重可测试性才能解决代码维护的问题。这使得编码比产生刚好可以工作的代码更加昂贵,但比维护和进化刚好可以工作的代码就廉价得多了。

2.使用工具辅助编码

我们认为成功的项目基于两个因素:懂得领导艺术的管理层,以及懂得代码质量的开发团队。

就编码而言,不一定有时间让开发者现在写代码,然后在往后的某个时间修复和整理它。每个开发者都会发誓,第二遍处理永远都不会发生,即使发生,也不会造成很大影响。

如果想在第一次就写出更好的代码,最好使用代码辅助工具。这些工具通常集成在IDE里,可以简化常见开发任务,使开发者的工作进展得更快,可以写出更好的代码。在最坏的情况下,代码可以写得更快,留有一些时间做第二遍处理。

自动完成、惯用设计提示(即根据语言或框架建议的惯用方式写代码)、代码检查、支持键盘输入的预定义代码片段,以及支持预定义和自定义模板等服务都是加快开发以及确保一致性和更好、更干净代码的实践。

代码辅助工具使开发得以持续发展,只需两次点击就能极大地改善你所写的代码的质量。代码辅助工具可以发现重复和没用的代码,使重构体验变得愉快,简化导航和检查,以及强制使用某些模式。

比如说,所有开发者原则上都同意适当的命名规范对于代码的可读性和质量来说是很关键的。(参见第4章“编写优质软件”。)但是,当你意识到你应该重命名一个命名空间或者一个方法时,你就会面临至少要在你自己的整个代码库里这样做的问题。这么疯狂的工作原本需要你自己在极短的时间内完成,现在可以由代码辅助工具代劳了。

ReSharper是最受欢迎的代码辅助工具。若想了解更多,可以访问http://www.jetbrains.com/resharper。其他类似工具有来自DevExpress的CodeRush(http://www.devexpress.com/Products/CodeRush),以及来自Telerik的JustCode(http://www.telerik.com/ products/justcode.aspx)。

但是,你要记住,代码辅助工具不是魔法,它们所做的只是让你付出更低的代价和更少的努力就可以写出更好和更干净的代码。除此以外,一切仍然取决于你。你在重构过程里以及在代码编辑阶段操作工具。

3.如何告诉别人他们的代码很烂

假设你发现你团队里有人在写烂代码。你会如何跟他们说?

这里涉及一些心理学方面的东西。你不想表现得尖锐,你也不想伤害任何人;与此同时,你不想其他人的工作在某一时刻伤害到你。沟通是关键,不是吗?所以你需要找到最佳方式在别人的代码很烂时告诉他们。

总体而言,我们认为让人注意到某些代码的最佳方式是不经意地问为什么用这种方式来写。你可能会找到更多背后的动机,不管是信息有误,态度不好,技能局限,或者你所不知的约束。

在没有确凿证据之前,不要断定你的编码方式更好。那么,你只需对问题代码背后的真正动机表现出好奇和兴趣,并且表达出想了解更多的意愿,因为如果换了你会用不同的方式来编码。

4.使每个人都变成更好的开发者

下面总结一下让团队写出好代码的金科玉律:

针对代码,而不是写代码的人。但通过写代码的人来尝试改善代码。

你可以通过某种方式修复任何一块烂代码。但是,当这种情况发生时,你不要责怪写代码的人;你可以帮助写代码的人改进他做事的方式。如果你可以这样做,你至少可以得到两方面的好处:你的团队得到一个更好的开发者,你的团队可能得到一个更快乐更有动力的开发者。你使这个开发者感觉更像英雄,因为他现在有了完成他的工作的最佳方式。

为了改进某些方面,每个人都需要培训和实践。最有效的方式是以敏捷的方式结合培训和实践。但是,我们经常看到一些公司购买了培训服务,让他们在短短几天内完成和交付,然后期望人们在接下来的星期一就能投入工作。事情并不是这样的,至少不会如此有效。

这让我们想起几年前非常流行的一个短语:在职培训。它指的是一边学习、一边做实际的工作。这起因于拥有不同技能的人在同一个团队里协同工作。

5.在签入代码之前检查一下

你的公司可能会有最好的编码标准,但你怎样实施它们?信任开发者是好的,但验证可能更加有效。结对编程和常规设计审核是检查代码库健康程度的具体方式。在一次典型的设计审核里,你可以和大家一起开放地讨论某些示例代码。这些代码可以是来自项目的真实代码片段,它是某些参与者写的,或者为了避免牵涉到情绪问题,也可以是为了阐明你想表达的观点而专门写的一段代码。

为了实施编码标准,你也可以考虑对你的代码控制系统采用签入策略,不管是Microsoft Team Foundation Server(TFS)、TeamCity或者其他系统。这个过程可以自动化吗?

今天,几乎任何源代码管理工具都提供针对签入文件实施控制的方式。比如说,TFS支持封闭签入(Gated Check-ins)。封闭签入本质上就是根据规则签入。换句话说,文件只有在符合既定规则的时候才会被系统接受。当你选择创建封闭签入时,TFS会要求你指定一个现有的构建脚本。只有在构建成功完成的时候,这个文件才会签入。

在TFS里,一个构建只是有一个MSBuild脚本,它可以使用各种任务来定制。TFS自带一些可以集成的预定义任务。比如说,你会找到代码分析(以前的FxCop)任务和一个运行选定测试列表的任务。因为MSBuild任务只是一个实现了约定接口的注册组件,所以你可以自己创建新的任务,添加自己的验证规则。

值得注意的是,JetBrains的ReSharper,前面提到的其中一个代码辅助工具,在它的最新版里提供了一组免费的命令行工具,可以在自定义的MSBuild任务里检测重复代码以及执行常见检查,包括根据你定义的自定义模板执行自定义检查。有趣的是,你甚至不需要ReSharper许可证就能使用这个命令行工具。若想了解更多关于ReSharper命令行工具,可以访问http://www.jetbrains.com/resharper /features/command-line.html

6.值得欣喜的是,这个项目不需要英雄

开发者倾向于超越自我,至少在他们深藏的梦想里,希望他们每周可以工作超过80小时来拯救项目,让客户满意,并且成为管理者和开发者同伴眼里的真英雄。

我们想改编诗人和剧作家Berthold Brecht的一句名言:我们总想活在不需要英雄主义的世界里。对英雄的需要以及由此而来的高压力通常源自不足的期限。

有时候,期限从项目一开始就不公平了。在其他情况下,期限是在进展的过程中被证明为 错的。

当这种情况出现时,情感上容易默许,但对指出不公平的期限的害怕产生对英雄的需要。沟通以及把问题挑明是一种坦诚,也是恢复更多控制以及降低压力的有效途径。

在软件里,我们可以说,你感到压力是因为迫在眉睫的最后期限或者缺少所需技能。如果及时沟通,两种情况都能很好解决。

我们不想要英雄,虽然我们自己也做过几次英雄(我们猜你们中的大多数也做过),我们认为英雄主义是一种例外情况。在软件里,例外通常是要避免的。

7.鼓励实践

几乎任何运动的专业运动员每天都会花上好几个小时来实践,到底是为什么呢?开发者和专业选手之间是否存在某种相似之处?看情况而定。

一种看法是开发者每天在工作中实践,并且没有与其他开发者竞争。有鉴于此,有人可能会得出没有相似之处的结论,因此没有必要实践。

另一种看法是选手经常练习基本动作,以便他们可以自动地重复这些动作。定期回顾面向对象基础、设计模式、编码策略以及某些领域的API可以使这些知识记忆得更牢固,回忆得更 快速。

注意:

写过多本ASP.NET的书,也实践过验证和成员系统,时隔多年,Dino最近在使用基于角色的ASP.NET系统时感到问题很大。“老兄”,他最近跟我说,“我上次处理角色是什么时候?”最终,创建基于角色的UI基础设施以及相关的成员系统耗费比预期更多的功夫。

8.持续改变是工作的一部分

持续改变是描述现代软件项目动态的有效方式。软件项目始于一个想法或者一个相对模糊的业务想法。架构师和领域专家需要收集一些正式的需求,使原来的想法或业务需要更加明显。

根据我们的经验,大多数软件项目就像活动目标,而需求就是把目标到处移动的东西。每次添加一个新的需求,这个环境以及系统的动态(在没有这个特性的情况下可以正常工作的设计)也会改变。需求的改变是因为问题领域有了更好的了解,问题领域变化很快,或者时间压力的问题。

需求波动(Requirements Churn)这个术语通常用来表示软件项目里的需求变化率(功能性需求、非功能性需求,或者两者都有)。高需求波动将会为BBM提供理想的藏身之所。

每当处理新的需求都重新审视整个系统的架构是切实避免BBM的唯一办法。重新审视整个系统的架构确实需要重构,也确实具有较大成本。这里的重点是找到保持低成本的方法。重构是其中一个很难察觉会为项目带来价值的东西。未能重构会导致这些价值流失,这很糟糕。

注意:

Twitter在2010年上线,当时的Web前端充满了客户端功能。大量功能是通过在动态下载的JSON数据之上即时生成HTML来提供的。在2012年,Twitter重构了整个系统,选择了服务器端渲染。这是一个架构层面的重构,毫无疑问是昂贵的。但他们认为这是必要的,并且持续服务数亿用户,不管是对还是错,反正它能工作。

作为一名架构师,架构和设计重构都是关键工具。架构师无法控制历史以及业务场景和现实世界的发展。架构师需要一直做出调整,避免重新构建。这正是重构派上用场的地方。

即使做了最好准备,也不管团队的努力如何,系统的设计都可能在某个时刻陷入困境。BBM的形成通常是一个缓慢的过程,会在一段相对较长的时间里使设计恶化。在这个过程里,你的类到处都有修补和变通方案,最终大片代码开始变得难以维护和进化。

这个时候问题就很严重了。

管理者面临与魔鬼交易的抉择,要么采用更多修补和变通方案,要么根据审核的需求和新的架构选择做一次彻底的重新设计。

重新设计一个完整系统与完全从头开始开发之间的区别是什么?就采取的措施而言,区别是极小的,如果存在任何区别的话。但心理层面的选择是不同的。如果要求重新设计,管理层传递的信息是团队正在迎头赶上并且快速修复东西。如果要求重写,管理层则是在承认失败。在软件项目里很少愿意接受失败。

当管理层要求对现有的代码库进行重大整改时,就证明了团队制造了一个软件灾难。现有的代码库就变成了某种令人感到难受的遗留代码了。

在软件里,你通常会继承你必须维护、支持或者使之保持现状并与新的东西整合的现有代码。这种代码通常被称为遗留代码。但是,架构师和开发者所面临的主要挑战不是与现有的遗留代码抗争,而是不要创建更多遗留代码。

遗留代码就是字面意思表达的那种东西。它是代码,它是遗留的。根据牛津词典,遗留这个词是指前任留下或者转交的东西。此外,这个字典还为这个词添加了一个软件特定的定义,意思是某些东西废弃了但因广泛使用而很难替换掉。

我们对遗留代码的一般定义是任何你拥有但不想拥有的代码。遗留代码被继承下来是因为它可工作。设计得好和写得好的可工作代码与设计得差和写得差的可工作代码之间没有根本区别。当你不得不着手处理遗留代码,并通过某种方式维护和进化它时,问题就来了。

遗留代码在某种程度上与软件灾难有关。但是,项目里有遗留代码本身不是灾难。当项目里的遗留代码是你造成的时候,事情就开始恶化了。如何把遗留代码(不管是你的还是从别人那里继承的)变成更可管理的代码,不会妨碍整个项目的进化和按照预期展开呢?

注意:

Michael Feathers十年前写了一篇论文,囊括了遗留代码和处理策略。这篇论文可以在这里找到:http://www.objectmentor.com/resources/articles/WorkingEffectivelyWithLegacyCode.pdf。这篇论文特别有意思的地方是作者提到的遗留代码与测试之间的关系。换句话说,遗留代码只是缺少测试的代码。

注意:

很多开发者不得不处理没有遵循软件设计基本原则和测试实践的遗留代码库,导致代码变成软件灾难。我们在讨论的过程中给出了处理烂代码的一般策略,我们也想跟你分享一个资源,它阐明了如何通过众所周知的重构技术和代码分析工具改善代码:http://blog.jetbrains.com/blog /2013/05/14/recording-refactoring-legacy-code-bases- with-resharper

总而言之,从软件灾难恢复类似于从创伤恢复。假设有一次放假,你决定出去长跑,后来遭受一些严重损伤,比如严重的跟腱拉伤。

对于非常活跃的人来说,嗯,这就是一场灾难。

你去看医生,医生进行了一些简单但有效的疗程。第一,医生要求你停止任何物理活动,包括在某些情况下行走。第二,医生在受伤的踝关节附近甚至在整条腿上绑上绷带。第三,医生建议你在感觉好一点的时候尝试走动,如果感觉不舒服就停止。这种方法行得通,很多人都能成功完成这个疗程。

相同的疗程也可以用到软件创伤上,而且通常都行得通。更确切地说,这个策略理论上是有效的,但实际效果取决于创伤的严重程度。

1.停止新的开发工作

在实施把写的烂得代码变成更可管理的东西的任何策略之前都必须先停止系统的开发工作。事实上,添加新的代码只会让设计得烂的系统变得更加糟糕。但是,停止开发工作并不意味着你要停止这个系统的工作。它只意味着,直到你把当前系统重组成更可维护的代码库,可以在现有特性不必做出让步的情况下接受新的特性,才开始添加新的特性。

重新设计一个正在进化的系统就像抓住一个正在乱跑的小鸡。你要有一个非常好的状态才能做到。但是,失败过一次的团队真能在这个时候进入良好的状态吗?

2.隔离痛处

就像医生在疼痛的踝关节附近绑上绷带一样,架构师应该用层包围写的烂的代码块。但是,这里的代码块(Block of Code)具体是指什么?指出“代码块”不是什么会不会更容易?

代码块不是一个类,而是某种跨越多个类的东西,如果不考虑多个模块的话。代码块实际上标识了系统的行为(正在执行的函数),包含了牵涉在内的所有软件组件。关键是标识出宏函数,并为它们每个定义一个不变的接口。你定义一个层实现这个接口并且成为这个行为的外观。最后,你修改代码,使每个需要触发那个行为的地方都通过这个外观(facede)来实施。

举个例子,看一下图2-1。这幅图表示了一个混乱的代码库的一个小的部分包含了两个关键组件,C1和C2,它们与其他组件有太多依赖。

图2-1 一个混乱的代码库的一个部分

这里的目的是把图2-1的布局转成某种带有更多独立区块的东西。你需要理解的是,你不一定在一开始就得到正确的设计。隔离痛点只是第一步,而且你不应该太在意你引入的层的大小。回到医生那个类比,有时候,医生会在整条腿上绑上绷带,即使受伤的地方只是踝关节附近。但是,几天休息之后,医生可能减少绷带,只覆盖踝关节附近的地方。

类似地,隔离软件痛处是一项迭代性的工作。图2-2给出了隔离图2-1的痛处的一种可能的方式。

看到图2-2时,你可能会认为_C_1和_C_2重复了;不过,这只是中间步骤,但为了得到牵涉同一个调用方的两个严格分离的子系统,这是必要的一步。分离的子系统应该像黑盒一样。

值得注意的是,代码块甚至可能跨越逻辑层,有时候也会跨越物理层。就像医生的绷带,减少隔离痛处所覆盖的区域是这项工作的最终目的。

图2-2 设计里的中间步骤:在客户端通过新的约定接口调用隔离痛块

术语:

本书将会大量提及领域驱动设计(DDD),尤其从第5章“发现领域架构”开始。我们认为这里提到的隔离区块概念与DDD的绑定上下文概念有着非常重要的关系。在DDD项目里,绑定上下文也可以是一个遗留代码黑盒。在DDD里,绑定上下文有一个与之相关的独特模型,可以由多个模块组成。最终,一些模块可能会共享相同的模型。

3.测试覆盖

一旦你把系统重构成一堆严格分离的区块,你的系统应该还能工作—只是从设计的角度来说更贴切。但是,你不能就此停下脚步。因此,强烈建议你在这个时候引入测试,它们可以告诉你系统在进一步重构之后是否仍然工作。

在这里,测试通常是指集成测试,顾名思义,测试可能会跨越多个模块、逻辑层和物理层。这种测试很难设置,比如说,需要使用专门的仿真数据填充的数据库,需要连接服务,这种测试还要长时间运行。但是,它们是绝对要有的,也是绝对要运行的。在前面提到deFeathers的论文里,他使用了“测试覆盖”这个术语来表示为后续更改定义行为不变性的测试。

注意:

测试在任何重构工作里都扮演着关键角色。任何重构之后,你都肯定需要可以检测是否出现任何回归的测试来结束这个过程。但是,在某些情况下,你可能会发现在你开始隔离痛块之前就准备好测试很有帮助。

术语:

逻辑层(Layer)这个术语通常是指逻辑边界。相反,物理层(Tier)是指物理边界。更具体地说,当我们提到逻辑层时,我们指的是在相同的进程空间里逻辑分离的代码块(即类或者程序集)。物理层意味着物理距离以及不同的进程空间。物理层通常也是一个部署目标。一些代码放到逻辑层还是物理层只是选择和设计的问题。边界才是真正的问题。一旦你有了清晰的边界,你就能决定哪些属于逻辑层,哪些又属于物理层。比如说,在ASP.NET项目里,你可以让一些应用程序的逻辑部署在与核心ASP.NET和页面一样的应用程序池的进程里,也可以部署到在另一台IIS机器上寄宿的不同的物理层里。

4.持续重构

在测试覆盖的首个迭代之后,你应该有一个稳定的系统了,也在某种程度上控制了它的行为。你甚至可能向测试输入一些数据,然后检测出来的行为。接着,你进入重构循环,在这个过程中,你尝试简化你所创建的黑盒结构。在这种情况下,你从黑盒里取出一些代码,然后通过新的接口重用它。来看一下图2-3。

图2-3 添加另一个黑盒把系统分成更可管理的部分

如你所见,C1和C2现在已经从子系统移出来,并且封装到一个新的可测试的黑盒里。重复这个过程,你可以逐渐减少黑盒的大小。一个好的影响是,现存的集成测试现在可以重写成更加接近单元测试的形式了。

Frederick P. Brooks在他的《The Mythical Man Month》(Addison-Wesley,1995)里有一句名言:向一个已经延迟的项目添加人手只会使之更加延迟。确实是这样,这样做不可能对日程安排有太大影响(这句是我们的)。然而,当项目延迟时,第一个涌进脑海的就是增加劳动力。但是,项目的活动有顺序的约束(比如说,调试要在开发之后),添加劳动力根本没有任何好处。根据Brooks的说法,孕育一个孩子需要9个月,9个女人不可能在一个月内生出一个孩子。

所以问题就变成:当项目延迟时你应该做什么?你永远都不应该考虑添加人手吗?这取决于项目延迟原因的实际分析结果。

1.需要更多时间

项目延迟的明显因素是每个特性需要的时间比预计的多。如果任务本身是顺序执行的,团队有更多的人意味着需要更多的管理工作,可能导致分配的资源没有达到最佳效用。

这个现实把焦点转移到估算上。软件人员的工作量很难准确估算。软件人员通常会认为事情最终会好起来,一切只需再多几个小时的工作就可以修复。这种态度也使监督进度变得困难。本章最开始的引言已经表明一切—一个项目每次延迟一天。如果进度可以及时跟踪,进度落后不需要很长时间就可以得到修复。在最坏的情况下,这个额外的时间可以分摊到一段更长的时间里。

但即使技术工作量的估算是正确的,另一个方面也常常被忽略。任何项目都有直接成本和间接成本。直接成本包括薪水和差旅费。间接成本包括设备和行政事务。此外,还有一些成本无法确知的东西:会议,修订,以及所有没和每个人沟通或者没被完全理解的小任务。估算疏漏是很常见的,而疏漏只能通过经验弥补—你的直接经验或者这方面专家的直接经验。很明显,你应该总是尽力清楚地写下每个需要完成的小任务,并且把它们的时间考虑进来。

一个务实的方案是在项目完成时总是比较一下实际成本和估算。如果发现有出入,把它换算成百分比,不管是低了还是高了,下次你可以使用这个因子去乘以你的估算。

一般而言,根据实际的工作模式,软件项目有两个主要的变化模式:固定价格或者时间/物资。对于前面那种情况,如果你意识到需要更多时间,作为一名项目经理,你可能会尝试寻找一种方式来重新调整计划安排。你尝试降低质量—缩减开发时间和测试,减少文档和任何对通过本次迭代的最终测试并非完全必要的活动。你也可能尝试重新商议,仔细回顾需求,看看是否有任何已经达成协议的东西可以重新界定为需求更改。如果你使用时间/物资模式,你可以设法计算过去迭代里估算时间和实际时间之间的差值,然后用它来修正每个新的迭代的估算。

2.需要更多专业技能

项目延迟的另一个原因可能是某些人并不胜任这项任务导致实现某些特性需要更长时间。如果你需要更多专业技能,不要害怕把你能找到的最好人才带进来。但是,当你需要有才能的人时,应该清楚为什么你想要把他们带进来。你可以找专家给你的人做培训,或者找他们解决问题。没有哪个做法比另一个更好。

培训带来附加价值,因为培训的结果留在公司里面,期待增值作用。同时,培训(即使提供专门定制的课件)针对的主题通常在一个比较通用的层次,需要一些额外的时间才能在当前项目里采用。另一方面,寻求咨询理论上更有效,但你要允许专家完全接触代码、人员和文档。代码库越是错综复杂,所需的时间可能越长,结果的可靠性也可能越低。

专家不会变魔法,魔法在软件里并不存在。如果一个专家会变魔法,它可能只是戏法。生活中也是如此。

尽管名字包含工程,软件工程的重点并不是工程,至少不是我们平常理解的工程。软件充满动态性,它产生的问题无法通过一组固定的规则来解决。

软件项目的头号敌人是BBM,而BBM与碎步增长和项目期限密切相关。项目的碎步增长是不争的事实,关键是要找到高效的策略来应对。这可能需要有效率、有魄力地与项目经理、客户还有利益相关者协商。领域经验引导你理智地识别最需要的功能,帮助你更好地引导客户发现他们的需求。

并非所有项目都是平等构建的,了解项目期限是另一个至关重要的因素。你可不想在设计短期项目、关键业务系统和行业应用程序时投入相同的精力。我们应该控制真实存在的复杂性,而不是创造原本没有或不该有的复杂性。

软件项目的运作机制有时候挺随性的,但成功的项目也是有的。软件项目一旦存在问题迟早都会暴露出来。防范胜于治疗,如果你能及时修复,你将节省大量金钱。

这里包含大量与计算机(以及非计算机)相关的定律和推论:http://www.murphys-laws.com。下面给出一些你可能会喜欢的内容。


相关图书

云原生测试实战
云原生测试实战
Kubernetes快速入门(第2版)
Kubernetes快速入门(第2版)
Kubernetes零基础实战
Kubernetes零基础实战
深入浅出Windows API程序设计:核心编程篇
深入浅出Windows API程序设计:核心编程篇
深入浅出Windows API程序设计:编程基础篇
深入浅出Windows API程序设计:编程基础篇
云原生技术中台:从分布式到云平台设计
云原生技术中台:从分布式到云平台设计

相关文章

相关课程