微软MVP
InfoQ中文站.NET社区首席编辑 朱永光
很早就在博客园上拜读过张逸的文章,尤其对他在设计模式方面的经验和见解印象深刻。在我把他邀请进InfoQ中文站编辑团队后,经过深入而频繁的交流,对他在整个.NET方面的学识和实践甚感佩服。尤其佩服于他深厚的文学功底,其能用优雅的文字把生涩枯燥的技术解析得浅显易懂。
理所当然,张逸在这本《软件设计精要与模式(第2版)》中,将用优雅的文字、贴切的比喻、精彩的示例为大家剖析设计的奥妙,分享他在.NET方面的丰富知识和经验。第一篇“设计之要”为目前流行的软件设计思想进行了提纲挈领;第二篇“.NET框架与设计模式”用我们每天工作的基础——.NET框架——作为最好的例子来讲解重要的设计模式;第三篇“媒体播放器的设计之旅”可以说是设计之要的完整实战演示;第四篇“设计模式应用实践”用一些与工作息息相关的实例展示设计模式的妙用;第五篇“.NET体系架构”则指引我们进入.NET应用程序架构的殿堂。
对于架构与设计模式,一直以来也是我最感兴趣的技术领域。而面向对象、设计模式、重构、测试驱动开发、敏捷编程这些思想,在我看来,即是武学中的各种秘籍,指引着我们修炼成为武林高手。但光拿到武学秘籍还不够,光勤学这些秘籍也还不够,还需要苦练其中的招数,并在一次次的比武中实践和体会。正如武学的最高境界是无招胜有招一样,软件设计的最高境界也最终是要把本书详细讲解的“招数”悉数忘记,让这些招数成为自己的习惯,自己的思维,在设计过程中自然而然地融会贯通——当然,这不是一朝一夕的事情。
通览本书,里面提到的很多思想和见解都能极大地引起我的共鸣。每当如此,常常会感叹自己没有足够时间和精力与大家分享类似的思想。因而,特意向大家推荐本书,一方面可以宽慰自己,更重要的一方面就是,很高兴张逸能给大家贡献这样一本软件设计的好书。
- 添加新评论
- 阅读次数:
一部电影巨制,巨星云集,又不失波澜壮阔的剧本,如何不值得期待?
一本经典好书,名家荟萃,精选了呕心沥血的力作,难道不值得珍藏?
QCon Beijing,在软件开发大会中,就是这么一部电影巨制,这么一本经典好书!让我们来看看导演与主演名单:
总导演:Floyd Marinescu,是 C4Media 的总裁和共同创始人,InfoQ总编。Floyd Marinescu 曾创立了 TheServerSide.com、TheServerSide.NET 和 TheServerSide Java 专题大会等。在 2000 年到 2005 年期间,在 Floyd 的带领下,TheServerSide.com 成为世界上最大的 Java 社区,曾经一度每月有高达 40 万的独立用户访问。
- 添加新评论
- 阅读次数:
InfoQ中文站(infoq.com/cn)总编辑霍泰稳
在InfoQ中文站.NET社区首席编辑朱永光介绍张逸,并希望邀请其加入编辑团队之后,我特别在网上找到张逸的相关资料研读,并粗略翻阅了他的著作《软件设计精要与模式》及译著《WCF服务编程》。一番考察之后,认为其符合InfoQ编辑人员所必备的两个特点:态度认真和技术扎实,然后郑重向其发出邀请。最终,张逸答应了我们的邀请,而我也为能和这样一位优秀的架构师合作而荣幸。通过其在InfoQ中文站上发表的作品,我和永光均为当初的决定欣慰,而张逸的作品也让网站.NET社区的内容更加充实。
很长一段时间以来,与模式相关的话题都特别引人注目。在去年InfoQ中文站举办的QCon北京大会上,来自知名网站eBay的架构师Randy Shoup介绍了eBay架构设计过程中的经验,场面之热烈,让很多参会者现在依然记忆犹新。今年Jolt图书大奖获得者Michael Nygard会带来他在系统设计过程中的反模式,虽未开场,已有多位朋友表示对此非常期待。诚然,在软件研发过程中,要找到一劳永逸的“银弹”并非易事,但类如“模式”和“反模式”这也的经验总结总会给后来者一些启发。
每个软件系统都有其独特的一面,研发所用的技术或者平台也多有不同,但仔细考察其背后的设计思想,总能发现几丝共性。而正是这些共性经过抽象之后,形成模式,然后被后来者不断传承和演进,使得我们的软件系统愈加庞大和健壮。希望通过本书,读者能够了解作者张逸对技术的真诚,也能够帮助自己在软件设计的道路上找到知音和共鸣。
- 添加新评论
- 阅读次数:
西门子中国中央研究院首席架构师
《软件架构的艺术》作者 李伟
张逸先生邀我为他的新著做序言,起初觉得难以应命。毕竟,一本书会成为很多人阅读学习的材料,并逐渐沉淀为社会文化的一部分而影响长远。长年的工程习惯告诉我,应该先认真阅读书稿,并且深刻理解书中鲜明的思想和观点后再下笔。但就个人目前的工作及精力,深感不能追求到如此完美的情景。然而,又意识到软件架构与设计工作对整个中国行业发展的重要和紧迫程度,决定借写序为契机,谈点关于架构和设计方面的点滴体会,做为本书的书序。
少年时代的我,充满了对科学的向往。儒乐.凡尔纳的科幻小说,把我带向了科学梦幻的世界,彷佛科学能够创造出美妙的未来世界。后来,对天文及天体物理的迷恋,把我强烈地吸引到了对伽利略、牛顿、爱因斯坦等伟人的崇拜。可笑的是,原想报考南京大学天文专业的我,被父母当头浇了一盆凉水。但是,一颗热爱科学的心一直在跳动。
整个大学的前两年,听课一直混混沌沌。直到大学三年级的时候,听了一位教授讲的数据结构课程,可以算是开启了我对计算机科学最初的认识。这是我第一次感知到计算机科学在很大程度上是研究人类智慧的学科,这也正是年轻的我所热望的专业!
毕业后,由于在国营单位这样的圈子中工作,又经历了一段混混沌沌。1992年后,面向对象的Borland C++ 及Turbo C++ 开始在世界乃至中国大陆范围内流行。半生半熟地阅读完这种全新的编程思想,仔细体会一番,又一次为人类智慧的结晶而震撼和赞叹。原来结构化的编程思想,虽然出自自然,但并不一定就是最好。人类居然可以模仿自然规律,来界定一个个关联的对象,可谓聪明和经典。
九十年代,是一个出国潮涌的时代,我也随着潮流,漂洋到北美。从那时开始,有两件事,真正把我从一个懵懵懂懂的年轻人,带到了计算机科学的智慧天堂。从而满足了专业工作人员的第一个要求,即知识的储备。
第一件是把自己所从事的研究工作,定位到了状态依赖的系统。这个方向的研究,彷佛打开了一扇大门,让我从只知道传统计算机科学的基础知识,加上有限的编程经验,真正地走向了专业知识的研究工作。进而使我深刻理解了国外为什么能够领先中国很多年,就已经能研发出很多严重状态依赖的实时系统。这也是我平生第一次,从软件系统的结构上,知晓了人类智慧的创造力。
另外,这个阶段也自然而然地接触到当时刚刚开始流行的Java这样相对纯净的面向对象编程语言所设计出来的一些系统。也很自然,工作中面对一个资深设计编程人员所设计出的模块结构和编写出的代码,科学之美的情感油然而生。期间,做为一个中国人,开始经常听到“架构”和“设计”这样两个有些陌生的词汇。最令我难忘的事,有个非常友善的同事,甚至还指导我去阅读一些有关架构和设计方面的知名著作。我也是从这个时刻开始,知道了Gang of Four的设计模式、Frank Buschmann(日后服务与西门子时,我的德国业务领导人)的架构和设计模式、Martin Fowler的著作……遗憾的是,由于当时自己所处工作环境的限制,没有能够更深入地体验出更多的东西,也没有一个合适的场合锻炼一下自己。庆幸的是,我已经比很多中国人早一些读到了一些经典的著作,学到了一些知识。
混混沌沌的我,在2003年底举家回到了祖国。当时的中国,正处于IT革命所带来的一片欣欣向荣的环境当中。由于是所谓的海归,自然有机会在这样的系统研发浪潮中冲锋在前,把自己一知半解的所谓经验使用到现实的系统研发工作中。着实轰轰烈烈的实践了一轮,却发现自己又一次迷失了:理论学习过了,实践也经历了,我该走向何方?
糊里糊涂地,无意间读到了一篇纪事报告,题目叫《最后的大师》。此文的作者是应钱学森先生的邀请,来记录自己的导师,清华大学物理系及清华大学创始人之一的叶企孙先生。叶先生早年在美国留学期间,在物理方面做出过杰出的贡献。虽然大多数后人并不知道叶先生,但是他的学生没有一个会忘记他,这包括三钱、华罗庚、李政道、杨振宁等等。可以这样说,你所知道的中国大师,大多都是他的学生。阅读完此文,颇受启发:真可谓“大师培养大师”。我非常欣赏这句话。既然我身边没有大师,就应该认真回味一下自己这些年来的学习和实践,看看是否能将既有的种种知识和经验,上升为智慧。毕竟,智慧是指导我继续工作的原始动力,并指导自己未来的创新工作。因此,我选择了阅读、学习和思考。
自己成长的这段历史,算是翻过去了。再回到张逸先生的这本《软件设计精要与模式》上来,我虽作粗略阅读,但从实践分享的视角来看,书的内容编写地非常认真。作者从自身工作的经历,分享了自己对软件设计的理解,并以设计原则这样的方式,来分享最宏观层面上的要点。总结、思考的分量,可见一斑。本书有些章节很有新意,注意到了利用自身实践过的设计模式,以真实示例的方式来介绍如何灵活使用各种设计模式。此举对读者的实际工作,颇有帮助,愿为推荐。
其实,个人成长的历程,也在一定程度上代表了中国专业从业人员的成长轨迹。中国正在面临一次深刻的变革,需要更多优秀的编程人员,优秀的设计人员,优秀的架构人员,优秀的创新人员。毕竟,一个要立足于世界之林的强国,急迫地需要能把事情做得精彩和经典的行动人员。
谨记所感,提供讨论。

2010年2月12日北京
- 添加新评论
- 阅读次数:
我希望告别冗长的前言,仅述说第二版的变更。写作第二版的我,疯狂地吸收了诸多大师的设计思想,这一点可以从参考文献的前后差别看到端倪。这两年以来,我又参与了几个项目的设计与开发工作,所谓“实践出真知”,在佐证大师观点的同时,自己对设计的认识更进了一步。或许,第二版不会比第一版优秀太多,但至少会减少诸多不足。囿于版本,我无法做出新的突破。我期待能创作一本全新的书,全面论述我对软件设计的认识。现在的我,还不足以写出梦想中的软件设计之道。
言归正传。
整体而言,我对第一版的所有章节都进行了一定程度的修订。或者更正了过去的错误,或者进一步完善了原有内容。本书的内容仍然是散漫而自由的,然而形散而神不散,大体遵循了设计的基本原则。
在第一篇《设计之要》中,我新增了《对象法则》一章,言简意赅地介绍了面向对象思想的核心要素与设计原则。这基于我的一贯理念,即设计模式的核心本质是面向对象设计思想的运用。只有掌握了面向对象设计思想,才能真正体会设计模式的精髓,并将其运用在实际的项目开发过程中。《对象法则》一章可以有机地与《封装变化》一章结合起来,再加上第23章《软件体系架构》的内容,基本上勾勒出软件设计的脉络,从面向对象思想到设计模式,再到软件体系架构。
在《封装变化》一章中,我不仅完善了项目实例,还增加了关于如何“解耦具体依赖”的几种技巧。对于软件设计而言,这是非常有益的指导。我整个儿删去了第一版的第5章《设计,由你掌握》,并将其中的部分内容转移到《封装变化》一章中。这使得第一篇的内容更为紧凑,虽然删去了讨论极限编程的相关内容,却可以使得我们能够更加关注于设计,而不是方法学。
第二篇《.NET框架与设计模式》增加了对.NET 3.X的源代码分析。我无法做到与时俱进,因为.NET 4.0即将走进.NET开发人员的程序生活。或许在本书出版之后的不久,还会有5.0,6.0……我只是希望我的书不要被时代抛弃得太远。好在设计模式本身属于经典,而经典总是能够经得起时间考验的。本书讲述经典,自然能讨得一定好处。
更新最明显的是迭代器模式在.NET中的实现。C# 2.0引入的yield return以及.NET 3.0引入的Lambda表达式都为迭代器模式在.NET中成为一种惯用法贡献了一份心力。我对此的分析,可以在一定程度上帮助读者更好地理解迭代器模式。在第二篇中,我新增了一章《.NET中的命令模式》,通过解析.NET 3.0引入的WF(Windows Workflow Foundation),展现命令模式的非凡价值。第二篇的内容虽然与.NET平台息息相关,但对于其他平台的开发人员而言,仍有可观之处。我在撰写本书第二版时,同样参考了Java平台的设计理念,以及Ruby中的设计模式。
从章节名称来看,我对第三篇《媒体播放器的设计之旅》进行了颠覆性的革新。事实不然,虽然内容仍有调整,但并未动摇其根本。在对本篇进行修订时,我扮演了一名重构者的角色,利用重命名和搬移内容的方法,极大地改善了既有章节的合理性。我抛开原有的以设计模式为核心的论述方式,转而从软件设计的角度看待问题。模拟真实的软件开发,我讨论了如何运用面向对象设计思想,如何对接口进行分离。当客户需要引入第三方软件时,我提出了接口适配的方案。当需求发生变化时,我则对接口行为进行了扩展或装饰。
第四篇《设计模式应用实践》仍然体现了本书的重要价值。我对第18章《命令模式应用》的实例进行了极大地完善,使得该实例在表现命令模式方面,更加丰富与完整。第19章《职责链模式应用》完全面貌一新,替换为最近完成的一个项目实例,并通过对领域进行建模,辅以用例图、时序图、通信图和类图推导详细设计,展现了“用例驱动开发”设计思想的冰山一角。
本书对软件架构着墨不多,主要的架构思想均放在第五篇《.NET体系架构》中。利用PetShop实例,对于指导读者初窥架构之美,仍有不可低估的作用。第二版对软件体系架构的内容有所补充与增强,更多地引入了企业应用架构模式和领域驱动设计的内容。第23章《软件体系架构》算得上是技术架构的入门读物,主要介绍了分层架构模式与相关设计要素。在第24章《数据访问层》中,我特别引入了.NET 4.0中的Entity Framework,算是一次有益的尝鲜。利用这样的ORM框架,还可以极度方便地实现资源库模式与工作单元模式,在诸多分层架构中,我们都可以看到它们的身影。在第28章《表现层》中,我设想了如何在PetShop中引入ASP.NET MVC框架。我本希望能有大量篇幅介绍Silverlight,以及MVP模式的运用,如此对于.NET的表现层设计方才显得完整,可惜我对Silverlight所知不多,心有余而力不足。
第二版还有诸多变化不能体现在目录中。例如我对本书的全部设计图进行了更新,更加准确、完善和美观,并保持了图形风格的一致性。第二版加入了诸多注解,大多数内容都是正文的补充与扩展,乃至思想点滴。阅读这些注解,可以帮助读者更好地理解我的设计意图,获得更多的模式知识。
本书面对哪些读者?读者又该如何阅读本书?第一版前言已经给出了答案。本书的再版并不打算彻底地改头换面。除了致谢,我不打算重复唠叨了。
钱锺书先生认为,献书仿佛魔术家玩的飞刀,放手而并没有脱手。随你怎样把作品奉献给人,作品总是作者自己的。可我还是希望把本书献给我的孩子——子瞻。当他宁静地呆在母亲肚子里时,本书的第二版同样也在孕育之中。现在,子瞻已经过了周岁生日,没有什么礼物可以比得上这本书更让我感到自豪。我还要把本书献给我亲爱的妻子。写作虽然痛苦,可哪里及得上你分娩痛楚的万分之一。抚养子瞻的辛劳,更让虚弱的你身心憔悴。本书献给你,可否给你一丝安慰?
感谢我的父母。尤其感谢我的母亲。这一年多以来,调皮的子瞻折磨得您腰酸背痛,您却没有任何怨言,反而甘之如饴。我能有时间写作本书,您功不可没。
- 添加新评论
- 阅读次数:
1. Scrum敏捷框架
1.1 Scrum概述
Scrum是一种敏捷过程,它使用迭代和增量方式管理和控制复杂的软件与产品开发。Scrum的开发流程非常简单。首先,Product Owner根据客户的需求编写Product Backlog,然后召开计划会议,评估各项功能的相对工作量,并确定Sprint的愿景和目标,生成Sprint Backlog。然后,在Sprint(即迭代)的开发过程中,召开每日会议,Scrum Master通过它了解开发的进展以及出现的问题,检查团队成员的工作与进度。迭代结束后,团队会召开评审会议,向项目关系人展示可运行的增量版本,并检查是否达到了Sprint的目标。评审会议之后的回顾会议则会总结以往的实践经验,以提高团队生产力。
Scrum的核心在于迭代。团队首先浏览开发需求,考虑可用技术,并对自身技术及能力做出评估。然后共同确定构建功能的方案,并每日调整方法,以应对新的复杂问题、困难和出乎意料的情况。团队找出并选择最佳方案去完成任务。此创造性过程便是Scrum生产力的核心[1]。Scrum的所有实践就是围绕着一个迭代和增量的过程开展的。
1.2 Scrum的不足
与XP(eXtreme Programming,极限编程)不同,Scrum并没有提供核心的价值观与指导原则,也缺乏具体的实践方法,例如结队编程、测试驱动开发。Scrum仅仅规定了实施的基本流程与检查表,它是一个开放的管理框架,重心在于项目管理,而不是指导团队成员如何进行开发。这既是Scrum的优点,因为它很灵活,能够适应大多数场景,也可以兼容并包地引入其他方法学所提倡的实践;同时也是Scrum存在的固有缺陷,使得它难以被实践。如果没有一位优秀的Scrum Master,而团队成员又缺乏自我组织和管理的能力,就会让开发过程变得一团糟,团队成员将会无所适从。
在开发实践方面,Scrum可以借鉴XP提倡的结队编程以及测试驱动开发实现编码,通过重构对编码进行调整以适应需求的变化。但是,Scrum在建模方面却是一片空白。例如,Scrum对于如何创建Product Backlog,如何建立架构模型,以及如何在编码之前进行必要的模型设计,都没有给出具体的解决方案。缺乏正确的建模活动,就可能会对Scrum开发过程造成阻碍,影响团队达成Sprint的目标。
2. 敏捷建模与Scrum的契合
敏捷建模(Agile Modeling)是一个基于实践的建模方法,包括一系列以特定原则和价值为导向的,可被软件专业人员应用到日常工作中的实际做法[2]。敏捷建模有效地将建模敏捷化,利用敏捷方法的思想对传统的建模理念进行了重新梳理,使其更加适用于敏捷开发。敏捷建模描述了一种建模的风格。当它应用于敏捷的环境中时,能够提高开发的质量和速度,同时避免过度简化和不切实际的期望。
敏捷建模可以弥补Scrum在建模方面的不足。如果说Scrum是一个对开发过程的所有活动进行了规定的基本框架,则敏捷建模由于其对建模活动的核心关注,极大地丰富和增强了Scrum的软件过程。建模在所有的软件开发中都是不可缺少的一个重要环节。传统的建模活动,常常会重视对文档与工具的使用,要求创建的模型涵盖软件开发过程的方方面面。这种重量级的建模活动与敏捷开发方法在核心思想上是相悖的。敏捷方法需要敏捷的建模,Scrum自然也不例外。
敏捷建模定义了一组与轻量级的建模有关的价值观、原则和实践,并说明如何把它们付诸实施。本文将从敏捷建模的价值观、核心原则和核心实践三个方面讨论敏捷建模与Scrum的契合。
2.1 敏捷建模的价值观与Scrum的契合
敏捷建模的价值观包括交流、简单、反馈、勇气和谦虚。前面四条来自于XP的价值观,但完全可以说是敏捷开发的价值观。敏捷软件开发宣言强调与客户交流和团队的合作。宣言对可工作软件的重视甚于详尽的文档,凸现了简单的价值观。宣言对变更的重视体现了反馈的重要性,以及拥抱变化的勇气。Scrum同样体现了敏捷建模的第五条原则——谦虚。Scrum将整个团队定义为一种角色,作为一个整体负责将Sprint Backlog转化为可运行的产品。在开发过程中,团队成员需要管理自身的工作,同时对每次迭代和整个项目共同负责。如果没有谦虚的精神,Scrum的团队是无法运作的。
2.2 敏捷建模的核心原则与Scrum的契合
敏捷建模提出了十一条核心原则。Ambler认为,只有完全采纳这些原则,才能真正地宣称自己在进行敏捷建模。Scrum虽然没有提出具体的指导原则,但在Scrum框架和实施流程中,仍然体现了部分敏捷建模的核心原则。表1展现了在Scrum项目中敏捷建模核心原则的适用性。
表1 在Scrum项目中敏捷建模核心原则的适用性
| 敏捷建模的核心原则 | 与Scrum的契合 |
| 软件是你的首要目标 | Scrum坚持所有的Sprint都结束于演示,其目的就是要交付可工作的软件。 |
| 支持后续工作是你的第二目标 | Scrum认为,需求列表是推动迭代的主要力量,只要项目有资金,迭代就不会停止。项目的后续工作属于需求列表的内容。 |
| 轻装前进 | Scrum的最终产出物除了可工作的软件外,只包括Product Backlog和Sprint Backlog。 |
| 主张简单 | Scrum主张在一开始就要保持设计尽可能简单。 |
| 包容变化 | Scrum要求Product Owner根据不断变化的商业环境对产品作出调整。 |
| 递增的变化 | Scrum属于增量式开发,要求团队在每个Sprint周期内完成一部分产品功能增量。 |
| 有目的地建模 | 与建模相关的原则,Scrum并未要求 |
| 多种模型 | 与建模相关的原则,Scrum并未要求 |
| 你需要一个技术知识工具箱 | 团队的基本要求。 |
| 高质量的工作 | Scrum要求开发过程具有可视性,提倡对最后结果会产生影响的各个方面必须是清晰可见的,同时要求频繁的检查,以及对不合格的内容进行调整。 |
| 快速反馈 | Scrum每日会议、评审会议与回顾会议反映了这一原则。 |
2.3 敏捷建模的核心实践与Scrum的契合
敏捷建模的精华在于它的实践,但敏捷建模的实践是在价值观和原则指引下体现的。它的核心实践分为四类,即迭代和增量建模、团队协作、简单性和验证。实际上,敏捷建模的实践并没有超出敏捷开发的范围之外,只不过它的关注对象被界定为建模活动而已。因此,敏捷建模的实践完全可以应用在Scrum的开发过程中。
迭代和增量建模实践与Scrum完全吻合,因为Scrum本身就是一种迭代和增量开发。既然建模活动贯穿整个项目开发周期,因而建模采用迭代与增量的方式自然顺理成章。Scrum定义了团队角色,从而突出了团队成员的协作,成员作为一个整体参与到软件开发过程中。在Scrum中,每个成员都可能是建模人员,例如Product Owner对需求进行建模,对用户界面进行建模,团队成员对设计进行建模。简单性实践要求建模人员使用最简单的工具,创建简单的内容,简单地描述模型。归根结底,模型只需要传达它应该展现的内容,不管是需求分析,还是架构设计,都应该尽可能地保持简单,既不需要考虑格式,也不需要考虑完整,甚至可以丢弃那些已经实现了的模型。Scrum大量使用了白板、索引卡、即时贴等简单工具,创建的模型非常简单,甚至是临时的。Scrum同样重视对产品的验证,避免出现错误或与需求产生偏差。
3. 贯穿Scrum敏捷过程的敏捷建模
3.1 Scrum软件生命周期
Scrum并没有明确划分项目开发过程的阶段,而是将几种会议(计划会议、评审会议和回顾会议)定义为软件开发的里程碑。如果借用软件生命周期的概念,我们可以将Scrum划分为初始阶段、计划阶段、冲刺阶段与发布阶段。初始阶段的活动主要包括组建团队、准备资源和编写Product Backlog。计划阶段包括了Sprint的两次计划会议。冲刺阶段即一个完整的Sprint迭代,周期通常不超过六个星期。发布阶段包括评审会议与回顾会议。此阶段结束后,将发布一个达到Sprint目标的增量版本。至于产品的维护,则属于Product Backlog的一部分,列入每次迭代的范围。
3.2 初始阶段的敏捷建模
Product Backlog的编写与建模活动息息相关。Product Backlog是Scrum的核心,也是一切的起源。从根本上说,它就是一个需求、或故事、或特性等组成的列表,按照重要性的级别进行了排序。它里面包含的是客户想要的东西,并用客户的术语加以描述[3]。编写Product Backlog就是对需求进行建模。根据敏捷建模“主张简单”的原则,我们在描述Backlog的条目时,通常借鉴XP对用户故事的描述方式,而不是采用用例驱动的模式。可以使用Excel工具来创建一个Backlog表。敏捷建模认为“内容比形式更重要”,在表示Backlog时,我们甚至可以使用即时贴,将其展示在白板上,使每个人都能直接看到需求模型。
编写Product Backlog时,项目的利益相关人必须积极参与,和Product Owner一起确定Backlog的条目以及优先级。Product Backlog应该能够“包容变化”,Product Owner通过与项目关系人的讨论,可以增加新的功能,或者根据新的需求变化对其进行修改。根据敏捷建模的“多种模型”核心原则,我们也可以在Product Backlog基础上,使用用例模型或用户界面模型,帮助说明Backlog的业务流程,促进开发人员对需求的理解。
3.3 计划阶段的敏捷建模
计划阶段仅仅包括两次计划会议,每次计划会议大约持续四个小时。第一个计划会议主要确定Sprint的目标以及Sprint Backlog。第二个计划会议则需要确定Sprint Backlog中每个任务的承担人,并根据实际情况裁减Sprint Backlog,生成最终的Sprint Backlog。
敏捷建模认为,项目关系人应积极参与到需求建模活动中。Scrum负责需求建模的主要是Product Owner和客户。在计划会议中,Product Owner必须出席会议,以便对Backlog的需求条目进行解释,帮助团队理解需求。
敏捷建模的核心实践要求“与他人一起建模”。在计划会议中,团队常常会对功能需求进行拆分,其目的主要是为了更容易对工作量进行估算,但另一方面也是对需求的一种细化。一种最佳实践是将需求条目细分为任务。任务与需求条目的区别在于,需求条目属于可交付的内容,是Product Owner以及他所代表的利益相关人所关心的。而任务则不可交付,它常常代表了分析、建模、编码、测试等实现需求条目的各个环节。在拆分任务期间,并不会真正开展建模活动,但团队成员在了解到需求的具体细节时,实际上已经开始考虑需求的实现。
计划会议会对Sprint Backlog进行工作量估算。Scrum建议发挥集体的智慧。方法是利用计划纸牌。团队中的每个人都可以在深思熟虑之后,出示自己手里的纸牌,根据出示纸牌的数值取平均值,就可以大致获得该需求条目的工作量。这种方式符合敏捷建模“简单”的价值观。在讨论Sprint Backlog的需求细节时,则可以使用索引卡。根据每个需求的重要程度与优先级依次将索引卡张贴在墙上。索引卡属于敏捷建模的临时模型,在失去价值之后可以考虑丢弃,而只保留更新后的Sprint Backlog。
3.4 冲刺阶段的敏捷建模
整个冲刺阶段就是一个迭代周期,即一次Sprint。在 Sprint的开始阶段,我们可以根据Sprint Backlog建立一个任务列表模型,以及一个能够直观反映开发进度和开发效率的Burndown(燃尽)模型,并形成一个任务板。任务板要尽量简单,只需要保留必要的列。Scrum要求召开站立的每日会议,通常就在任务板前完成。团队成员一边描述昨天已经做的和今天要做的事情,一边移动任务板上对应的即时贴。每日会议结束,则任务模型也随之更新,最后由Scrum Master负责更新Sprint Backlog和Burndown模型。
在冲刺阶段引入敏捷建模非常必要,它有助于解决团队在开发过程中遇到的需求、设计以及开发方面的问题。一个方法是召集相关人员举行简短的设计会议,这在敏捷建模中称为专门或即兴建模会议。通常的流程是:首先与项目关系人探讨相关的需求,可能需要创建基本用户界面模型或者讨论业务规则的逻辑;随后继续前进讨论这些需求潜在的解决方案,这时常常会画一张白板草图来帮助讨论;再往后就是继续前进编码并测试这个解决方案[4]。
Scrum团队没有设计人员、建模人员和编码人员之间的区别,它是自组织、自管理的团队。团队的每一个成员都具有项目中所有方面的参与权力,不存在单一的团队成员对系统架构、需求或者测试负责的情况[5]。这正是敏捷建模“有效团队协作的实践”的运用。
在冲刺阶段,通过引入敏捷建模,我们可以在开发过程中创建架构模型、类结构模型和测试用例模型等内容。根据项目的实际情况,我们可以选择使用UML建模工具,或直接利用简单的白板工具创建架构模型,利用CRC卡展现类的结构模型。我们还可以借助一些需求模型以及用户界面模型,深入对需求的理解。
3.5 发布阶段的敏捷建模
Scrum评审会议实际上就是一次增量产品的演示和发布。在进行Sprint演示时,需要确保清晰阐述了Sprint目标,并让演示关注于业务层次,而不要考虑技术细节。如果我们在冲刺阶段严格地遵循了持续集成原则,就可以在每次Sprint之后发布一个增量版本,供用户使用。这实际上是“快速反馈”和“包容变化”核心原则的体现。通过每次迭代发布的版本,可以及时获得客户的反馈,验证实现是否与需求相符。如果出现偏差,或者客户提出新的建议和变化,就可以将其列入到下次Sprint的范围和目标中。
回顾会议在Scrum中是一项特殊的活动。审视和适应的能力是Scrum的基础,这也是开展回顾会议的目的。在回顾会议期间,项目团队会分析Sprint中的成功经验和遇到的障碍。Scrum回顾会议不会涉及建模活动,但它对于敏捷建模而言却具有促进作用,因为我们可以在回顾会议中总结敏捷建模应用的得与失。例如我们可以讨论建模活动是否过于复杂,是否需要引入其它的建模工具,哪些模型属于临时模型或契约模型。简而言之,在回顾会议中,我们可以检查团队的建模活动是否背离了敏捷建模的价值观、原则和实践。
4. 结束语
在Scrum项目中,建模活动仍然属于必不可少的一个环节,但却是很多Scrum实践容易忽视或轻视的一部分。Scrum敏捷框架的不足主要体现于此。如果将敏捷建模的价值观、原则与实践应用到Scrum的整个开发过程中,将有利于规范Scrum的建模活动。二者的关系是框架与细节的有机契合。Scrum提供了一个基础的框架,对敏捷开发过程中的所有活动进行了规定,而敏捷建模的重点则是全部软件过程的一部分,因而需要与另一个完整的过程结合,以增强这些过程。敏捷建模是Scrum的有效补充,在Scrum中实施敏捷建模,能够提高Scrum的可操作性,并在建模活动方面给与指导与规范。敏捷建模帮助Scrum团队找到建模的最佳点,保证我们既进行了足够的建模,以保证有效地研究和记录系统,但又没有过多地建模以致变成减慢项目进展的负担。
参考文献
[1] Ken Schwaber. Agile Project Management with Scrum[M]. 上海:世界图书出版公司,2007:6.
[2] 王毅嘉,张为群. 一种基于UML和敏捷建模的JEMM方法研究[J]. 西南师范大学学报(自然科学版),2005,30(3):426.
[3] Henrik Kniberg. Scrum and XP from the Trenches[M/OL].C4Media,2008, http://infoq.com/minibooks/scrum-xp-from-the-trenches
[4] Scott W Ambler. 敏捷建模——极限编程和统一过程的有效实践[M]. 张嘉路,朱鹏,程宾,译. 北京:机械工业出版社,2003:120.
[5] Robert C Martin. 敏捷软件开发——原则、模式与实践[M]. 邓辉,译.北京:清华大学出版社,2003:7.
- 添加新评论
- 阅读次数:
一个外部具体对象的引入,必然会给一个模块带来与外部模块之间的依赖。而具体对象的创建始终是我们无法规避的。即使我们可以利用设计模式的工厂方法模式或抽象工厂封装具体对象创建的逻辑,但却又再次引入了具体工厂对象的创建依赖。虽然在设计上有所改进,但没有彻底解除具体依赖,仍让我心有戚戚焉。 以一个电子商务网站的设计为例。在该项目中要求对客户的订单进行管理,例如插入订单。考虑到访问量的关系,系统为订单管理提
- 添加新评论
- 阅读次数:
《编程絮语》之二
没有对象协作的系统是不可想象的,因为此时的系统就是一个庞大的类,一个无所不知的“上帝类”。每个对象都有自己的自治领域,“各人自扫门前雪”,对象定义的法则就是这么自私。单一职责原则(SRP)[1]体现的正是这样的道理。对象的职责越少,则对象之间的依赖就越少。这一前提就是对象具有足够的高内聚与细粒度。这样的对象一方面有利于对象的重用,另一方面也保证了对象的稳定性。
对象的职责可以是自己承担,也可以委派给其他对象。因此,有对象就必然有依赖,正如有人就有江湖。那么,我们该如何降低对象之间的依赖?第一要则是依赖于抽象,如依赖倒置原则(DIP)[2]所云。如果无法依赖于抽象,则至少应该保证你所依赖的对象是足够稳定的。事实上,最稳定的对象就是抽象对象,所以万法归一,稳定才是降低依赖的基础。
依赖之殇的源头是“变化”。变化与稳定显然是矛盾的,软件设计的最大问题就是如何协调这两者之间的矛盾。我们需要像高明的杂技师,要学会掌握平衡,能够在钢丝绳上无碍的行走。那么,如何解决变化带来的影响呢?答案是利用封装来隔离变化。
封装的一种方式是抽象,因为相对于实现而言,接口总能保持一定的稳定性。例如税收策略。对于调用方而言,只是希望能够得到准确的税值,至于如何计算,则不是他关心的内容。抽象出计算税值的接口,就能够隔离调用方与可能变化的税收策略之间的依赖关系,如下图所示:
利用抽象还可以解除对特定实现环境例如外部资源、硬件或数据库的依赖。此时抽象隔离的变化可能是外部环境提供的API。例如,在考勤系统中,利用抽象隔离不同型号考勤机的变化。
利用抽象解除对象之间的依赖,还可以保证系统具有良好的可测试性。因为调用者依赖于抽象接口,就为我们引入Mock对象(当然也可以是Fake对象)执行单元测试提供了方便。尤其是当我们对领域对象进行测试时,如果领域对象需要对数据库操作,可以通过依赖抽象的持久对象(或仓储对象)实现职责的委派。此时,我们可以引入持久对象的Mock对象,模拟领域对象持久化的职责,既分离了领域对象与数据库资源的依赖关系,又能够提高单元测试的效率。
public interface IOrderRepository
{
void Add(OrderInfo order);
void Remove(OrderInfo order);
}
public class Order
{
private IOrderRepository m_repository;
public Order(IOrderRepository repository)
{
m_repository = repository;
}
public void Place(OrderInfo order)
{
if (order.Validate())
{
m_repository.Add(order);
}
}
}
利用封装隔离变化,并非必须依赖于抽象,根据不同的场景,降低要求,依赖于较为稳定的具体类对象也是可行的。这是一种降低复杂度的设计方式。例如,我们可以引入一个Helper类来封装第三方API的调用,从而实现调用方与第三方API的隔离。例如为SQL Server数据库操作定义一个Helper类:
public static class SQLHelper
{
public int ExecuteNonQuery() {}
public DataSet ExecuteQuery() {}
}
这样的设计类似于Gateway模式[3],利用一个Gateway对象来封装外部系统或资源访问。具体类对象显然不如抽象接口稳定,因此在设计时,我们需要遵循单一职责原则。这样的设计体现了DRY[4]原则,利用封装避免代码的重复,避免解决方案蔓延的坏味道[5]。合理的封装可以将变化点集中或限制到一处,以应对变化。一个常见的例子是利用简单工厂模式,将所有对象的创建集中在一个类中(当然也可以按模块创建不同的静态工厂)。即使创建的产品对象发生了变化,我们也可以只修改静态工厂类一处的实现。简单工厂模式常常可以应用在领域层中,通过工厂对象创建持久层对象(或所谓的数据访问对象)。
依赖源于对象的协作。传递依赖的方式可以通过属性,构造函数或方法的参数。若要保证对象间的松散耦合,构造函数或方法的参数以及属性的类型就应定义为抽象类型,如前面例子中的Order类。这是依赖解耦的关键方式,完全符合“面向接口设计”的编程思想,同时,它也有利于我们在后期实现“依赖注入”。
然而,产生依赖的方式绝不仅限于上述三种情形。例如,方法的返回值以及方法体中局部对象的创建,同样可能产生依赖。比较而言,这种依赖关系更难解除,因为它与具体的实现紧密相关。换句话说,因为这两种情形的依赖都涉及到具体对象的创建,且由实现者完成,而不能转交给调用方。例如,在如下的设计中,消息头会决定消息编码的方式。

MessageHeader的GetEncoder()方法需要返回一个IEncoder对象,这就要求在方法体中创建一个具体的IEncoder对象。要解除这样的依赖关系非常困难,如需彻底解除,一种可能是利用反射技术,通过具体类的类型来创建。还有一种可能是利用“惯例优于配置”实现解耦[6]。如果不需要彻底解除依赖,也可以利用“表驱动法”,或者直接将条件分支语句封装到方法中。
如果在方法实现中需要创建一个局部对象,我们可以考虑简单工厂模式或Registry模式[3]。例如,在Role对象的IsAuthorized()方法中,需要创建一个PriviledgeFinder对象,通过调用它的FindPriviledges()方法获得角色对应的权限集。此时,我们可以在Registry对象中提供PriviledgeFinder对象:
interface IPriviledgeFinder
{
IList<Priviledge> GetPriviledges(int roleID);
}
public class PriviledgeFinder:IPriviledgeFinder
{}
public class Registry
{
private Registry()
{ }
private static Registry Instance = new Registry();
protected virtual IPriviledgeFinder m_priviledge = new PriviledgeFinder();
public static IPriviledgeFinder PriviledgeFinder()
{
return Instance.m_priviledge;
}
}
public class Role
{
public bool IsAuthorized()
{
IList<Priviledge> priviledges =
Registry.PriviledgeFinder().GetPriviledges(this.ID);
}
}
上述实现实际上仍然利用了“将变化集中在一处”的设计原则。注意Registry类中的m_priviledge属性是virtual的受保护属性,它提供了一种变化的可能,可以交由子类去实现。
如何知道一个类是否过多的依赖其他类?一个办法就是创建这个类,并保证创建的对象能够正常使用。如果创建的过程非常复杂,就说明该类的依赖过多。此时,可以考虑分解该类的职责。如果这些依赖是必须的,则可以考虑利用封装,例如将对外部对象的调用修改为在内部创建(应用builder模式);也可以考虑使用Factory Method模式或者利用简单工厂。
依赖关系不仅仅只限于类与类之间,包(组件、模块、层)与包(组件、模块、层)之间同样存在依赖关系。良好的设计需要包之间保持松散耦合。大体上讲,包之间的依赖解除与类之间的依赖解除方式是一致的。即:要求一个包尽量依赖于一个稳定的包。注意,一个包依赖于另一个包,就代表着它依赖于这个包的每一个类。Robert C. Martin说:“我放入一个包中的所有类是不可分开的,仅仅依赖于其中一部分的情况是不可能的。”[7]因此,我们可以将一个包看做是一个类,它仍然要求职责的高内聚。在包中对类的分配,就相当于是对类进行一次分类。共同封闭原则[7]要求:“包中的所有类对于同一类性质的变化应该是共同封闭的。一个变化若对一个包产生影响,则将对该包中的所有类产生影响,而对于其他的包不造成任何影响。”简言之,我们在对包进行设计时,需要避免将不同的职责耦合在一个包中,它会造成变化点的扩散。
解除包之间依赖关系的一个重要方法仍然是抽象。使用Seperated Interface模式[3],在一个包中定义接口,而在另一个与这个包分离的包中实现这个接口。例如在分层架构模式中,我们常常对数据访问层进行抽象,使得业务逻辑层依赖于该抽象层,而不是它的低层模块。
上图的设计实际上是依赖倒置原则的体现。在项目开发中,这种将抽象与实现分别放在不同的包中,是系统设计中常见的方式。这样的设计也能够更好地应用在分布式开发场景中。
[1]单一职责原则(Single Responsibility Principle):就一个类而言,应该只专注于做一件事和仅有一个引起变化的原因;
[2]依赖倒置原则(Dependency Inversion Principle):高层模块不应该依赖于低层模块,二者都应该依赖于抽象;抽象不应该依赖于细节,细节应该依赖于抽象;
[3]Martin Fowler, Patterns of Enterprise Application Architecture;
[4]DRY原则,即“不要重复你自己(Don't Repeat Yourself)”它要求“系统中的每项知识只应该在一个地方描述。”
[5]Joshua Kerievsky, Refactoring to Patterns;
[6]文章《解除具体依赖的技术》
[7]Robert C. Martin Agile Software Development:Principles,Patterns and Practices
- 添加新评论
- 阅读次数:
《编程絮语》之一

C# 的语法脱胎于C++,因而保留了virtual关键字,可以定义一个虚方法(或虚属性)。一个类的成员被定义为virtual,就意味着它在告诉自己的子 类:我准备了一笔遗产,你可以全盘接受,也可以完全拒绝或者修改我的遗嘱。显然,虚方法授予子类的权利甚至大于抽象方法。子类面对抽象方法只有重写 (override)的权利,而对于虚方法,它还可以选择完全继承。
毫无疑问,虚方法破坏了对象的封装性。如果不加约束的使用,会对调用方 造成破坏,至少它有可能破坏子类与父类之间在外在行为上的一致性。因此,当我们在重写虚方法时,务必要遵循Liskov替换原则。我们要保证对于调用方而 言,子类对于父类是完全可以替换的。这里所谓的“替换”,是指子类不能破坏调用方对父类行为的期待。准确地说,子类在重写父类的虚方法时,必须遵循调用该 方法的前置条件与后置条件。这也是“契约式设计”的思想。最理想的状态是让使用对象甚至无法知道是否存在派生类[1]。即类的继承体系对于调用者而言,必 须体现外部接口的一致性,这样才能做到调用者对派生类无知。
如果确实需要重写父类的方法,最好的方式是扩展而不是修改。这实际上也是开放- 封闭原则的体现。例如在Decorator模式中,我们重写父类方法的目的,是为了实现对该方法的装饰。Proxy模式的实现同样如此。Michael C. Feathers对此给出的忠告是[2]:
1)尽可能避免重写具体方法。
2)倘若真的重写了某个具体方法,那么看看能否在重写方法中调用被重写的那个方法。
Feathers 的忠告是针对Java语言,因为在C#中我们无法重写具体方法,只能利用new关键字在子类中新建一个相同方法签名的具体方法,而这样的方法并不具备多态 性。这里涉及到一个有趣的话题,是关于Java和C#的比较。在Java语言中,如果没有添加任何关键字,则方法默认就是虚方法,任何子类都可以重写它。 C#则相反,它对虚方法给予了显式的定义。Java语言的缔造者显然是“性本善”论者,他认为所有子类的实现者均抱着善意的态度来对待父类的方法,因而他 赋予了子类相当程度的自由,但却可能被别有用心者偷偷打开封装的**。如果确有非常重要的隐私防止被篡改,则可以利用final关键字来强制保护。C#语 言的发明者则持有“性本恶”的论调,他恶意地揣测子类总是会不怀好意,所以提供了一套默认的强权,来保护父类的隐私。如果需要对子类开放,则明确地声明为 virtual,这就牢牢地把控制权攥紧在父类的手中。
C#保守的做法使得语言的特质更加安全(当然,Java会更加自由),我们可以使用 virtual的自由性,搭配方法的访问限制,搭建一个安全合理的白盒框架。virtual关键字的含意本身就是面向子类的,所以,我们应该尽可能地将其 放在protected方法中使用。如果该方法代表的行为确实需要公开给调用者,我们可以定义一个公开的具体方法,在其中调用一个受保护的虚方法。
在Template Method模式中,体现了C#这种划分具体方法和虚方法的好处。Template Method模式要求子类只能部分地替换父类的实现,整个骨架则必须保持固定不变。在父类中,我们将模板方法定义为具体方法,将基本方法定义为抽象方法。 模板方法规定了基本方法的调用顺序,如果我们可以在子类中重写模板方法,就可能破坏基本方法的调用顺序,从而对整个策略造成影响。Strategy模式就 不存在这个问题,因为它的策略是整体的。Template Method模式在模板方法中规定的骨架,实际上就是为调用者制订的前置条件和后置条件。
有 一种说法是不要在虚方法中访问私有字段[3]。这存在一定的合理性。因为一旦我们在父类的虚方法中访问了私有字段,那么在子类重写该虚方法时,由于无法获 得父类的私有字段值,就可能会导致该字段值的缺失。但这种说法并不完全准确。一方面,我们认为Liskov替换原则主要是为了约束Is-A关系在行为上的 一致性[4],如果该字段对行为不会造成影响,则无大碍。另一方面,这也说明我们在重写虚方法时,最佳实践还是需要在重写的同时,调用父类的虚方法,如 Decorator模式的实现方式。
[1] Alan Shalloway, James R. Trott Design Patterns Explained
[2] Michael C. Feathers Working Effectively with Legacy Code
[3] Dino Esposito, Andrea Saltarello Microsoft.NET Architecting Applications for the Enterprise
[4] Robert C. Martin Agile Software Development:Principles,Patterns and Practices
- 添加新评论
- 阅读次数:
当我看到什么速成或者多少天学会某种技术时,我泰半会采取怀疑的态度。这属于典型的标题党。那么,重构能够在31天速成吗?能,前提作为读者的你必须具备非常扎实的设计技能,以及丰富的项目经验。如果真是这样的读者,恐怕一周就能速成了吧。
开个玩笑。实际上我是想推荐一本书,它的名字叫31 Days of Refactoring。这本书其实讲的并不是什么速成技巧,而是重构技术的经验荟萃。它利用大量的代码实例(C#代码)演示了各种重构模式的应用。这些示例很简单,简单到初学者都可以看会。至于重构模式的讲解,就更直观了,没有冗长的理论描述,更没有让人乏味的重构步骤,甚至没有让人小心翼翼的单元测试,只有初始与结果。重构之前是什么样,重构之后又是什么样,全部用代码来说话。干脆,直接。
最关键的,本书完全免费!!你可以通过这个链接在线阅读每一章节,也可以通过这个链接下载本书的PDF电子版。
还犹豫什么,快行动吧!Enjoye it:)
- 添加新评论
- 阅读次数:






张逸(Bruce Zhang)


