好代码、坏代码之四
好代码、坏代码之四
9.扩展无处不在
扩展性是衡量一个软件产品的重要尺度之一。通过合适的设计为软件系统赋予一定程度的扩展,是架构师着手设计的重要考虑因素,如图3-16所示。
扩展是个大课题,涉及软件系统的方方面面,依赖于粒度不同的架构格局。举例来说,数据库设计可以考虑在横向或纵向的扩展、在多层架构中实现可适配的数据层、为业务层实现注入逻辑设计、在UI层提供可配置的界面选择以及为物理架构提供横向扩展的部署设计。实现基于服务的系统,就意味着在服务层支持扩展良好的高层架构;而一个面向接口的设计,将是为扩展提供可能的选择之一;采用ASP.NET MVC构建的Web系统,将在很多方面被赋予扩展的标签,基于管线模型的设计将扩展深入到几乎所有的方面,例如ActionFilter、ViewEngine、Route、HtmlHelper、ModelBinder以及Controller,开发者可以轻易地替换所有原有支持元素,扩展出不同的“个性”功能;而MEF(ManagedExtensibility Framework)则实现了更灵活的扩展设计,基于MEF可以发现并使用扩展,甚至在应用程序之间重用扩展。
在语言层面,考量扩展性的指标遍布于.NET语言特性的各个细节:
· 基于类的继承、组合和多态。面向对象的基本特征就是扩展良好,而.NET的面向对象特性,在本书第1章 “OO大智慧”已经有了详细的讨论。
· 面向接口和抽象类,接口和抽象类是语言层的抽象载体,而面向抽象编程的设计原则在实际编码中的应用之一就是面向接口和抽象类,详见8.4节“面向抽象编程:接口和抽象类”。
· 基于委托和事件的回调。回调是一种扩展良好的实现机制,提供动态扩展性表现,使得框架能够以委托来调用用户代码:
private void btnLogin_Click(object sender, RoutedEventArgs e)
{
MessageBox.Show("Hello, Windows Phone.");
}
就像给框架提供了一个“钩子”来动态地将用户代码扩展到框架的逻辑,在单击按钮的时候,执行用户代码的流程逻辑,并将这个流程注入到框架行为中。在.NET中,可以通过委托实现线程的安全回调,而事件正是这种模式的最佳实践,详情参考9.7节“一脉相承:委托、匿名方法和Lambda表达式”。
· 应用扩展方法。扩展方法,本身就是为扩展而生的,详见13.2节“赏析C#3.0”。
· 以部分类延伸类的组织,在很多情况下,为了便于组织和物理上的方便,将一个类分布在多个独立的文件,是一种合适的处理方式;另一方面,对于越来越多的自动生成代码情况,部分类提供了“手动”扩展支持。例如,应用LINQ to SQL作为数据访问层框架时,通常可以通过Visual Studio自动生成实体类:
[global::System.Data.Linq.Mapping.TableAttribute(Name="dbo.Users")]
public partial class User : INotifyPropertyChanging, INotifyPropertyChanged
{
}
在这种情况下,就可以考虑通过部分类的方式,为实体类User增加新的成员、继承统一的基类:
public partial class User : EntityBase
{
public bool IsAdmin { get; set; }
}
· 通过反射注入。反射特性是.NET平台非常有吸引力的语法游戏,通过反射可以实现动态注入设计,在3.2节“依赖的哲学”中有详细的讨论。
· 基于DLR实现动态扩展。在.NET 4.0中,巨大的变革即是动态编程,为静态语言插上动态的翅膀,让动态扩展无处不在,详见14.3节“动态变革:dynamic”。
· 让扩展可配置。在ASP.NET整体架构中,将Web请求的处理设计为管道模型,模型中的重要元素包括HttpApplication、HttpModule和HttpHandler等,而对于这些管道中的过滤器(HttpModule)和处理器(HttpHandler)则通过配置实现可插拔的扩展性设计:
<httpModules>
<add name="TimeLogModule" type="Anytao.Devkit.Core.Web.Modules.TimeLogModule, Anytao.Devkit.Core" />
</httpModules>
例如,上述配置可以将TimeLogModule注入到HTTP管道,从而将每个请求的处理时间输出到日志。
public class TimeLogModule : IHttpModule
{
public void Dispose()
{
}
public void Init(HttpApplication context)
{
context.BeginRequest += (sender, e) =>
{
var sw = new Stopwatch();
HttpContext.Current.Items["StopWatch"] = sw;
sw.Start();
};
context.EndRequest += (sender, e) =>
{
var sw = (Stopwatch)HttpContext.Current.Items["StopWatch"];
sw.Stop();
TimeSpan ts = sw.Elapsed;
string result = string.Format("{0}ms", ts.TotalMilliseconds);
Logger.Log(result);
};
}
}
· 基于ConfigurationManager的配置扩展。一般来说,配置是为扩展而准备的,而扩展可通过配置注入。在.NET框架中提供非常优秀的配置支持,开发者完全可以通过这套完美的配置框架实现自定义的配置扩展。
· 硬编码总是不好的。任何时候都尽可能将变化的部分从代码中分离,以配置或者其他方式加载,为扩展提供机会。
扩展无处不在。软件设计师的职责,在于将这种无处不在深入到软件系统的各个环节,为各种可能提供基础与准备。
10.性能是一把尺子
性能,永远是任何软件产品衡量的标准,就像一把标准的千分尺,可以精度准确地为产品打上分数,在.NET中性能的指标体现在语言的各个方面,在本书6.4节“性能优化的多方探讨”中,对于性能的问题有详细讨论。
11.信赖的是测试,不是自己
质量的保证,一直是复杂的软件开发的软肋,为了保证软件产品的完美,测试是整个开发流程中最重要的部分。现代软件开发也衍生出很科学的测试方法、方式和制度,不管是黑盒的还是白盒的,只要逮住Bug,就是好测试。
与传统测试方式比较,测试驱动开发(Test Driven Development,TDD)已经被证明是非常靠谱和科学的开发方式。TDD至少在两个方面为软件开发注入活力:
· 保证质量。足够的覆盖率将能保证软件质量和稳定性,系统的修改和变化将第一时间反馈在测试代码上,结果驱动的方式将能最大限度地评估变化对原有系统造成的影响,从而保证业务代码的正确性。
· 驱动设计。另一方面,为了能够让业务代码具有可测试性,你应重新审视业务代码的设计,就像Bob大叔的名言:编写单元测试更像一种设计行为,文档行为而不是验证行为。编写单元测试缩短了反馈周期读数,最小读数基于功能验证。
因此,测试驱动是值得提倡和普及的,将由人的信任测试,转变为由代码的信任测试,信任的是测试,而不是开发者自己。
12.是进度还是质量,平衡是关键
开发者经常挂在嘴边的一句话是:给我足够的时间,我将实现得更好。然而,实际的情况往往是,开发的周期和开发的进度总是存在着冲突,进而带来进度和质量之间的妥协,而妥协的关键在于平衡。
作为开发者而言,需要评估设计和实现所花费的时间,然后根据评估的结果对进度做以平衡,很多时候,并没有一次就很完美的设计,只有当下适合的设计。平衡进度与质量的关键,是建立起行之有效的开发流程和进度计划,将资源、进度和质量有机地整合在可控制的框架管理中,并准备好三个要素之间的缓冲带,在适合的时候做好调整的准备。
本文节选自《你必须知道的.NET(第2版)》一书
图书详细信息:http://blog.csdn.net/broadview2006/article/details/6673353