Effective C# Item32:尽可能实现小尺寸、高内聚的程序集
我们在划分程序集时,经常会犯的一个错误:在一个程序集中放入了太多的东西,导致程序集很难被重用。
一个好的程序集应该是“高内聚”的,所谓内聚,是指将程序集封装为一个有意义、有职责的单位,它一般可以用一句话简单的话来描述,例如.NET框架中的System.Collection程序集就为存储一组相关的对象提供了数据结构。我们不能这么说:MyApplication程序集提供了任何我们所需的功能,这样的描述太过散漫了。
在如何划分程序集方面,我们既不能将所有东西都放入一个程序集,也不能针对每个公开类都创建一个程序集,我们需要寻找一个中间地带。如果创建太多的程序集,我们将失去封装带来的好处;如果不能将相关的公有类放入一个程序集中,那么我们将失去内部类型带来的好处。我们的目标是为组件中发布的功能创建最佳尺寸的程序集,这个目标很容易使用高内聚的组件来达到,每个组件都应该有一个职责。
我们使用类来封装算法和数据存储,只有公有接口才是对外开放的,而程序集是为一组相关的类提供一个二进制包,只有公有类和受保护的类才是对外开放的。
使用小尺寸的程序集也有利于部署升级,将独立的功能抽取出来,放入到一个独立的程序集中,在今后升级维护中,会很方便的。
小尺寸的程序集还可以让我们降低应用程序的启动成本,程序集越大,CPU在加载程序集时将IL代码转换成机器指令的工作就越多,只有一些在启动时被调用的方法才会执行JIT编译,但是整个程序集都要被加载,CLR也要为程序集中的每一个方法创建一个存根。
但是从另一方面说,如果程序集的尺寸太小,就会产生过多数量的程序集,这也会有一些不好的影响。首先,程序流程跨月程序集边界时,会有相关的性能代价,CLR加载器在加载多个程序集时并将IL代码编译成机器指令所做的工作也就会多一些(这一点比较牵强,CLR加载程序集并将IL翻译成机器指令的工作,应该和程序集的数目没有关系,这应该和应用程序本身的规模有关系);其次,在跨越程序集边界时,还需要执行一些额外的安全检查,来自同一个程序集中的代码具有相同的信任级别,当代码流程跨越程序集边界时,CLR会执行安全检查,程序流程跨越程序集边界的次数越少,代码的效率就越高。
如何判断一个程序集中应该放入多少类呢?或者说如何判断哪些代码应该放入到哪个程序集中呢?这没有标准的答案,一般情况下,建议:首先看所有的公有类,将所有的公有类以及共用的基类放到一些程序集中;然后把“为公有类提供功能的辅助类”也放入同样的程序集中;再把相关的公有接口放入到它们自己的程序集中;最后,再来处理遍布应用程序中“水平”位置的类。被广泛使用的工具程序集中的类(或者说应用程序的工具程序库)应该属于这样的类型。
对于程序集的尺寸来说,“小”是一个相对含义,程序集的设计目标是包含一组相关联的类和接口,在绝对尺寸上的差别与功能上的差别相关。我们会为应用程序创建两种类型的程序集:一种是小而聚合,具有某项特定功能;另一种是大而宽,包含公用功能。这两种类型的程序集都应该“合理的小”。