依赖注入:如何出售


95

众所周知,我是依赖注入(DI)和自动化测试的忠实拥护者。我可以整天谈论它。

背景

最近,我们的团队刚刚获得了一个从头开始构建的大型项目。它是具有复杂业务需求的战略应用程序。当然,我希望它美观大方,对我而言意味着:可维护且可测试。所以我想使用DI。

抵抗性

问题出在我们的团队中,DI是禁忌。它被提出了几次,但是众神不赞成。但这并没有阻止我。

我的举动

这听起来可能很奇怪,但是第三方库通常不经我们的架构师团队批准(请考虑:“您不要谈论UnityNinjectNHibernateMoqNUnit,以免割伤您的手指”)。因此,我没有使用已建立的DI容器,而是编写了一个非常简单的容器。基本上,它在启动时连接了所有依赖项,注入了任何依赖项(构造函数/属性),并在Web请求结束时处置了所有可抛弃对象。它非常轻巧,可以满足我们的需求。然后我请他们进行审查。

响应

好吧,简而言之。我遭到了沉重的抵抗。主要论点是:“我们不需要在已经很复杂的项目中增加这种复杂性”。另外,“这不像我们将插入组件的不同实现”。而且,“我们希望保持简单,如果可能的话,只需将所有内容装入一个程序集。DI是一种无用的复杂性,没有任何好处”。

最后,我的问题

您将如何处理我的情况?我不能很好地表达自己的想法,我想知道人们将如何表达自己的观点。

当然,我假设像我一样,您更喜欢使用DI。如果您不同意,请说出原因,以便我能看到硬币的另一面。看到不同意的人的观点真的很有趣。

更新资料

谢谢大家的回答。它确实使事情成为现实。拥有另一双眼睛可以给您反馈非常好,十五岁的确很棒!这确实是一个很好的答案,可以帮助我从不同角度看待这个问题,但是我只能选择一个答案,所以我只选择投票率最高的一个。感谢大家花时间回答。

我认为现在可能不是实施DI的最佳时机,我们还没有做好准备。相反,我将致力于使设计可测试,并尝试提出自动化的单元测试。我知道编写测试是额外的开销,并且如果曾经确定额外的开销不值得,我个人仍然认为这是一个成功的情况,因为设计仍然可以测试。而且,如果将来进行测试或DI是一种选择,则设计可以轻松应对。


64
听起来您需要搬到另一家公司...
Sardathrion 2012年

24
使用DI(至少在Java,C#,...中)几乎是使用单元测试的自然结果。贵公司是否使用单元测试?
sebastiangeiger 2012年

27
或根本不专注于DI。DI与“可维护和可测试”不同。当对项目有不同意见时,您必须撰写,有时接受您不确定。例如,我看到控件,DI和多层框架的倒置加剧了灾难,使复杂的事情变得很简单(我不能在你的情况下争论)。
DenysSéguret2012年

34
“ ...我可以整天谈论它。” 听起来您需要停止痴迷于某些经常被滥用的事物,这不仅仅是因为对今天的某些白皮书的教条主义。

21
从它的声音来看,您的同事有反对使用DI容器的客观论点:“我们不需要在已经很复杂的项目中增加这种复杂性” –他们也许是对的吗?您实际上是否从增加的复杂性中受益?如果是这样,那应该是可以争论的。他们声称“ DI是不必要的复杂性,没有收益。” 如果您可以显示出客观收益,那么您应该很轻松地赢得这一收益,不是吗?其他注释中经常提到测试。但是测试并没有从根本上需要DI,除非你需要嘲笑(这不是一个给定的)。
康拉德·鲁道夫

Answers:


62

采取一些相反的论点:

“我们希望保持简单,如果可能的话,将所有东西都装进一个组件中。DI是一种无用的复杂性,没有任何好处。”

“这不像我们将插入组件的不同实现”。

您想要的是使系统可测试。为了易于测试,您需要模拟项目的各个层(数据库,通信等),在这种情况下,您插入组件的不同实现。

向DI出售可以带来的测试好处。如果项目很复杂,那么您将需要良好,可靠的单元测试。

另一个好处是,在对接口进行编码时,如果您提出了一个更好的组件实现方式(更快,更少的内存消耗,无论如何),那么使用DI可以更轻松地将旧的实现方式换成组件。新。

我在这里要说的是,您需要解决DI带来的好处,而不是为了DI而争辩DI。通过使人们同意以下声明:

“我们需要X,Y和Z”

然后,您可以解决问题。您需要确保DI是此语句的答案。这样,您的同事将拥有解决方案,而不是感到自己被强加给他们。


+1。简明扼要。不幸的是,从梅尔的同事们提出的论点来看,他将很难说服他们即使尝试也值得-斯派克在回答中说,更多的是人员问题而不是技术问题。
PatrykĆwiek'5

1
@ Trustme-I'maDoctor-哦,我同意这是一个人的问题,但是使用这种方法,您展示的是好处,而不是为了自己而为DI辩护。
克里斯·弗雷德(ChrisF)

没错,我希望他能好运,如果有适当的测试能力,他作为开发人员的生活将更加轻松。:)
PatrykĆwiek2012年

4
+1我认为您的最后一段应该是第一段。这是问题的关键。如果有人提出要进行单元测试,并且建议将整个系统转换为DI模型,我也对他说不。
Andrew T Finnell,2012年

+1确实很有帮助。我不会说DI对于单元测试是绝对必须的,但是它确实使事情变得容易得多。
梅尔(Mel)

56

从您所写的内容来看,DI本身不是禁忌,而是使用DI容器,这是另一回事。我猜想,当您编写独立的组件,使其他组件(例如,构造函数)传递给您时,您的团队不会遇到任何问题。只是不要将其称为“ DI”。

您可能会认为,对于此类组件,不要使用DI容器很繁琐,而且您是对的,但请给您的团队一些时间自己找出答案。


10
+1解决政治/教条僵局的务实方法
k3b 2012年

32
值得考虑的是仍然要手动连接所有对象,并仅在开始变得毛茸茸时才引入DI容器。如果这样做,他们不会将DI容器视为增加了复杂性,而是将其视为增加了简单性。
菲尔(Phil)

2
唯一的问题是将松散耦合的依赖项传递给依赖项的模式 DI。IoC或使用“容器”解决松散耦合的依赖关系是自动 DI。
KeithS 2012年

51

如您所问,我将带领您的团队对抗DI。

反对依赖注入的案例

有一个我称之为“复杂性守恒”的规则,类似于物理学中称为“能量守恒”的规则。它指出,没有多少工程可以降低针对问题的最佳解决方案的总复杂度,而它所能做的就是改变这种复杂度。苹果的产品并不比微软的产品复杂,它们只是将复杂性从用户空间转移到工程空间。(尽管这是一个有缺陷的类比。虽然您无法创造能量,但工程师却习惯于动every动动复杂性。实际上,许多程序员似乎喜欢增加复杂性并使用魔术。,从定义上讲,这是程序员无法完全理解的复杂性。可以减少这种过多的复杂性。)通常,降低复杂性看起来只是将复杂性转移到其他地方,通常是转移到库中。

同样,DI不会降低将客户端对象与服务器对象链接起来的复杂性。它的作用是将其从Java中移出并移入XML。这样做的好处是,它将所有内容集中到一个(或几个)XML文件中,在这里可以轻松查看正在发生的一切,并且可以在不重新编译代码的情况下进行更改。另一方面,这很糟糕,因为XML文件不是Java,因此不可用于Java工具。您不能在调试器中调试它们,也不能使用静态分析来跟踪它们的调用层次结构,依此类推。尤其是,DI框架中的错误变得极其难以检测,识别和修复。

因此,在使用DI时要证明程序正确是非常困难的。许多人认为使用DI的程序更容易测试,但是程序测试永远无法证明没有bug。(请注意,链接的纸张已有40年历史,因此有一些奥术参考,并引用了一些过时的做法,但许多基本声明仍然成立。特别是,P <= p ^ n,其中P是系统没有错误的概率,p是系统组件没有错误的概率,n是组件的数量。当然,这个方程式被简化了,但它指出了一个重要的事实。) Facebook IPO期间的纳斯达克(NASDAQ)崩溃是一个最近的例子,其中他们声称他们花了1000个小时来测试代码,但仍未发现导致崩溃的错误。1,000小时是一年半的人年,所以这并不像他们在测试中有所懈怠。

没错,几乎没有人会承担(更不用说完成)正式证明任何代码是正确的任务,但是即使是较弱的证明尝试也无法击败大多数测试系统来发现错误。大力进行证明的尝试(例如L4.verified)总是会发现大量测试未发现的错误,除非测试人员已经知道该错误的确切性质,否则可能永远不会发现。 任何使代码难以通过检查验证的事情都可以视为减少了检测到错误的机会,即使它增加了测试能力。

此外,测试不需要DI。我很少将它用于此目的,因为我更喜欢根据系统中的实际软件而不是某些替代测试版本来测试系统。我在测试中所做的所有更改都(或可能)存储在属性文件中,这些文件将程序定向到测试环境中的资源而不是生产环境中。我宁愿花时间用生产数据库的副本来建立测试数据库服务器,也不愿花时间对模拟数据库进行编码,因为这样我就可以跟踪生产数据的问题(字符编码问题只是一个例子) ),系统集成错误以及其余代码中的错误。

依赖倒置也不需要依赖注入。您可以轻松地使用静态工厂方法来实现依赖倒置。实际上,这就是1990年代的做法。

实际上,尽管我已经使用DI已有十年了,但它提供的唯一好处是,我发现说服力在于能够配置第三方库以使用其他第三方库(例如,将Spring设置为使用Hibernate,并且都使用Log4J )以及通过代理启用IoC。如果您没有使用第三方库,也没有使用IoC,则没有太多意义,而且确实增加了不必要的复杂性。


1
我不同意,可以降低复杂性。我知道,因为我已经做到了。当然,某些问题/要求会增加复杂性,但除此之外,大多数问题/要求都可以轻松消除。
瑞奇·克拉克森

10
@Ricky,这是一个微妙的区别。就像我说的那样,工程师可以增加复杂性,并且可以消除复杂性,因此您可能降低了实现的复杂性,但是根据定义,您不能降低针对问题的最佳解决方案的复杂性。大多数情况下,降低复杂性似乎只是将其转移到其他地方(通常转移到库中)。在任何情况下,我要说的第二点都是DI不会降低复杂性。
旧版专业版

2
@Lee,在代码中配置DI甚至没有多大用处。DI的最广泛接受的好处之一就是无需重新编译代码即可更改服务实现的能力,如果在代码中配置DI,那么您将失去它。
老职业

7
@OldPro-在代码中进行配置具有类型安全的优点,并且大多数依赖项是静态的,并且无法从外部定义中受益。

3
@Lee如果依赖项是静态的,那么为什么不首先通过硬编码而不通过DI绕行呢?
康拉德·鲁道夫2012年

25

遗憾的是,鉴于您的同事在您的问题中所说的话,您遇到的问题是政治性质的,而不是技术性质的。您应该牢记两个原则:

  • “这总是一个人的问题”
  • 变化是可怕的”

当然,您可以根据可靠的技术理由和好处提出很多反对意见,但这会使您和公司其他部门陷入困境。他们已经有了不想要的立场(因为他们对现在的工作方式很满意)。因此,如果您坚持不懈,甚至可能会损害您与同事的关系。相信我,因为这发生在我身上,并最终使我在几年前被解雇了(我年轻又幼稚);这是您不想陷入困境的情况。

关键是,您需要获得一定程度的信任,然后人们才能改变他们目前的工作方式。除非您处于可以信任的位置,或者可以决定这些事情,例如担任架构师或技术主管,否则您几乎无济于事。要使公司的文化得到扭转,需要说服力和精力(如采取小的改进步骤)需要大量时间才能赢得信任。我们所说的时间跨度为数月或数年。

如果您发现当前的公司文化不适合您,那么至少您知道在下一次面试中还有另一件事要问。;-)


23

不要使用DI。算了吧。

创建GoF 工厂模式的实现,该模式 “创建”或“查找”您的特定依赖项并将它们传递给您的对象,然后将其保留,但是不要将其称为DI,也不要创建复杂的通用框架。保持简单和相关。确保相关性和您的类是可以切换到DI的;但是请保留工厂的主人资格,并限制其范围。

随着时间的推移,您可以希望它会增长,甚至有一天可以过渡到DI。让线索按自己的时间得出结论,而不要推动。即使您从未做到这一点,至少您的代码也具有DI的一些优势,例如,可单元测试的代码。


3
+1与项目一起成长,并“不要称其为DI”
凯文·麦考密克

5
我同意,但最后是DI。只是不使用DI容器;它把负担转移给了呼叫者,而不是将负担集中在一处。但是,将其称为“外部依赖关系”而不是DI是明智的。
Juan Mendes 2012年

5
我经常想到,要真正掌握象DI这样的象牙塔概念,您必须经历与传福音者相同的过程,才意识到首先要解决的问题。作者经常忘记这一点。坚持使用低级模式语言,结果可能会(也可能不会)出现对容器的需求。
汤姆W

@JuanMendes从他们的回应中,我认为外部化依赖关系是他们所反对的,因为他们也不喜欢第三方库,并且希望所有内容都在一个程序集中(整体式)。如果是这样,那么称其为“ Externalizing Dependencies”(外部依赖关系)并不比从其优势点调用DI更好。
乔纳森·诺伊菲尔德

22

我见过将DI发挥到极致的项目,以进行单元测试和模拟对象。如此之多以至于DI配置本身就成为了编程语言,而使系统运行所需的配置也与实际代码一样多。

由于缺少适当的无法测试的内部API,系统现在是否正在遭受苦难?您是否希望将系统切换到DI模型可以更好地进行单元测试?您不需要容器即可对代码进行单元测试。使用DI模型可以使您更轻松地对Mock对象进行单元测试,但这不是必需的。

您无需一次切换整个系统即可与DI兼容。您也不应该随意编写单元测试。在功能和错误工单中遇到代码时,请对其进行重构。然后确保您触摸过的代码易于测试。

认为CodeRot是一种疾病。您不想开始随意破解某些东西。您有一个病人,看到一个疼痛的部位,因此您开始修复该部位及其周围的东西。一旦该区域干净,您就可以继续下一个。迟早您将接触到大部分代码,并且不需要这种“大规模”的体系结构更改。

我不喜欢DI和Mock测试是互斥的。在看到一个项目进行了以后,由于缺少更好的词而疯狂地使用DI,我也很疲倦,没有看到好处。

有一些使用DI的地方很有意义:

  • 数据访问层,用于交换故事数据的策略
  • 认证和授权层。
  • 用户界面的主题
  • 服务
  • 您自己的系统或第三方可以使用的插件扩展点。

要求每个开发人员知道DI配置文件在哪里(我知道容器可以通过代码实例化,但是为什么要这样做。)当他们想要执行RegEx时令人沮丧。哦,我看到有IRegExCompiler,DefaultRegExCompiler和SuperAwesomeRegExCompiler。但是,我无法在代码中的任何位置进行分析,以查看使用Default的位置和使用SuperAwesome的位置。

如果有人展示一些更好的东西,然后尝试用他们的话说服我,我自己的性格就会做出更好的反应。对于某人,要花些时间和精力来改善系统,有话要说。我发现没有很多开发人员足够关心产品的真正改进。如果您可以对他们进行系统上的真正更改并向他们展示如何使它变得更好,更轻松,那么这比任何在线研究都有价值。

总而言之,将更改引入系统的最佳方法是使每次传递的代码逐渐变得更好。迟早您将能够将系统转换为更好的东西。


“ DI和Mock测试是互斥的”这是错误的,它们也不是互斥的。他们在一起工作得很好。您还列出了DI很好的地方,服务层,dao和用户界面-几乎到处都是吗?
NimChimpsky

@Andrew有效积分。当然,我不希望它走得太远。我最希望对业务类别进行自动化测试,因为我们必须实施非常复杂的业务规则。而且我确实意识到我仍然可以在没有容器的情况下测试它们,但是就个人而言,使用容器才有意义。我实际上做了一些样本来说明这个概念。但它甚至没有被看过。我猜我的错误是我没有创建一个测试来显示测试/模拟的简单程度。
梅尔(Mel)

如此多的协议!我真的很讨厌DI配置文件及其对跟踪程序流的影响。整个过程变成了“远距离的怪异动作”,各部分之间没有明显的联系。当在调试器中读取代码比在源文件中读取代码变得容易时,这确实是错误的。
Zan Lynx 2012年

19

DI不需要控制容器或模拟框架的侵入式转换。您可以通过简单的设计解耦代码并允许DI。您可以通过内置的Visual Studio功能进行单元测试。

公平地说,您的同事有一点。严重使用这些库(由于它们不熟悉它们,因此会有学习上的麻烦)会引起问题。最后,代码很重要。不要搞乱产品进行测试。

请记住,在这些情况下必须折衷。


2
我同意DI不需要IoC容器。尽管我不会将其称为侵入性的(至少是此实现),因为代码被设计为与容器无关的(主要是构造函数注入),并且所有DI东西都发生在一个地方。因此,即使没有DI容器,它仍然可以正常工作-我放入了无参数的构造函数,该构造函数将具体的实现“注入”到具有依赖项的其他构造函数中。这样,我仍然可以轻松地模拟它。是的,我同意,DI实际上是通过设计实现的。
梅尔(Mel)

+1妥协。如果没有人愿意妥协,每个人都会遭受痛苦。
Tjaart 2012年

14

我认为您不能再出售DI了,因为这是一个教条式的决定(或者@Spoike更客气地说这是政治性的)。

在这种情况下,不再可能与诸如SOLID设计原则之类的广泛接受的最佳实践争论。

具有比您更大的政治权力的人(不正确)决定不使用DI。

另一方面,您通过实现自己的容器来反对此决定,因为禁止使用已建立的容器。您尝试过的这种既成事实政策可能会使情况变得更糟。

分析

在我看来,没有测试驱动开发(TDD)和单元测试的DI并没有超过最初的额外成本的显着优势。

这个问题真的有关吗

  • 我们不要DI吗?

还是更多关于

  • tdd / unittesting是浪费资源吗?

[更新]

如果您从项目管理视图中经典错误中看到了您的项目,那么您会看到

#12: Politics placed over substance.
#21: Inadequate design. 
#22: Shortchanged quality assurance.
#27: Code-like-hell programming.

而其他人看到

#3: Uncontrolled problem employees. 
#30: Developer gold-plating. 
#32: Research-oriented development. 
#34: Overestimated savings from new tools or methods. 

我们目前不在团队中进行任何单元测试。我怀疑您的最后声明是正确的。我已经提出了几次单元测试,但是没有人对此表现出真正的兴趣,并且总是有我们不能采用它的原因。
梅尔(Mel)

10

如果您发现自己必须向团队“出售”一项原则,那么您可能已经走错了几英里。

没有人愿意做额外的工作,因此,如果您想卖给他们的东西看起来像他们在做额外的工作,那么您就不会通过重复调用“高级论点”来说服他们。他们需要知道您正在向他们展示一种减少而不是增加工作量的方法。

如今, DI通常被视为额外的复杂性,并有望在以后减少潜在的复杂性,这具有价值,但对试图减少其前期工作量的人而言却没有那么多。在许多情况下,DI只是YAGNI的另一层。而且这种复杂性的代价也不是零,对于一个不习惯这种模式的团队来说,这实际上是很高的。这就是将真实的美元铲入水桶,这可能永远不会产生任何收益。

放到
合适的位置。最好的选择是在DI有用的地方使用DI,而不是在常见的地方使用DI。例如,许多应用程序使用DI来选择和配置数据库。而且,如果您的应用程序附带数据库层,那么放置它是一个诱人的地方。但是,如果您的应用附带了内置的用户不可见数据库(否​​则该数据库已紧密集成到代码中),则在运行时需要替换数据库的机会接近零。通过说“看,我们可以轻松地做到这一点,我们永远,永远不会想做的事”来出售DI,很可能会让您和老板一起进入“这个家伙有不好的主意”清单。

但是说相反,您有调试输出,通常在发行版中将IFDEF排除掉。每次用户遇到问题时,您的支持团队都必须向他们发送调试版本,以便他们可以获取日志输出。您可以在此处使用DI来允许客户在日志驱动程序之间切换- LogConsole适用于开发人员,LogNull客户和LogNetwork有问题的客户。一个统一的构建,运行时可配置。

然后,您甚至不必将其称为DI,而是被称为“ Mel的好主意”,并且您现在位于好主意列表中,而不是坏主意列表中。人们将看到该模式的效果,并在其代码中有意义的地方开始使用它。

当您正确地提出一个想法时,人们不会知道您说服了他们任何东西。


8

当然,控制反转(IoC)有很多好处:它简化了代码,增加了执行典型任务的魔力,等等。

然而:

  1. 它增加了很多依赖性。用Spring编写的简单的hello world应用程序可以重达12 MB,我看到Spring只是为了避免几行构造函数和setter而添加的。

  2. 它添加了上述魔术,这对于外部人员(例如,需要在业务层进行一些更改的GUI开发人员)来说很难理解。请参阅伟大的文章“ 创新思维不要一样”-纽约时报,描述了专家们如何低估了这种影响。

  3. 很难跟踪类的用法。像Eclipse这样的IDE具有强大的能力来跟踪给定类或方法调用的使用情况。但是,在调用依赖项注入和反射时,会丢失此跟踪。

  4. IoC的使用通常不以注入依赖关系结尾,它以无数代理结尾,并且您获得计数数百行的堆栈跟踪,其中90%是代理并通过反射调用。它极大地增加了堆栈跟踪分析的复杂性。

因此,我自己是Spring和IoC的忠实拥护者,最近我不得不重新考虑以上问题。我的一些同事是从Python和PHP统治的Web世界中汲取的Java开发人员中的Web开发人员,对于Javaist而言,显而易见的东西对他们而言并不明显。我的其他一些同事正在做一些技巧(当然没有记录),这使错误很难发现。当然,IoC滥用会使事情减轻;)

我的建议是,如果您在工作中强迫使用IoC,将对其他人可能会造成的任何误用负责;)


+1和其他功能强大的工具一样,它很容易被滥用,您必须了解何时不使用它。
菲尔(Phil)

4

我认为ChrisF做对了。请勿自行出售DI。但是出售关于单元测试代码的想法。

使DI容器进入体系结构的一种方法可能是,然后缓慢地使测试将实现的驱动力引入该体系结构中。例如,假设您正在使用ASP.NET MVC应用程序中的控制器。假设您这样编写课程:

public class UserController {
    public UserController() : this (new UserRepository()) {}

    public UserController(IUserRepository userRepository) {
        ...
    }

    ...
}

这样,您就可以编写与用户存储库的实际实现脱节的单元测试。但是该类仍然可以在没有DI容器的情况下为您创建它。无参数构造函数将使用默认存储库创建它。

如果您的同事发现这导致更清洁的测试,也许他们可以意识到这是一种更好的方法。也许您可以让他们开始这样的编码。

一旦他们意识到这是比UserController了解特定UserRepository实现更好的设计,就可以采取下一步行动,向他们展示可以拥有一个组件,即DI容器,该组件可以自动提供所需的实例。


大!我目前正在执行此操作,因为即使我无法获得DI,我也确实希望进行自动化的单元测试。我忘了提,单元测试,也没有在我们的团队:(这样做,如果我将是第一个。
梅尔

2
在那种情况下,我会说这是真正的问题。找到抵抗单元测试的根源,然后加以攻击。让DI暂时说谎。恕我直言,测试更为重要。
Christian Horsdal 2012年

@ChristianHorsdal我同意,我希望它可以松散地结合在一起,以方便进行模拟/测试。
梅尔(Mel)

这个想法真的很酷。。。测试很畅销
Bunglestink 2012年

2

也许最好的办法是退后一步,问自己为什么要引入DI容器?如果要提高可测试性,那么您的首要任务是让团队投入到单元测试中。就个人而言,我会比缺少DI容器更加担心缺少单元测试。

拥有一个或多个全面的单元测试套件,您可以放心地着手设计随时间的演变。没有它们,您将面临越来越艰巨的斗争,以使您的设计与不断变化的需求保持步调一致,这最终会导致许多团队失败。

因此,我的建议是先进行冠军的单元测试和重构,然后再引入DI,最后引入DI容器。


2

我在这里听到您团队的一些重大反对意见:

  • “没有第三方库”-又称为“此处未发明”或“ NIH”。担心的是,通过实现第三方库并因此使代码库依赖于该库,如果该库存在错误或安全漏洞,甚至只是您需要克服的限制,您就会依赖于此第三方库一方修复其库,以使您的代码正常工作。同时,由于其“您的”程序严重失败,被黑客入侵或没有按照他们的要求进行操作,因此您仍然承担责任(第三方开发人员会说他们的工具“按原样” ”,后果自负)。

    反对意见是第三方库中已经存在解决问题的代码。您是从头开始构建计算机吗?您编写Visual Studio吗?您是否在避开.NET中的内置库?不。您对Intel / AMD和Microsoft有一定程度的信任,他们总是会发现错误(并且不一定总是快速修复它们)。添加第三方库可以使您快速获得可维护的代码库,而无需费力。当您告诉他们.NET加密库中的错误使他们对使用.NET程序时遭受客户入侵的事件负有责任时,Microsoft的律师大军将对您大笑。尽管微软肯定会跳出补丁来修复其所有产品中的“零时”安全漏洞,

  • “我们想将所有东西塞进一个组件中”-实际上,您不需要。至少在.NET中,如果更改一行代码,则必须重建包含该行代码的整个程序集。良好的代码设计基于多个程序集,您可以尽可能独立地对其进行重建(或者至少只需重建依赖关系,而不是依赖关系)。如果他们真的要发布仅是EXE的EXE(在某些情况下确实有价值),那么可以使用一个应用程序;ILMerge。

  • “ DI是禁忌”-WTF?使用“接口”代码结构,DI是任何O / O语言中公认的最佳实践。如果您的团队没有松散地耦合对象之间的依赖关系,您的团队将如何遵守GRASP或SOLID设计方法?如果他们不打扰,那么他们将使意大利面条工厂永存,这会使产品变得复杂起来。现在,DI确实通过增加对象数量而增加了复杂性,虽然它使替换不同的实现变得容易,但它使更改接口本身变得更加困难(通过添加必须更改的代码对象的额外层)。

  • “我们不需要将这种复杂性添加到一个已经很复杂的项目中”-他们可能有一点。在任何代码开发中都要考虑的关键是YAGNI。“您不需要它”。从一开始就经过SOLIDly精心设计的设计就是经过SOLID“基于信仰”的设计;您不知道是否需要SOLID设计所提供的轻松更改,但是您有直觉。虽然你可能是对的,但你也可能是错误的。一个非常坚固的设计最终可能会被视为“千层面代码”或“馄饨代码”,具有如此多的层或一口大小的块,使看似微不足道的更改变得非常困难,因为您必须深入研究许多层并/或追溯这样复杂的调用堆栈。

    我支持三招规则:使其正常运行,使其整洁并使其牢固。当您第一次编写一行代码时,它只是必须工作,并且您不能获得象牙塔设计的积分。假设这是一次性的,然后要做就做。第二次查看该代码时,您可能正在寻求扩展或重用它,而这并非您原本想的那样。在这一点上,通过重构使其更加优雅和易于理解;简化复杂的逻辑,将重复的代码提取到循环和/或方法调用中,在此处添加一些注释,以解决所有必须保留的麻烦,等等。第三次查看该代码可能很重要。现在,您应该遵守SOLID规则;注入依赖关系而不是构造依赖关系,提取类以容纳与代码的“实质”不太紧密的方法,

  • “这不像我们要插入不同的实现方式”-先生,您有一个不相信您手中的单元测试的团队。我认为编写一个单元测试是不可能的,它不能独立运行且没有副作用,而不能“模拟”具有这些副作用的对象。任何不平凡的程序都有副作用;如果您的程序读取或写入数据库,打开或保存文件,通过网络或外围套接字进行通信,启动另一个程序,绘制窗口,输出到控制台,或者以其他方式使用了进程“沙盒”之外的内存或资源,副作用。如果它不做这些事情,它会做什么,我为什么要购买它?

    公平地讲,单元测试不是证明程序有效的唯一方法。您可以编写集成测试,编写经过数学验证的算法的代码,等等。但是,单元测试确实很快显示出,在给定输入A,B和C的情况下,这段代码输出了预期的X(或不输出),因此在给定的情况下,代码可以正常工作(或不能正常工作)。单元测试套件可以高度自信地证明代码在所有情况下都能按预期运行,并且还提供了数学证明无法实现的功能;演示程序仍然可以按预期方式工作。该算法的数学证明不能证明其正确实现,也不能证明所做的任何更改都正确实现并且没有破坏它。

简短的是,如果这个团队看不到DI会做的任何价值,那么他们将不会支持它,并且每个人(至少是所有高级人士)都必须购买一些东西才能真正改变即将发生。他们非常重视YAGNI。您将重点放在SOLID和自动化测试上。真相在中间。


1

未经批准,请小心在项目中添加其他功能(例如DI)。如果您的项目计划有时间限制,那么您可能会陷入没有足够时间来完成批准的功能的陷阱。

即使您现在不使用DI,也要参与测试,以便您确切知道公司对测试的期望。将有另一个项目,您将能够与当前项目与DI的测试进行对比。

如果您不使用DI,则可以发挥创造力,并使代码成为DI“就绪”。使用无参数构造函数来调用其他构造函数:

MyClassConstructor()
{
    MyClassConstructor(new Dependency)
    Property = New Dependent Property
}

MyClassConstructor(Dependency)
{
    _Dependency = Dependency
}

0

我认为实际上他们最大的担忧之一是相反的:DI并没有使项目变得复杂。在大多数情况下,它减少了很多复杂性。

试想如果没有DI,您将如何获得所需的从属对象/组件?通常,它是通过从存储库中查找,获取单例或实例化自己来完成的。

前2个代码需要额外的代码来定位相关组件,在DI世界中,您无需执行任何操作即可执行查找。实际上,您降低了组件的复杂性。

如果您在某些情况下实例化自己不合适,那甚至会增加复杂性。您需要知道如何正确创建对象,需要知道将对象初始化为可用状态的值,所有这些都会给代码增加额外的复杂性。

因此,DI并没有使项目变得复杂,而是通过使组件更简单而使项目变得更简单。

另一个大争论是,您不会插入其他实现。是的,在生产代码中,在实际的生产代码中实现许多不同的实现并不常见。但是,有一个主要地方需要不同的实现:单元测试中的模拟/存根/测试双打。为了使DI能够正常工作,在大多数情况下,它依赖于界面驱动的开发模型,从而使在单元测试中进行模拟/存根成为可能。除了替换实现之外,“面向接口的设计”还使设计更加简洁。当然,您仍然可以使用不带DI的界面进行设计。但是,单元测试和DI促使您采用这种做法。

除非您公司中的“神”确实反对:“简单代码”,“单元测试”,“模块化”,“单一责任”等,否则就没有理由拒绝使用DI。


从理论上讲这很好,但是在实践中添加DI容器会增加额外的复杂性。论据的第二部分说明了为什么值得进行这项工作,但仍然可行。
schlingel 2012年

实际上,根据我过去的经验,DI可以减少而不是增加复杂性。是的,它在系统中添加了一些额外的东西,这增加了复杂性。但是,使用DI可以大大降低系统中其他组件的复杂性。最后,实际上降低了复杂性。对于第二部分,除非他们不进行任何单元测试,否则这不是多余的工作。如果没有DI,则它们的单元测试通常将很难编写和维护。使用DI可以大大减少工作量。因此,实际上这是工作的减少(除非他们不是并且不信任单元测试)
Adrian Shum 2012年

我应该强调一件事:DI在我的回应中并不意味着任何现有的DI框架。这只是设计/开发应用程序中组件的方法。如果您的代码是接口驱动的,则组件将期望注入依赖项,而不是查找/创建依赖项,那么您将受益匪浅。通过这种设计,即使您正在编写一些用于组件布线的特定于应用程序的“加载器”(没有通用的DI fw),您仍将从我提到的内容中受益:降低复杂性,减少单元测试的工作
Adrian Shum 2012年

0

“我们希望保持简单,如果可能的话,将所有东西都装进一个组件中。DI是一种无用的复杂性,没有任何好处。”

好处是它促进了接口的设计,并允许轻松进行模拟。因此,这有利于单元测试。单元测试可确保可靠,模块化和易于维护的代码。如果您的老板仍然坚持一个坏主意,那么您无能为力-他们正在反对其公认的行业最佳实践。

让他们尝试使用DI框架和模拟库编写单元测试,他们很快就会喜欢上它。


现在,我发现系统中的类数从字面上增加了一倍或三倍是有用的。这类似于通过为每种方法编写单个测试或编写有意义的单元测试来尝试获得100%单元测试覆盖率的辩论。
Andrew T Finnell,2012年

我不明白,您认为单元测试是个好主意吗?如果这样做,您还会模拟对象吗?使用接口和DI更容易。但这似乎是您所反对的。
NimChimpsky

这确实是鸡肉和鸡蛋的问题。接口/ DI和单元测试之间的联系是如此紧密,以至于很难做到一个都没有。但是我确实相信,从存在的代码库开始,单元测试是一个更好,更轻松的途径。逐步将旧的模糊代码重构为内聚的组件。在那之后,您可能会争辩说,对象创建应该使用DI框架完成,而不是new随处可见并编写工厂类,因为您已经具备了所有这些组件。
斯派克,2012年

0

您是否与这些人一起从事任何较小的项目,或者只是一个大项目?说服人们在中学/大学项目中尝试新事物要比团队/公司面包和黄油容易得多。主要原因是,如果失败,则删除/替换它的成本会小得多,并且在一个小项目中将它部署到任何地方的工作量将大大减少。在现有的大型系统中,您可能要花大笔的前期成本立即进行任何地方的更改,或者在较长的时间内,代码是新旧方法的混合体,会增加摩擦,因为您必须记住每个类的设计方式上班。


0

尽管促进DI的事情之一是单元测试,但是我认为在某些情况下它增加了一层复杂性。

  • 拥有比测试容易和维修困难的汽车更好地进行测试的汽车,但易于维修。

  • 在我看来,推动DI的团队似乎只是通过影响某些现有设计方法是不好的来表达自己的想法。

  • 如果我们有一种方法可以执行5种不同的功能

    DI-人说:

    • 没有分离的关注点。[那是什么问题?他们试图使它看起来很糟糕,这在逻辑和编程中最好了解彼此正在做的事情,以更好地避免冲突和错误期望,并轻松地查看代码的影响,因为我们无法完全改变您的想法。彼此不相关的对象100%或至少我们不能保证这一点(除非回归测试为什么很重要?}
    • 复杂[他们希望通过增加五个以上的类来分别处理一个功能,并通过设置(XML或字典),并根据设置(XML或字典),另外创建类(容器)来为所需功能创建类来降低复杂性。讨论”,然后他们将复杂性移到了容器和结构上,[对我来说,这就像将复杂性从一处移到了两处,所有复杂的语言都可以解决。]

我认为最好创建适合业务需求的自己的设计模式,而不要受到DI的影响。

支持DI的方法在某些情况下可能会有所帮助,例如,如果同一接口有数百个实现依赖于选择器,并且这些实现在逻辑上有很大不同,那么在这种情况下,我将使用DI或DI的类似技术。但是,如果我们对于同一接口的实现很少,则不需要DI。

By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.