.NET代码设计的重点在于重视程度,也就是开发人员需要集中思想,提前做好准备工作。如果不重视,很可能出现太多的public会让使用这些class的二次开发的人员无所适从的局面。
为什么要重视.NET代码设计?
在绝大多数项目中,尤其是在大型、资源缺少(这是软件项目的典型现象)的项目中,正规的架构可能只是解决系统级的事项,而特意把大部分的设计工作留待代码构建阶段去做。这可能会引发两个问题:
<!--[if !supportLists]-->1) <!--[endif]-->代码构建阶段的设计分散保存在开发人员的头脑中,得不到有效的验证
<!--[if !supportLists]-->2) <!--[endif]-->由于开发人员各有所长,也各有所短,往往在进行了部分或完成代码构建后,问题才能被明确。最终,不合理的设计导致大量的重构工作,延误项目工期。
设计工作千头万绪,然而软件设计的首要使命是“管理复杂度”;重点和难点是“管理变化”。虽然“管理变化”是“管理复杂度”的内容之一,但由于“管理变化”具有突出的重要性,因此单独进行介绍。在代码构建整个过程中,所有设计工作必须充分考虑这两个方面,从而实现高质量代码。
<!--[if !supportLists]-->1. <!--[endif]-->管理复杂度
复杂度来源:
<!--[if !supportLists]-->1) <!--[endif]-->用复杂的方法解决简单的问题
这个现象通常出现在过度设计上:首先是简单的问题复杂化,然后对复杂化的问题使用复杂的方法。
如在10个有序数字中检索某个数字。按照目前的CPU运算速度,直接采用顺序检索简单有效。如果盲目考虑CPU运算的因素,采用二分法检索,反而导致逻辑冗余。但是,如果对位数过万的数组进行检索,则应采用二分法或其它更有效的算法。
<!--[if !supportLists]-->2) <!--[endif]-->用简单但错误的方法解决复杂的问题
由于设计是一个由浅入深,不断尝试,不断失败,最终得到正确结果的过程,因此,在代码构建之初,当设计者未能正确考虑复杂问题的各个方面,将复杂问题简单化后,将会导致“用简单但错误的方法解决复杂的问题”。
对此,我说一下自己的一个切身体会,在SharePoint开发中,我们一个常见的问题是“内容数据库非关系型数据库”,虽然Moss提供了Lookup类型的字段来建立这种关系,但是存在两个问题,首先是不能跨站点,更不能查询多个列表的内容,也不能综合显示多个字段的内容。这些缺陷对于很多应用来说,似乎是致命的。为此,我曾经设计出同时支持跨网站、跨列表、多字段的自定义Field。但是设计中欠缺考虑,以致完成该Field的开发后,一个显著的缺陷让我头疼不已:将关联关系存储与字段值中,虽然在显示、编辑和存储上简化了编码工作,但是却未能解决关联关系本质上的问题,从而导致了一系列更为复杂的问题的出现,如反向关联查询,关联同步,以致于花费了大量的精力来维护这种关系。
在该字段新版本的设计中,我将关联关系单独存储在一个列表中,同时扩展基内容类型的New & Edit Form。嵌入维护不同列表间项与项关系的可视化展示与编辑部件(基于Silverlight实现)。顺便提一下,使用了多年的Flex,发现Silverlight除了在基础组件库上略逊色外,性能方面还是很不错的,尤其是继承自NET的垃圾回收机制。
但是无论是Flex和Silverlight,都要注意它在图形绘制方面的局限性,当需要绘制的组件和图表过多时,尽量只绘制ViewPort中的部分,否则性能问题将导致整个程序的崩溃。
总结一下,如果使用简单但错误的方法解决复杂问题,那么代价会在后面成几何级数回馈给你。
套用一句话:代码无小事!
<!--[if !supportLists]-->3) <!--[endif]-->用不恰当的复杂方法解决复杂问题
在旧有经验的指导下,某些复杂问题的表象会误导设计者,将之当作另外一个曾经发生过的复杂问题,而问题本身的复杂程度也令人望而却步,不愿做进一步的分析,直接套用已有的解决方法虽然省事,却有可能“用不恰当的复杂方法解决复杂问题”。
管理复杂度的方法:
<!--[if !supportLists]-->1) <!--[endif]-->任何人在同一时间只处理必不可少的复杂度
<!--[if !supportLists]-->a) <!--[endif]-->分类汇总
从问题的领域着手,而不是从底层实现细节入手去编写程序,在最抽象的层次上工作,也能减少人的脑力负担。
问题的领域在不同抽象程度上,可以划分为不同的级别:业务,子系统,功能模块,类、子程序。通常,从较高的领域分解下来,会有效的降低问题的复杂程度。如在子系统级别确定好每个功能模块接口,那么在进入功能模块领域后,你只需要关心少量的接口,而把更多的精力放在该功能的数据、逻辑和UI设计上。
C#等面向对象语言可以轻易实现这种抽象,通过定义基类或抽象类(如C#中的abstract 关键字)。
<!--[if !supportLists]-->b) <!--[endif]-->一致的抽象
定义基类或抽象类时,必须保持同级别抽象的一致性,摒除不必要的细节。
如ASP.NET编程中的Page和Control就属于同一级别的抽象。 在Page类的实现中,它无需知道具体是什么类型的Control,是DataGrid还是TextBox,它根本不关心,它只关心Control类中的Render()可以提供什么样的Html。而这个Control是红的还是绿的,是方的还是圆的,根本不需要关心。如果编写Page类时调用了Control的一个子类,那抽象就不一致了,Page将自动降级,Page对于Control基类毫无意义。当然,这样的错误谁也不会去犯,仅仅用来说明这个道理而已。
在SharePoint中,通常我们也会实现一些基础类以提高团队的开发效率,如BaseAjaxWebPart、BaseAjaxField等等。这些Base类中一定不要使用某些实现了特定细节的类或组件。
<!--[if !supportLists]-->2) <!--[endif]-->不要让偶然性的复杂度无谓地快速增长
偶然性的复杂度,即潜在的变化,包括:已知的未知、未知的未知。
对于未知的未知这种情况,我们可以不过多考虑,只需要根据经验在任务工期上预留一定资源:时间、人力等。
对于已知的不确定的变化,在下面《管理变化》中简要介绍。
<!--[if !supportLists]-->2. <!--[endif]-->管理变化
没有变化的程序是不可能存在的。但不能让变化像病毒一样蔓延到整个程序。
管理变化的手段比较多,如24种设计模式,大部分都是解决这个问题的。但万变不离其宗,主要还是以下两个方面:
<!--[if !supportLists]-->1) <!--[endif]-->封装
通过有效的继承和一致的抽象,可以确保发生变化时,最小的代码改动量。
<!--[if !supportLists]-->2) <!--[endif]-->模块化
通过对系统模块化及划分界面,确定接口,可以有效地将变化控制在模块内部。
使用封装和模块化来管理变化时,必须注意隐藏信息:该使用private之处,绝不要使用protected。该使用protected之处,绝不要使用public。Internal也不要多用,它的作用域是程序集内部,在编写代码时,往往跟public一样混淆视听,降低了代码的可读性。
太多的public会让使用这些class的二次开发的人员无所适从。充斥着非必要public成员的class,不符合管理复杂度的原则。有可能是设计原因,也有可能是未按照设计严格编码。
.NET代码设计的要点就介绍到这里。
原文标题:谈谈代码设计
链接:http://www.cnblogs.com/ghner/archive/2009/09/05/1560998.html
【编辑推荐】