jer*_*jvl 19 .net dependency-injection ninject
我正在计划一些工作,将依赖注入引入当前的大型单片库,试图使库更容易进行单元测试,更容易理解,并可能更灵活地作为奖励.
我决定使用NInject,我真的很喜欢Nate的"做一件事,做得好"(转述)的座右铭,而且它似乎在DI的背景下特别好.
我现在想知道的是,我是否应该将当前单个大型装配拆分为具有不相交特征集的多个较小装配.这些较小的程序集中的一些将具有相互依赖性,但远非所有这些程序集,因为代码的体系结构已经非常松散地耦合.
请注意,这些功能集不是微不足道的,也不是很小......它包含客户端/服务器通信,序列化,自定义集合类型,文件IO抽象,常用例程库,线程库,标准日志记录等.
我看到前一个问题:什么是更好的,许多小型装配,或一个大装配?有点解决这个问题,但是看起来更精细的粒度,这让我想知道那里的答案是否仍适用于这种情况?
此外,在接近这个主题的各种问题中,一个常见的答案是,"太多"集会导致了未指明的"痛苦"和"问题".我真的想要具体了解这种方法可能存在的缺点.
我同意在仅需要1个之前添加8个组件是"有点痛苦",但是必须为每个应用程序包含一个大的单片库也不是完全理想的...加上8个组件只是你做的事情曾经,所以我对这个论点很少有同情心(即使我最初可能会和其他人一起抱怨).
附录:
到目前为止,我已经看到没有针对小型装配的任何理由,所以我想我现在将继续进行,好像这是一个非问题.如果有人能够用可验证的事实来考虑好的可靠理由来支持它们,我仍然会非常有兴趣了解它们.(我会尽快增加赏金以提高知名度)
编辑:将性能分析和结果移到单独的答案中(见下文).
jer*_*jvl 30
由于性能分析比预期的要长一些,我已经把它放在它自己的单独答案中.我将接受彼得的官方答案,尽管它缺乏测量,因为它最激励我自己进行测量,并且因为它给了我最值得测量的灵感.
分析:
到目前为止提到的具体缺点似乎都集中在一种另一种的性能上,但缺少实际的定量数据,我已经做了以下测量:
这种分析完全忽略了某些人在答案中提到的"设计质量",因为在这种权衡中我并不认为质量是一个变量.我假设开发人员首先要让他们的实现以获得最佳设计的愿望为指导.这里的权衡是,为了(一些衡量标准)性能,是否值得将功能聚合到更大的组件中而不是设计严格要求.
应用程序结构:
我构建的应用程序有点抽象,因为我需要大量的解决方案和项目来测试,所以我写了一些代码来为我生成它们.
该应用程序包含1000个类,分为200组,每组5个相互继承.类名为Axxx,Bxxx,Cxxx,Dxxx和Exxx.类A是完全抽象的,BD是部分抽象的,覆盖A的每个方法之一,E是具体的.实现这些方法,以便在E的实例上调用一个方法将对层次结构链执行多个调用.所有方法体都很简单,理论上它们应该全部内联.
这些类沿着2个维度以8种不同的配置分布在程序集中:
测量结果并非完全准确; 有些是通过秒表完成的,并且误差较大.测量结果如下:
结果:
在VS2008中打开解决方案
----- in the IDE ------ ----- from prompt -----
Cut Asm# Open Compile Start new() Execute Start new() Execute
Across 10 ~1s ~2-3s - 0.150 17.022 - 0.139 13.909
20 ~1s ~6s - 0.152 17.753 - 0.132 13.997
50 ~3s 15s ~0.3s 0.153 17.119 0.2s 0.131 14.481
100 ~6s 37s ~0.5s 0.150 18.041 0.3s 0.132 14.478
Along 10 ~1s ~2-3s - 0.155 17.967 - 0.067 13.297
20 ~1s ~4s - 0.145 17.318 - 0.065 13.268
50 ~3s 12s ~0.2s 0.146 17.888 0.2s 0.067 13.391
100 ~6s 29s ~0.5s 0.149 17.990 0.3s 0.067 13.415
Run Code Online (Sandbox Code Playgroud)
观察:
因此,从这一切看来,更多装配的负担主要由开发人员承担,然后主要以编译时间的形式承担.正如我已经说过的那样,这些项目非常简单,每次编译都需要不到一秒钟的时间来编译,导致每个程序集的编译开销占主导地位.我可以想象,跨越大量程序集的亚秒级程序集编译强烈表明这些程序集已进一步拆分而不是合理的.此外,当使用预编译的程序集时,反分裂(编译时间)的主要开发人员参数也将消失.
在这些测量中,为了运行时性能,我可以看到很少有证据表明不能拆分成较小的组件.唯一需要注意的(在某种程度上)是尽可能避免切断继承; 我认为大多数理智的设计无论如何都会限制它,因为继承通常只发生在功能区域内,通常最终会在一个组件内.
Pet*_*yer 14
我将给你一个真实世界的例子,其中许多(非常)小组件的使用产生了.Net DLL Hell.
在工作中,我们有一个长大的本土框架(.Net 1.1).除了通常的框架类型管道代码(包括日志记录,工作流,排队等)之外,还有各种封装的数据库访问实体,类型化数据集和一些其他业务逻辑代码.我不是为了这个框架的初始开发和后续维护,但确实继承了它的用法.正如我所提到的,整个框架导致了许多小DLL.而且,当我说很多时,我们说的是100以上 - 而不是你所提到的可管理的8.更复杂的是,这些程序集都经过严格签名,版本化并出现在GAC中.
因此,快进几年和以后的一些维护周期,发生的事情是DLL和它们支持的应用程序的相互依赖性造成了严重破坏.在每台生产机器上都是machine.config文件中的一个巨大的程序集重定向部分,它确保无论请求何种程序集,都可以通过Fusion加载"正确"的程序集.这是因为重建每个依赖于已修改或升级的依赖框架和应用程序程序集所遇到的困难.采取了很大的痛苦(通常)以确保在修改组件时不会对组件进行任何重大更改.重建了程序集,并在machine.config中创建了一个新的或更新的条目.
这是我会停下来听一声巨大的集体呻吟声和喘息声!
这种特殊情况是不做的事情的典型代表.确实在这种情况下,你会陷入完全无法维持的境地.我记得当我第一次使用它时,花了两天的时间让我的机器设置用于开发这个框架 - 解决我的GAC和运行时环境的GAC,machine.config程序集重定向之间的差异,编译时的版本冲突由于不正确的引用,或者更可能是由于直接引用组件A和组件B引起的版本冲突,但是组件B引用了组件A,但是与我的应用程序的直接引用不同.你明白了.
这个特定场景的真正问题是程序集内容过于细化.并且,这最终导致了相互依赖的纠缠网络.我的想法是,最初的架构师认为这将创建一个高度可维护的代码系统 - 只需要重建对系统组件的非常小的更改.事实上,情况正好相反.此外,对于已经发布的其他一些答案,当你达到这个数量的组件时,装载大量的组件会产生性能损失 - 绝对是在解决期间,我猜,虽然我没有经验证据,在某些边缘情况下,运行时可能会受到影响,特别是在反射可能起作用的地方 - 在这一点上可能是错误的.
你认为我会被嘲笑,但我相信程序集有逻辑物理分离 - 当我在这里说"程序集"时,我假设每个DLL有一个程序集.这一切归结为互相依赖.如果我有一个取决于装配件B的装配件A,我总是问自己是否需要在装配件A中引用装配件B.或者,这种分离是否有益处.查看如何引用程序集通常也是一个很好的指标.如果您要在程序集A,B,C,D和E中划分大型库.如果您在90%的时间内引用了程序集A,那么您总是必须引用程序集B和C,因为A依赖于它们,那么组合A,B和C可能更好的结合,除非有一个非常有说服力的论据允许它们保持分离.Enterprise Library就是这样的经典示例,您几乎总是需要引用3个程序集才能使用库的单个方面 - 但在Enterprise Library的情况下,能够构建核心功能和代码之上重用是它的架构的原因.
看建筑是另一个很好的指导方针.如果你有一个很好的干净堆叠架构,你的程序集依赖关系是堆栈的形式,比如说"垂直",而不是"web",当你在每个方向都有依赖关系时会开始形成,然后分离程序集功能边界是有道理的.否则,请将事物放入一个或寻找重新设计.
无论哪种方式,祝你好运!