.NET 4和VS2010新技术亮点选析

微软公司宣布, 2010年4月12日,在全球同步发布.NET 4和Visual Studio 2010。  

作为微软平台最重要的产品,.NET 4和Visual Studio 2010所带来的诸多新技术成为了众人关注的焦点,本文将选取其中几个重要的技术创新点,介绍其来龙去脉,点出其设计思路,剖析其技术关键,展示其应用前景,从而帮助读者在这一轮新技术浪潮中占据先机。 
  

保证代码质量的强有力工具:Code Contract  

防卫型编程风格已为广大程序员所熟悉,最典型的做法是在函数开头书写代码检测传入参数的有效性,发现无效参数时抛出一个异常。  

“防卫式编码”简单而有效,因而在实际开发中得到了广泛应用。  

但是,任何事物都不是完美无缺的,“防卫式编码”也一样,因为它要求在执行真正的功能代码前进行一些检测工作,因此必然会引入一些额外的开销,如果某些检测工作还比较复杂,对程序运行效率会有负面影响。  

为了避免这种情况的出现,人们就想出一个“两全其美”的方法——使用“条件编译”。通过定义条件编译符号控制编译器是否编译特定的代码。  

从.NET 4开始,软件工程师们多了一个选择,这就是“代码协定(Code Contract)”。  

代码协定(Code Contract)的核心类是“Contract”,以下是一个示例:  

static double Divide(double divisor, double dividend)  

{  

Contract.Requires(divisor >= 0 && dividend > 0,  

“除数与被除数必须大于0,并且除数不能为0″);  

return divisor / dividend;  

}  

如果只是重复已有的功能,那么“代码协定”就没有太多存在的意义,事实上,它的最强大之处在于提供了一系列的静态方法实现 “协定式编程”。  

“协定式编程”要求软件工程师在设计和编写代码时必须考虑:  

  • 方法调用入口所必须满足的条件,称为“前条件(precondition)”
  • 方法调用出口所必须满足的条件,称为“后条件(postcondition)”。
  • 方法调用前后,必须始终满足的条件是什么,称为“不变量(invariant)”。

由此可见,“协定式编程”能清晰地传递“原始”开发者的设计意图,使得代码调用者在编写新代码时有所参照,无须另外查找文档。  

.NET 4将代码协定相关的类型移入到.NET核心程序集Mscorlib.dll中,成为CLR功能集的组成部分,对所有.NET编程语言“一视同仁”地给予了代码协定的支持。 
  

使用MEF开发拥抱变化的软件系统  

.NET 4引入了一个“Managed Extensibility Framework(MEF)”,开发支持插件的软件系统变得前所未有的简单。 
  

MEF的基本原理  

将一个MEF应用程序可看成是多个“可组合部件(Composable Part)”的集合,通过简单地给部件附加“[Import]”和“[Export]”标记,可以清晰地表明部件之间的“服务消费”与“服务提供”关系(图 1)。  

图1  可组合部件示意图 图1 可组合部件示意图 

“导入(Import)”描述了一个部件需要什么。“导出(Export)”描述了一个部件能提供什么。  

从面向对象的角度来看,MEF其实就是将类之间的组合关系转换为“导入(需要外界提供的服务)”和“导出(向外界提供服务)”罢了。  

导入与导出之间必须相互匹配,匹配的标准由“协定(contract)”进行描述。协定由“协定名(ContractName)”和“元数据(Metadata)”组成。  

协定名通常就是导出所关联的字段或属性类型的完整名称。  

元数据其实是一个“名称/值”的集合,在实际开发中,程序员可以给部件附加特定的元数据,然后在部件宿主程序中查询这些元数据,完成特定的功能。  

装配部件为一个完备的软件系统的过程称为“组合(Compose)”,由部件“组合容器(Composition Container)”完成。每个需要装配部件的.NET部件宿主程序都至少创建一个部件组合容器对象。  

部件组合容器为了完成“为部件宿主程序创建合适的部件对象”的工作,必须知道以下信息:  

  • 有哪些部件可用。
  • 这些部件之间的依赖关系。

MEF使用 “部件编目(Catalog)”来封装上述信息。  

部件组合容器通过查询Catalog,就能获得它所需要的信息,然后,在底层使用反射动态地完成组件识别、装配工作。 
  

MEF的具体应用  

在实际项目中,我们可以将系统中需要动态扩展或升级的部分封装为部件,将其编译为独立的程序集(一个程序集中可以包容多个部件),通过MEF完成部件的实时装配。由此可见,MEF可用于实现系统的可扩充点,多用于应用系统的中间层。  

需要特别指出的是,Silverlight 4支持MEF。  

可以给一个Silverligtht页面附加[Import]标记,指明它可以装配部件,类似地,通过给Silverlight“用户控件(User Control)”附加[Export]标记,每个用户控件就可以成为一个可组合的部件,我们可以将这些用户控件编译为独立的Silverlight程序集,放置在网站的特定文件夹下。  

当用户访问Silverlight应用程序时,开始可以只显示一个“初始的简单的”页面,当用户需要时,再动态从Web网站上下载新的Silverlight程序集(这利用了Silverlight所支持的“按需下载(download on demand)”功能),然后,Silverlight客户端应用程序再使用MEF将程序集中所包容的页面组件“组装”为一个新的功能增强了的Silverlight页面。  

应用这种技术,可以减少Silverlight客户端与Web服务器间的数据流量,提升Silverlight应用程序的响应性能,同时,也使得Silverlight应用程序的动态扩展变得简单。 
  

迈入多核时代  

多核化是CPU确定不移的发展方向,双核CPU早已普及,装备有四核CPU的个人电脑正在热卖,所有一切都告诉我们,硬件技术的发展推动着软件开发进入了多核时代。  

.NET 4为程序员提供了强大的工具以迎接这一技术挑战。 
  

线程取消模型  

.NET 4在多线程开发中新推出了一个引人注目的线程“统一取消模型(Unified Model for Cancellation)”。  

这个模型的核心是两个类:CancellationToken和CancellationTokenSource。  

CancellationToken称为“取消令牌”,它用于代表一个外界的“取消操作”请求。  

.NET4所提供的线程统一取消模型可以简述如下。  

  • 创建CancellationTokenSource对象,它包容了一个取消令牌对象,外界可以通过其Token属性获取“取消令牌”。
  • 设计可以被“中断”的线程函数,在此函数中检测取消令牌的IsCancellationRequested属性,从而知道是否应该中止执行。
  • 需要取消线程执行时,调用CancellationTok-enSource类的Cancel()方法即可。

.NET 4所引入的线程统一取消模式,规范了多线程程序中“提前结束”线程的方式,在.NET 4新增加的类和组件中得到了较为广泛的应用。 
  

并行任务库  

“任务并行库(Task Parallel Library :TPL)”是.NET 4为帮助软件工程师开发并行程序而提供的一组类,位于System.Threading和System.Threading.Tasks这两个命名空间中,驻留在3个.NET核心程序集Mscorlib.dll、System.dll和 System.Core.dll里。使用这些类,可以让软件工程师在开发并行程序时,更关注于问题本身,而不是诸如线程的创建、取消和同步等烦琐的技术细节。  

使用TPL开发并行程序,考虑的着眼点是“任务(task)”而非“线程”。  

一个任务是一个Task类的实例,它代表某个需要计算机执行的数据处理工作,其特殊之处在于:  

在TPL中,任务通常代表一个可以被计算机并行执行的工作。  

任务可以由任何一个线程执行,特定的任务与特定的线程之间没有绑定关系。在.NET 4中,TPL使用.NET线程池中的线程来执行任务。  

负责将任务“分派”到线程的工作由“任务调度器(Task Scheduler)”负责。任务调度器集成于线程池中。  

1 Parallel类  

在TPL中,最容易使用的类是Parallel,此类提供了三个方法“群”用于实现三种常用的并行程序执行结构。  

(1)使用Parrallel.Invoke()方法可以让多条语句并行执行:  

Parallel.Invoke(  

() => StatementA(),  

() => StatementB(),  

() => StatementC() );  

(2)使用Parrallel.For()方法启动并行循环  

Parallel.For(0, 100, (i) => DoWork(i));  

(3)如果你有一大堆“单个”的数据,并且每个数据都需要进行同样的处理,并且这些处理可以并行,那么使用Parallel.ForEach()方法可以实现这些处理工作的并行执行。  

Parallel.ForEach(sourceCollection, item => Process(item));  

2 Task类  

TPL提供了多种手段实现多任务间的同步。例如,以下代码在task1完成之后自动运行task2:  

Task task1=new Task(()=>  

{  

DoStep1();  

});  

Task task2 = task1.ContinueWith((PrevTask) =>  

{  

DoStep2();  

});  

task1.Start();  

使用Task开发,比直接使用Thread要方便许多。  

3 并行LINQ 

PLINQ主要用于并行执行数据查询,而它本身又是.NET 4所引入的并行扩展的有机组成部分,因此,它与LINQ和TPL都有着密切的联系。 

在目前的版本中,PLINQ只实现了LINQ to Object的并行执行。 

将LINQ查询转换为PLINQ非常简单,在许多情况下只需简单地添加一个AsParallel子句就行了,例如,以下代码将把整数集合中的偶数挑出来: 

//创建一个100个元素的整数集合,保存从1到100的整数. 

var source = Enumerable.Range(1, 100); 

var evenNums = from num in source.AsParallel() 

where num % 2==0 

select num; 

与LINQ类似,PLINQ也具有“延迟执行”的特性,只有对查询集合调用foreach迭代、或者调用ToList()之类方法时,PLINQ查询才会真正执行。 

另外,PLINQ在底层使用TPL所提供的基础架构完成所有工作,因此,PLINQ是比“Task”抽象层次更高的编程手段。 

总之,在设计并行程序时,推荐按照以下顺序来设计技术解决方案: 

基于PLINQ的声明式编程方式→使用Task的直接基于TPL的“任务并行”编程方式→使用线程的基于CLR的“多线程”编程方式。 
 

使用Silverlight 4开发多层商业应用 

Visual Studio 2010的正式版本中集成了Silverlight 4,与以前的版本相比,Silverlight 4带来了众多的新变化,最大的亮点是Silverlight 4已成为了一个强大的商业应用开发平台。 

图2所示为一个典型的Silverlight多层应用程序架构。 

图2  Silverlight多层应用程序架构 图2 Silverlight多层应用程序架构 

如图2所示,在Web服务端我们使用ASP.NET应用程序作为WCF RIA Service的宿主,使用ADO.NET实体框架封装数据。 

WCF RIA Service在服务端将业务逻辑功能封装为“领域服务(Domain Service)”,Visual Studio 2010在编译项目时,会同时生成此领域服务的客户端代理类(派生自DomainContext),Silverlight 4提供了一个DomainDataSource组件作为数据绑定源,诸如TextBlock和DataGrid之类客户端数据绑定控件可以直接绑定到DomainDataSource组件。 

可以这么说,Silverlight直到4.0版本才算是成为了一个真正的商业应用开发平台,我想这将大大地促进其推广和应用。 
 

.NET编程语言新成员:F# 

函数式编程语言拥有很长的历史,但大多局限于学术界使用,直到Visual Studio 2010中新添加了F#,才使函数式编程开始走向真正的商业应用。 

Visual Studio 2010为F#提供了方便和强大的编辑手段,只需在代码编辑器中选中要执行的F#语句,按Alt+Enter组合键,就可以直接在Visual Studio 2010集成的F# Interactive窗口显示结果。 

与其他的函数式编程语言Lisp、OCaml等不一样,F#是一个“混血儿”,它同时支持函数式和面向对象两种编程风范,并且可以直接调用.NET基类库中的各种组件,它所开发出来的组件也可以被其他.NET编程语言所调用。 

由于F#中的数据结构都是不可改(immutable)的,因此如果使用它来开发多线程程序会非常简单,因为它无须耗用计算资源来进行解决多线程开发中的“竞台条件(race condition)”共享资源的问题。另外,函数式编程的风格,使得F#在实现某些计算机算法时显得简洁而高效,因此,预计F#会在计算密集型的应用系统中有较好的表现。 
 

新时代的曙光——云计算 

微软首次将对云计算的支持直接融入到Visual Studio 2010中,微软的云计算应用依托于Azure这个云计算平台。 

如果希望在云计算应用中访问传统的关系型数据,微软提供了一个SQL Azure服务,云计算应用访问此服务,非常类似于传统.NET应用程序为了方便用户开发云计算应用,微软在Visual Studio 2010中提供了一个本地的Azure开发环境(称为“Windows Azure Simulation Environment”),它使用本地的SQL Server Express作为数据存储。毫无疑问,云计算将是下一个十年中软件技术竞争的主战场,从微软在Visual Studio 2010中首次集成云计算应用开发功能这一现象上,我们似乎已经嗅到了一场技术大战的硝烟:一个新时代正在向我们走来! 

(本文来自《程序员》杂志10年04期)

解读敏捷需求分析五大关键因素

大多数学计算机语言的人都会有过这样的感受,过去一直认为编程和架构是整个软件生命周期里最了不起的部分,但实际工作后才会发现在商业产品里,需求分析才是一个商业软件成功与否的关键。

放眼望去,在当今软件工程领域出现的许多问题,诸如缺陷及资源运用不当,都源于需求的不清晰,甚至有软件人戏称:“需求变更乃万恶之源”,一时也获得了颇多响应。时至如今,业务IT间需求分析过程中存在的问题主要有哪些?什么是敏捷需求分析?产品级和项目级需求有何异同?敏捷需求分析方法论中的五大关键点是什么?就以上热点话题,雅各布森中国区总经理吴穹分享了他的看法。

三大症状

在吴穹看来,两份需求、合同式验证、产品需求缺失成为了当前需求沟通的三大症结。

两份需求——用户(业务)需求和软件需求。用户需求由不熟悉IT的业务人员完成,大多归于天马行空的意识流,基本上是想起什么写什么。而软件需求由IT人员编写,经过技术思维的过滤、梳理、增删,包含进了算法、数据库设计、架构之类的技术专业词汇,业务人员往往已不知文档内所云。

合同式验证——业务人员和技术人员企图在沟通后以合同形式将需求固化并且确定下来,而没有充分考虑到软件开发过程中可能出现的需求变更。

产品需求缺失——项目是片段,产品是总量,两者的关系在于项目其实就是一个不断完善产品的过程。由于国内PMP(ProjectManagement Professional)和项目管理流行,更多IT需求都是以项目形式存在,而往往忽视了产品需求的积累,导致最后的结果多是项目(需求)很多,但产品需求缺失。

项目级和产品级需求的具体区别,如果放在几年或十多年前并不明显,对于全新产品而言,项目(需求)=产品(需求)。随着时间推移,两者的区分逐步明朗,由于全新项目越来越少,更多的需求都是在维护和升级老的产品。以咖啡机为例,从基本型升级到1.1版,或许是加入一个按钮。此时和客户沟通的时候就需要引导客户想清楚,需要的是项目级还是产品级的需求,是做整个咖啡机的需求还是仅仅只是新添按钮的需求。如果未来再做1.2版,继续添按钮,这时候的需求又该如何写?新按钮的需求,然后和以前的按钮有些变化。如果不能明确两种需求的差异,随着项目需求的累积,到最后会发现所有的(需求)都是片段的,都是增量而缺乏一个总的全景。

事实上,业务IT需求造成如今的混乱状况,CMMI(Capability Maturity Model Integration,能力成熟度模型集成)和国内企业对CMMI的僵化理解可以说“功不可没”。在对“两份需求”的认识上,CMMI里有明确分项,用户需求和软件需求。但值得一提的是,其实里面并未明确要求是两份文档或由两部分人来写,而只是表示需求细化的两个阶段。遗憾的是,很多国内CMMI认证企业也并没有真正打算去了解它的内涵,只是僵化地表现出自己是否有这样的能力。

最近接触到一些项目也出现了这样的情形,大家先做了一份用户需求,然后花费大量时间写软件需求,以满足认证的需要,但到头来软件需求根本没人看,大家都只是应付,CMMI成为了摆设。 

需求贯穿于软件开发测试全过程

在吴穹看来,敏捷的最大贡献在于它是对整个软件工程的一次再认识。具体到敏捷需求分析领域,其实涉及到一个核心问题:是否承认(软件)需求可以在一开始就搞清楚并确定下来?敏捷的答案是No!而在传统瀑布式开发中,更多的是合同式验证的情形,大多数客户的思想基础都是基于需求最初就能确定下来的。但事实上,这在当前阶段基本属于“不可能完成的任务”,不符合软件开发本质。在敏捷需求分析中,需求应是贯穿于整个软件生命周期全过程中并在其中不断变更、迭代和完善。

敏捷需求分析认为,需求应建立在以用例为中心的需求文档体系,采取协作式而非合同式的沟通方式之上。具体可分为五个关键点:

  • 用例;
  • 协作;
  • 迭代,即需求不是一次最终确定,而是先完成主要框架,再通过迭代逐步精化;
  • 整个过程中以分析为支撑,做需求同时也在做分析,分析模型的输出结果应跟需求分开;
  • 把用例分解到用户故事,在整个软件生命周期过程中来驱动开发和测试。
 业务/技术沟通频现“两份需求”

同时还要考虑到的是,将两份需求改为一份文档,而不必死抠CMMI概念区分出用户和软件需求。首份需求稿将由SA(系统分析师)来牵头完成,负责各方协调和沟通的工作。理想的情况下,整个团队在项目开始前就应搭建完毕,包括客户、开发测试人员都参与的写作和迭代,而不是以往的由技术人员对用户进行里程碑式的教辅。通常来说,一个项目里一名SA同时对应5~9名开发人员是比较合适的。 

需求文档化与敏捷的平衡点

至于用例和用户故事。按照敏捷大师Martin博客中的说法,两者都是组织需求的方式,只是目的不同而已,用例的目的是为了把需求描述清楚,而用户故事的目的是把需求分解成可用于迭代计划的单元。对应到产品级和项目级文档,用例是产品级,例如做咖啡机,不管有多少不同版本,有些核心功能是不改变的,这些都是产品级需求。而用户故事则是项目级,属于做完就扔的“抛弃型”。

进一步理解的话,用户故事其实是一个或多个完整的业务场景,而用例是场景的抽象,一个用例里可以包含成百上千个场景。用户故事是基于开发思想的,不光要考虑业务,还要考虑如何实现包括工作量大小、任务分配、项目风险以及架构风险等多重因素。有人认为写用户故事是极简单的事,但在吴穹看来,现在有很多人都还在用功能点套用用户故事,显得不伦不类,而没有理解到用户故事的精髓。

以ATM取款为例,正常流程是插卡、取钱、把钱拿走。这个看似简单的场景其实工作量很大,可以在整个流程中做一些必要的简化。有人认为既然用户故事是一个场景,那就把它变成一个场景步骤吧,于是就成了功能点。其实他们忽略了一点,用户故事还是一个简化了但还保证完整业务价值的场景。ATM取款建立用户故事会涉及哪些因素呢?取款是否需要输入密码?小额取款时能否取消密码输入的步骤?取钱后打印账单,查询余额等,在这里面哪些功能是风险级别高的,哪些需要与银行核心数据通信?这不仅涉及(功能)优先级的问题,还可以根据原则简化用户故事。例如可以考虑做一个用户故事,储户用不需验密的卡,限额是一千块,取几百块钱的时候,把去银行验证的过程取消掉。这种情形下很多时候都要考虑到账户的风险情况,这些都需要多方沟通。类似的用户故事简化的情形有很多,但这时一定基于黑盒方式来做简化。而在简化的过程中,考虑如何实现如何合理调整工作量提高效率,这些都是找(用户)故事的过程,也是一个白盒的过程。

在实现上,除了强调快速交付或生命周期很短、业务模式高度可变的互联网、网游等项目,可以采用纯用例的模式,现阶段让(大型)企业IT项目全面接纳需求完全无文档化还是不现实的,更实际的解决办法是在文档化和敏捷需求分析之间找到一个平衡,一份需求用例加上用户故事,然后驱动开发这种方式,目前看来,这是现阶段更适合大型企业的敏捷需求实践模式。

(本文来自《程序员》杂志10年07期)