技术开发 频道

如何避免在.NET代码中出现不恰当依赖?

        【IT168 技术】在如何至始至终保持代码的可维护性方面我给.NET开发者团队的最好建议是:将应用程序中的每个命名空间都当作组件看待,同时确保组件之间不存在依赖环。 通过遵守这条简单的原则,大型应用系统的结构就不会陷入大块意大利面式代码的混沌之中——而这种意大利面式代码在专业企业应用开发中往往被视为正常而非异常的现象。

  命名空间即组件

  从十多年前.NET技术出现以来,Visual Studio开发工具一直隐式地将VS项目作为组件(也即程序集)。这是不恰当的,因为组件应该是结构代码的逻辑部件,而程序集应该是包代码的物理部件。这导致了另一个被视为正常而非异常的现象:有些企业应用程序竟由几百个VS项目组成。

  我为什么鼓励使用命名空间这个轻量级概念来定义组件边界呢?其好处如下:

  • 更轻量的组织:多用命名空间而少用程序集意味着所需的VS解决方案个数和VS项目个数变少了。

  • 减少了编译时间:每个VS项目都会在编译时产生额外的时间开销。具体点说,项目很多的话会导致编译需要花几分钟时间,但如果大幅减少VS项目的数量,则编译仅需花几秒钟时间。

  • 更轻量的部署:部署几十个程序集要比部署上千个简单多了。

  • 更少的应用程序启动时间:CLR加载每个程序集时都需要付出一小些额外的性能开销。加载几十或上百个程序集的话,总共的开销就相当明显了,达到了以秒记的级别。

  • 方便了组件的层次组织:命名空间能够表达出层次结构,程序集则不能。

  • 方便了组件的细颗粒度化:存在1000个命名空间不是什么问题,存在1000个程序集就是个问题。选择构建一些非常细粒度的组件不应该因为需要专门创建相对应的VS项目而令人扫兴。

  依赖环危害不小

  组件间的依赖环会导致出现人们常说的意大利面式代码(spaghetti code)或者纠缠式代码(tangled code)。假如组件A依赖于B,B依赖于C,而C依赖于A,则A不能够离开B和C单独进行开发和测试。A、B和C形成了一个不可见环,一种超级组件。这个超级组件的开销要比A、B和C三者的开销之和还大,这就是所谓的规模不经济现象(diseconomy of scale phenomenon)。

  通常,这会导致开发最小单元代码的开销呈指数级增长。这意味着,如果不能将1000行代码划分成相互独立的两份500行的代码的话,开发和维护1000行代码的开销要比500行多出三或四倍。如果是碰到意大利面式或者纠缠式代码的话,那就可能无法维护了。为了使组织架构更加合理,人们应该确保组件之间不存在依赖环,同时确保每个组件的大小是合适的(500至1000行之间)。

  对战设计侵蚀(design erosion)

  五月份发布的NDepend版本4引入了应对应用程序环的新特性,在这里我想讨论下其所具有实践意义。

  现在我们能够按照LINQ查询要求来实现编码规范(我们称之为CQLinq),我们能够利用LINQ的巨大灵活性构建出特定规范。其中一个我参与构建的规范是能够报告命名空间依赖环的代码规范。例如,如果我们来分析.NET Framework v4.5,观察程序集System.Core.dll内部,就会发现其存在两个命名空间依赖环,这两个环都由7个命名空间组成。代码规范特性可以索引环中的某个命名空间(随机选取)并展现这个环。用鼠标左键点击下图cycle字段可以查看依赖环所包括的命名空间:

  通过鼠标右键点击命名空间列表或者依赖环本身,就会出现将他们导出为依赖图(dependency graph)或者依赖矩阵(dependency matrix)的菜单。下面的截图显示了7个相互纠缠的命名空间。但这不是循环依赖的典型图示,典型的情况是:假定两个命名空间A和B,通过B可以访问到A,并且反之亦然。显然,这样纠缠起来的代码是不容易维护的。

0
相关文章