众所周知,我是依赖注入(DI)和自动化测试的忠实拥护者。我可以整日谈论它。
背景
最近,我们的团队刚刚得到了一个从头开始构建的大型项目。它是具有复杂业务需求的战略应用程序。当然,我希望它变得干净整洁,对我而言意味着:可维护和可测试。所以我想使用DI。
电阻
问题出在我们团队中,DI是禁忌。它被提出了几次,但众神不赞成。但这并没有让我感到沮丧。
我的举动
这听起来可能很奇怪,但是第三方库通常未经我们的架构师团队批准(请考虑:“您不要说话,Ninject,NHibernate,Moq或NUnit,以免我割伤手指”)。因此,我没有使用已建立的DI容器,而是编写了一个非常简单的容器。基本上,它在启动时连接了所有依赖项,注入了任何依赖项(构造函数/属性),并在Web请求结束时处置了所有可抛弃的对象。它非常轻巧,可以满足我们的需求。然后,我请他们对其进行审核。
响应
好,简短点。我遭到了沉重的抵抗。主要论点是:“我们不需要在已经很复杂的项目中增加这种复杂性”。另外,“这不像我们将插入组件的不同实现”。还有“我们希望保持简单,如果可能的话,将所有内容都放入一个程序集中。DI是一种无用的复杂性,没有任何好处”。
最后,我的问题
你处理我的情况?我不擅长表达自己的想法,我想知道人们如何表达自己的观点。
当然,我假设像我一样,您更喜欢使用DI。如果您不同意,请说出原因,以便我能看到硬币的另一面。看到一个不同意的人的观点真的很有趣。
更新
谢谢大家的回答。它确实使事情变得透视。拥有另一双眼睛可以给您反馈非常好,十五岁的确很棒!这确实是一个很好的答案,可以帮助我从不同角度看待这个问题,但是我只能选择一个答案,所以我只选择投票率最高的一个。感谢大家花时间回答。
我认为这可能不是实施DI的最佳时机,我们还没有准备好。相反,我将致力于使设计可测试,并尝试提出自动化的单元测试。我知道编写测试是额外的开销,并且如果曾经确定额外的开销不值得,我个人仍然认为这是一个成功的情况,因为设计仍然可以测试。而且,如果将来进行测试或DI是一种选择,那么设计可以轻松应对。
#1 楼
采取几个相反的论点:“我们希望保持简单,如果可能的话,将所有内容都放入一个程序集中。DI是一种无用的复杂性,没有任何好处。”就像我们将插入组件的不同实现一样。”
您想要的是系统可测试的。为了易于测试,您需要考虑对项目的各个层(数据库,通信等)进行模拟,在这种情况下,您将插入组件的不同实现。
将DI出售给测试的好处您。如果项目很复杂,那么您将需要良好的,可靠的单元测试。
另一个好处是,在对接口进行编码时,如果您想出一个更好的实现(更快,更少的内存消耗,无论如何)。 )之一,使用DI可以更轻松地将旧的实现换成新的实现。
我在这里要说的是,您需要解决DI带来的好处,而不是争论DI为了DI。通过使人们同意以下说法:
“我们需要X,Y和Z”
然后您转移问题。您需要确保DI是此语句的答案。这样,您的同事将拥有解决方案,而不是觉得解决方案强加于他们。
评论
+1。简明扼要。不幸的是,从梅尔的同事们提出的观点来看,他将很难说服他们即使尝试也值得-斯派克在回答中说,更多的是人的问题而不是技术的问题。
–PatrykĆwiek
2012年5月23日上午10:46
@ Trustme-I'maDoctor-哦,我同意这是一个人的问题,但是使用这种方法,您展示的是好处,而不是为了自己而争论DI。
–ChrisF♦
2012年5月23日10:56
没错,我希望他能好运,如果拥有适当的可测试性,他作为开发人员的生活将更加轻松。
–PatrykĆwiek
2012年5月23日11:02
+1我认为您的最后一段应该是第一段。这是问题的关键。如果有人提出要进行单元测试,并且建议将整个系统转换为DI模型,我也对他说不。
– Andrew T Finnell
2012年5月23日11:16
+1确实很有帮助。我不会说DI是单元测试的绝对必需,但这确实使事情变得简单得多。
–梅尔
2012年5月23日12:41
#2 楼
从您的写作来看,DI本身不是禁忌,而是使用DI容器,这是另一回事。我猜想,当您编写独立的组件,使其他组件需要传递(例如,由构造函数传递)时,您的团队不会遇到任何问题。只是不要将其称为“ DI”。您可能会认为,对于此类组件,不使用DI容器是一件很乏味的事情,您是对的,但是请让您的团队有时间自己找出问题的答案。 。
评论
+1解决政治/教条僵局的务实方法
– k3b
2012年5月23日12:15
值得考虑的是仍然要手动连接所有对象,并仅在开始变得毛茸茸时才引入DI容器。如果这样做,他们不会将DI容器视为增加了复杂性,而是将其视为增加了简单性。
–菲尔
2012年5月23日13:49
唯一的问题是将松散耦合的依赖项传递给依赖项的模式是DI。 IoC或使用“容器”解决松散耦合的依赖关系是自动DI。
– KeithS
2012年5月24日21:07
#3 楼
如您所愿,我将支持您的团队反对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不需要进行测试。我很少将其用于此目的,因为我更喜欢根据系统中的实际软件而不是某些替代测试版本来测试系统。我在测试中所做的所有更改都(或可能)存储在属性文件中,这些文件将程序定向到测试环境中的资源,而不是生产环境。我宁愿花时间用生产数据库的副本来建立测试数据库服务器,也不愿花时间对模拟数据库进行编码,因为这样我就可以跟踪生产数据的问题(字符编码问题只是一个例子) )和系统集成错误以及其余代码中的错误。
依赖倒置也不需要依赖注入。您可以轻松地使用静态工厂方法来实现Dependency Inversion。也就是说,实际上,它是在1990年代完成的。
实际上,尽管我使用DI已有十年之久,但它提供的唯一让我感到信服的优点是配置能力第三方库以使用其他第三方库(例如,将Spring设置为使用Hibernate,并且两者都使用Log4J)以及通过代理启用IoC。如果您没有使用第三方库,也没有使用IoC,则没有太多意义,并且确实增加了不必要的复杂性。
评论
我不同意,可以降低复杂性。我知道,因为我已经做到了。当然,某些问题/要求会增加复杂性,但除此之外,大多数问题/要求可以轻松消除。
–瑞奇·克拉克森(Ricky Clarkson)
2012年5月23日19:00
@Ricky,这是一个微妙的区别。就像我说的那样,工程师可以增加复杂度,并且可以消除复杂度,因此您可能降低了实现的复杂度,但是根据定义,您无法降低问题的最佳解决方案的复杂度。大多数情况下,降低复杂性似乎只是将其转移到其他地方(通常转移到库中)。无论如何,我要说的第二点是DI不会降低复杂性。
– Old Pro
2012年5月23日19:07
@Lee,在代码中配置DI甚至没有多大用处。 DI的最广泛接受的好处之一是无需重新编译代码即可更改服务实现的能力,如果您在代码中配置DI,则会丢失它。
– Old Pro
2012年5月23日20:58
@OldPro-在代码中进行配置具有类型安全的优势,并且大多数依赖项是静态的,并且无法从外部定义中受益。
–李
2012年5月23日在21:11
@Lee如果依赖项是静态的,那么为什么不首先通过硬编码而不通过DI绕行呢?
–康拉德·鲁道夫(Konrad Rudolph)
2012年5月24日12:54
#4 楼
很遗憾地说,鉴于您的同事在您的问题中所说的,您遇到的问题是政治性质的,而不是技术性质的。您应该牢记两个主题:“这总是一个人的问题”
“变化令人恐惧”
当然,您可以提出具有充分的技术理由和利益的许多反驳理由,但这会使您与公司其他部门陷入困境。他们已经有了不想要的立场(因为他们对现在的工作方式很满意)。因此,如果您坚持不懈,甚至可能会损害您与同事的关系。相信我,因为这发生在我身上,并最终在几年前让我被解雇(我年轻又幼稚);这是您不想让自己陷入困境的情况。
问题是,您需要获得一定程度的信任,然后人们才能改变现有的工作方式。除非您处于可以信任的位置,或者可以决定这些事情,例如担任架构师或技术主管,否则您几乎无济于事。要使公司的文化得到扭转,需要说服力和精力(如采取小的改进步骤)需要大量时间才能赢得信任。我们所说的时间跨度为数月或数年。
如果您发现当前的公司文化与您不合作,那么至少您知道在下一次工作面试中还有另一件事要问。 ;-)
#5 楼
不要使用DI。算了吧。创建GoF工厂模式的实现,该模式可以“创建”或“查找”您的特定依赖项,并将其传递给您的对象并保留在该对象上,但不要将其称为DI并且不要创建复杂的通用框架。保持简单和相关。确保相关性和您的类是可以切换到DI的;但请保留工厂的主人资格,并限制其范围。
随着时间的推移,您可以希望它会增长,甚至有一天可以过渡到DI。让领导者按自己的时间得出结论,不要推动。即使您从未做到这一点,至少您的代码也具有DI的一些优势,例如,可单元测试的代码。
评论
+1与项目一起成长,并“不要称之为DI”
–凯文·麦考密克(Kevin McCormick)
2012年5月23日13:52
我同意,但最后是DI。只是不使用DI容器而已。它把负担转移给了呼叫者,而不是将负担集中在一处。但是,将其称为“外部依赖关系”而不是DI是明智的。
–胡安·门德斯(Juan Mendes)
2012年5月23日18:06
我经常想到,要真正掌握象DI这样的象牙塔概念,您必须经历与传福音者相同的过程,才意识到首先要解决的问题。作者经常忘记这一点。坚持使用低级模式语言,结果可能会(或可能不会)出现对容器的需求。
–汤姆·W
2012年5月29日上午10:52
@JuanMendes从他们的回应中,我认为外部化依赖关系是他们所反对的,因为他们也不喜欢第三方库,并且希望所有内容都在单个程序集中(整体式)。如果是这样,那么称它为“ Externalizing Dependencies”并不比调用D.I.好。从他们的有利位置。
–乔纳森·诺伊费尔德
15年4月10日在19:01
#6 楼
我见过将DI发挥到极致的项目,以进行单元测试和模拟对象。如此之多以至于DI配置本身就成为了编程语言,并且具有使系统像实际代码一样运行所需的配置。由于缺少适当的内部API,该系统目前正在遭受苦难无法测试?您是否希望将系统切换到DI模型可以更好地进行单元测试?您不需要容器即可对代码进行单元测试。使用DI模型可以使您更轻松地对Mock对象进行单元测试,但这不是必需的。
您无需立即切换整个系统就可以与DI兼容。您也不应该随意编写单元测试。当您在功能和错误工作单中遇到代码时,请对其进行重构。然后确保您触摸过的代码易于测试。
将CodeRot视为一种疾病。您不想开始随意破解某些东西。您有一个病人,看到一个痛处,因此您开始修复该处及其周围的东西。一旦该区域干净,您就可以继续下一个。迟早您将接触到大部分代码,并且不需要这种“大规模”的体系结构更改。
我不喜欢DI和Mock测试是互斥的。在看到一个项目进行了之后,由于缺少更好的词而疯狂地使用DI,我也很疲倦,没有看到好处。
在某些地方可以使用DI:
数据访问层交换用于叙述数据的策略
身份验证和授权层。
用户界面主题
服务
您自己的系统或第三方可以使用的插件扩展点。
要求每个开发人员知道DI配置文件在哪里(我知道容器可以通过代码实例化,但是为什么要这样做。)当他们想要执行RegEx时令人沮丧。哦,我看到有IRegExCompiler,DefaultRegExCompiler和SuperAwesomeRegExCompiler。但是,我无法在代码中的任何位置进行分析,以查看使用Default的位置和使用SuperAwesome的位置。
如果有人向我展示一些更好的东西,然后尝试用自己的话说服我,我自己的性格就会做出更好的反应。对于某人,要花费时间和精力来改善系统,有话要说。我发现没有很多开发人员足够关心产品的真正改进。如果您可以对他们进行系统的真正更改,并向他们展示如何使它变得更好,更轻松,那么这比任何在线研究都有价值。更改为系统是为了使您每次碰到的代码变得更好。迟早您将能够将系统转换为更好的系统。
评论
“ DI和Mock测试是互斥的”这是错误的,它们也不是互斥的。他们在一起工作得很好。您还列出了DI很好的地方,服务层,dao和用户界面-几乎到处都是吗?
– NimChimpsky
2012年5月23日11:28
@Andrew有效积分。当然,我不希望它走得太远。我最希望对业务类别进行自动化测试,因为我们必须实施非常复杂的业务规则。而且我确实知道我仍然可以在没有容器的情况下测试它们,但是就个人而言,使用容器才有意义。我实际上做了一些样本来说明这个概念。但它甚至没有被看过。我猜我的错误是我没有创建一个测试来显示测试/模拟的简单程度。
–梅尔
2012年5月23日12:39
这么多协议!我真的很讨厌DI配置文件及其对跟踪程序流的影响。整个过程变成了“远距离的怪异动作”,各部分之间没有明显的联系。当在调试器中读取代码比在源文件中读取代码变得容易时,这确实是错误的。
–赞·山猫
2012年5月24日18:03
#7 楼
DI不需要控制容器或模拟框架的侵入式转换。您可以通过简单的设计解耦代码并允许DI。您可以通过内置的Visual Studio功能进行单元测试。公平地说,您的同事有一个要点。严重使用这些库(由于它们不熟悉它们,会导致学习困难),这会引起问题。最后,代码很重要。请勿在测试中使用任何产品。
请记住,在这些情况下必须折衷。
评论
我同意DI不需要IoC容器。尽管我不会将其称为具有侵入性的(至少是此实现),因为代码被设计为与容器无关的(主要是构造函数注入),并且所有DI东西都发生在一个地方。因此,即使没有DI容器,它仍然可以正常工作-我放入了无参数构造函数,该构造函数将具体实现“注入”到具有依赖项的其他构造函数中。这样,我仍然可以轻松地模拟它。是的,我同意,DI实际上是通过设计实现的。
–梅尔
2012年5月23日12:47
+1妥协。如果没有人愿意妥协,每个人都会遭受痛苦。
– Tjaart
2012年5月25日上午10:34
#8 楼
我认为您不能再出售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.
评论
我们目前不在团队中进行任何单元测试。我怀疑您的最后声明是正确的。我已经进行了几次单元测试,但是没有人对此表现出真正的兴趣,并且总是有我们不能采用它的原因。
–梅尔
2012年5月23日12:26
#9 楼
如果您发现自己必须向团队“出售”一项原则,那么您可能已经走错了几英里。没有人愿意做额外的工作,因此,如果您试图出售它们的东西看起来像他们在做额外的工作,那么您就不会通过反复援引高级论点来说服他们。他们需要知道您正在向他们展示减轻他们工作量而不是增加工作量的方法。
现在,DI通常被视为额外的复杂性,并且有望在以后降低复杂性,这具有价值,但对试图减少其前期工作量的人而言却没有那么多。在许多情况下,DI只是YAGNI的另一层。而且这种复杂性的代价也不是零,对于一个不习惯这种模式的团队来说,这实际上是很高的。这就是将真实的美元扔进一个水桶里,它可能永远不会产生任何收益。
最好将它放在它的位置上
最好在最有用的地方使用DI,而不是仅仅在它所在的位置使用DI共同。例如,许多应用程序使用DI来选择和配置数据库。而且,如果您的应用附带数据库层,那么放置它是一个诱人的地方。但是,如果您的应用附带一个内置的用户不可见数据库,而该数据库否则已紧密集成到代码中,则在运行时需要替换数据库的机会接近零。并通过说“看,我们可以轻松地做到这一点,这是我们永远不会想要做的事情”来出售DI,很可能会让您和老板一起进入“这个家伙有不好的主意”列表。
但是要说的是,您有调试输出,通常在发行版中将IFDEF排除掉。每次用户遇到问题时,您的支持团队都必须向他们发送调试版本,以便他们可以获取日志输出。您可以在此处使用DI来允许客户在日志驱动程序之间进行切换-开发人员使用
LogConsole
,客户使用LogNull
,问题用户使用LogNetwork
。一个统一的构建,运行时可配置。然后,您甚至不必将其称为DI,而是被称为“ Mel的好主意”,并且您现在位于好主意列表中,而不是坏主意列表中。人们将看到该模式的运行状况,并在其代码中有意义的地方开始使用它。
当您正确地提出一个想法时,人们不会知道您说服了他们任何东西。
#10 楼
当然,控制反转(IoC)有很多好处:它简化了代码,增加了执行典型任务的魔术,等等。但是:
它增加了很多依赖性。用Spring编写的简单的hello world应用程序可以重达12 MB,而我看到Spring只是为了避免几行构造函数和设置方法而添加。
它增加了上述魔术,这对于外界来说是很难理解的(例如,需要在业务层进行一些更改的GUI开发人员)。请参阅《纽约时报》精彩文章《创新思维不要有相同想法》,其中描述了专家们如何低估了这种影响。
很难追踪类的用法。像Eclipse这样的IDE具有强大的能力来跟踪给定类或方法调用的使用情况。但是,在调用依赖项注入和反射时,会丢失此跟踪。
IoC的使用通常不会以注入依赖关系而结束,它以无数代理结束,并且您会得到计数数百行的堆栈跟踪,其中90%是代理并通过反射调用。
所以,我自己是Spring和IoC的忠实拥护者,最近我不得不重新考虑上述问题。我的一些同事是从Python和PHP统治的Web世界中带入Java世界的Web开发人员,对于Java主义者而言,显而易见的事情对他们而言并不明显。我的其他一些同事正在做一些技巧(当然没有记录),这使错误很难发现。当然,IoC的滥用会使事情减轻;)
我的建议,如果您在工作中强迫使用IoC,则对任何其他人将要使用的滥用承担责任;)
评论
+1和其他功能强大的工具一样,它很容易被滥用,您必须了解何时不使用它。
–菲尔
2012年5月23日13:52
#11 楼
我认为ChrisF做对了。请勿自行出售DI。但是出售有关对代码进行单元测试的想法。将DI容器引入体系结构的一种方法可能是,然后缓慢地使测试将实现的驱动力引入与该体系结构相适应的东西。例如。可以说您正在使用ASP.NET MVC应用程序中的控制器。假设您是这样编写类的:
public class UserController {
public UserController() : this (new UserRepository()) {}
public UserController(IUserRepository userRepository) {
...
}
...
}
这使您可以编写与用户存储库的实际实现分离的单元测试。但是该类仍然可以在没有DI容器的情况下为您创建它。无参数构造函数将使用默认存储库创建它。
如果您的同事可以看到这导致更清洁的测试,也许他们可以意识到这是一种更好的方法。也许您可以让他们开始这样的编码。
一旦他们意识到这是比UserController了解特定UserRepository实现更好的设计,则可以采取下一步行动,向他们展示您可以拥有一个组件,即DI容器,该组件可以自动提供所需的实例。
评论
大!我目前正在执行此操作,因为即使我无法获得DI,我也确实希望进行自动化的单元测试。我忘了提一下,我们的团队中也没有进行单元测试:(所以,如果我是第一个。
–梅尔
2012年5月23日晚上11:08
在那种情况下,我要说的是这里的真正问题。找到抵抗单元测试的根源,然后进行攻击。让DI暂时说谎。恕我直言,测试更为重要。
–克里斯蒂安·霍斯达尔(Christian Horsdal)
2012年5月23日在11:42
@ChristianHorsdal我同意,我希望它可以松散地结合在一起,以方便进行模拟/测试。
–梅尔
2012年5月23日12:30
这个想法真的很酷。
–废纸t
2012年5月25日12:46
#12 楼
也许最好的办法就是退后一步,问自己为什么要引入DI容器?如果要提高可测试性,那么您的首要任务是让团队投入到单元测试中。就个人而言,我会比缺少DI容器更加担心缺少单元测试。有了一个或多个全面的单元测试套件,您可以放心地着手设计升级时间。没有它们,您将面临越来越艰巨的斗争,以使您的设计与不断变化的需求保持一致,最终使许多团队失败。
所以我的建议是,首先进行冠军单元测试和重构,依次介绍DI,最后介绍DI容器。
#13 楼
我在这里听到您的团队的一些反对意见:“没有第三方库”-又称为“此处未发明”或“ NIH”。担心的是,通过实现第三方库并因此使代码库依赖于该库,如果该库存在错误或安全漏洞,甚至只是您需要克服的限制,您将变得依赖于此第三方库一方修复其库,以使您的代码正常工作。同时,由于其“您的”程序严重失败,被黑客入侵或没有按照他们的要求进行操作,因此您仍然要承担责任(第三方开发人员会说他们的工具“按原样” “,使用后果自负)。
相反的说法是第三方库中已经存在解决问题的代码。您是从头开始构建计算机吗?您编写Visual Studio吗?您是否在避开.NET中的内置库?不。您对Intel / AMD和Microsoft有一定的信任度,他们总是会发现错误(并且不一定总是快速修复它们)。添加第三方库可让您快速获得可维护的代码库,而无需费力。当您告诉他们.NET加密库中的错误使他们对使用.NET程序时遭受客户端入侵的事件负有责任时,Microsoft的律师大军会笑容满面。尽管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和自动化测试上。真相在中间。
#14 楼
如果未批准,请小心在项目中添加其他功能(例如DI)。如果您有一个有时间限制的项目计划,那么您可能会陷入没有足够时间来完成批准的功能的陷阱。即使您现在不使用DI,也要参与测试,因此您确切地知道公司对测试的期望。将会有另一个项目,您将可以通过对当前项目和DI的测试来对比问题。
如果您不使用DI,则可以发挥创造力并使代码DI- “准备”。使用无参数构造函数来调用其他构造函数:
MyClassConstructor()
{
MyClassConstructor(new Dependency)
Property = New Dependent Property
}
MyClassConstructor(Dependency)
{
_Dependency = Dependency
}
#15 楼
我认为实际上他们最大的担忧之一就是相反:DI并没有使项目变得复杂。在大多数情况下,它减少了很多复杂性。
仅考虑没有DI,您将如何获得所需的从属对象/组件?通常,它是通过从存储库中查找,获取单例或实例化自己来实现的。
前2个代码涉及到额外的代码来查找相关组件,在DI世界中,您没有执行任何操作查找。实际上,组件的复杂性降低了。
如果在某些情况下不恰当地实例化自己,甚至会带来更多的复杂性。您需要知道如何正确创建对象,需要知道将对象初始化为可用状态的值,所有这些都会给代码增加额外的复杂性。
因此,DI并没有使项目复杂,通过简化组件使其更简单。
另一个大争论是,您不会插入其他实现。是的,的确,在生产代码中,在实际的生产代码中实现许多不同的实现并不常见。但是,有一个主要地方需要不同的实现:单元测试中的模拟/存根/测试双打。为了使DI能够正常工作,在大多数情况下,它依赖于界面驱动的开发模型,从而使单元测试中的模拟/存根成为可能。除了替换实现之外,“面向接口的设计”还使设计更加简洁。当然,您仍然可以使用不带DI的界面进行设计。但是,单元测试和DI促使您采用这种做法。
除非您的公司“神志”确实认为:“简单代码”,“单元测试”,“模块化”,“单一责任” “,等等,这些都是他们反对的东西,应该没有理由让他们拒绝使用DI。
评论
从理论上讲这很好,但实际上添加一个DI容器会增加额外的复杂性。论据的第二部分说明了为什么值得进行这项工作,但仍然可行。
– schlingel
2012年5月23日12:37
实际上,根据我过去的经验,DI可以减少而不是增加复杂性。是的,它在系统中添加了一些额外的东西,这增加了复杂性。但是,使用DI可以大大降低系统中其他组件的复杂性。最后,实际上降低了复杂性。对于第二部分,除非他们不进行任何单元测试,否则这不是多余的工作。如果没有DI,则它们的单元测试通常将很难编写和维护。使用DI可以大大减少工作量。因此,实际上这是工作的减少(除非他们不是并且不信任单元测试)
–沈志安(Adrian Shum)
2012年5月24日在1:25
我应该强调一件事:DI在我的回应中并不意味着任何现有的DI框架。这只是设计/开发应用程序中组件的方法。如果您的代码是接口驱动的,则组件将期望注入依赖项,而不是查找/创建依赖项,那么您将受益匪浅。通过这种设计,即使您正在编写一些用于组件布线的特定于应用程序的“加载器”(没有通用的DI fw),您仍将从我提到的内容中受益:降低复杂性,减少单元测试的工作
–沈志安(Adrian Shum)
2012年5月24日在1:29
#16 楼
“我们希望保持简单,如果可能的话,将所有内容放入一个程序集。DI是一种无用的复杂性,没有任何好处。”好处是它促进了对接口的设计并允许简单的模拟。因此,这有利于单元测试。单元测试可确保可靠,模块化和易于维护的代码。如果您的老板仍然坚持一个坏主意,那么您无能为力-他们正在反对其公认的行业最佳实践。
让他们尝试使用DI框架和模拟库编写单元测试,他们很快就会喜欢上它。
评论
我发现您的系统中的类数量从字面上翻倍或翻了三倍。这类似于通过每种方法编写单个测试或编写有意义的单元测试来尝试获得100%单元测试覆盖率的辩论。
– Andrew T Finnell
2012年5月23日在11:20
我不明白,您认为单元测试是个好主意吗?如果这样做,您还会模拟对象吗?使用接口和DI更容易。但这似乎是您所反对的。
– NimChimpsky
2012年5月23日11:27
这确实是鸡肉和鸡蛋的问题。接口/ DI和单元测试之间的联系是如此紧密,以至于很难做到一个都没有。但是我确实相信,从现有的代码库开始,单元测试是一个更好,更轻松的途径。逐渐将旧的kludgy代码重构为内聚的组件。之后,您可能会争辩说,应该使用DI框架来创建对象,而不是在所有地方进行更新并编写工厂类,因为您已经拥有了所有这些组件。
–说话
2012年5月23日晚上11:54
#17 楼
您是否与这些人一起从事任何较小的项目,或者只是一个大项目?说服人们在中学/大学项目中尝试新事物要比团队/公司面包和黄油容易得多。主要原因是,如果失败,则删除/替换它的成本会小得多,并且在一个小项目中将其放置到任何地方的工作量要少得多。在现有的大型系统中,您可能要花大笔的前期成本立即进行任何地方的更改,或者在较长的时间内,代码是旧方法/新方法的混合体,增加了摩擦,因为您必须记住每个类的设计方式上班。#18 楼
虽然促进DI的事情之一是单元测试,但我认为在某些情况下它会增加一层复杂性。更好地进行测试,但易于维修而不是易于测试且难以维修的汽车。
在我看来,推动DI的团队只是通过影响某些现有设计方法是不好的来表达自己的想法。
如果我们有一种方法可以执行5种不同的功能
DI-People说:
没有分离的关注点。 [那是什么问题?他们试图使它看起来很糟,这在逻辑和编程中更好地了解彼此在做些什么,以更好地避免冲突和错误期望,并轻松地查看代码的影响,因为我们不能让您所有对象彼此不相关100%或至少我们不能保证{除非为什么回归测试很重要?}]
复杂[他们想通过增加五个以上的类来处理一个函数和另一个类来降低复杂性(容器)现在根据设置(XML或字典)为基础创建所需功能的类,以及“无默认值或无默认值”讨论的哲学,然后将复杂性移至容器和结构,[在我看来,将复杂性从一处转移到两处,并使用所有特定语言的复杂性来处理此问题。 ]
我相信最好创建适合业务需求的自己的设计模式
而不是受到DI的影响。
赞成DI,它在某些情况下可能会有所帮助,例如,如果您有数百个相同接口的实现依赖于选择器,并且这些实现在逻辑上有很大不同,那么在这种情况下,我将使用DI或DI类似的技术。但是,如果我们针对同一接口的实现很少,则不需要DI。
评论
听起来您需要搬到其他公司...使用DI(至少在Java,C#等中)几乎是使用单元测试的自然结果。贵公司是否使用单元测试?
或者根本不专注于DI。 DI与“可维护和可测试”不同。当对项目有不同意见时,您必须撰写,有时接受您不确定。例如,我看到控件,DI和多层框架的倒置加剧了灾难,使复杂的事情变得简单(我不能在你的情况下争论)。
“ ...我可以整天谈论它。”听起来您需要停止痴迷于某些经常被滥用的事物,这不仅仅是因为对今天的某些白皮书的教条主义。
从它的声音来看,您的同事有反对使用DI容器的客观论据:“我们不需要在已经很复杂的项目中增加这种复杂性” –他们也许是对的吗?您实际上是否从增加的复杂性中受益?如果是这样,那应该是可以争论的。他们声称“ DI是不必要的复杂性,没有好处。”如果您可以表现出客观利益,那么您应该很轻松地赢得这一殊荣,不是吗?其他注释中经常提到测试。但是测试从根本上不需要DI,除非您需要模拟(不是给定的)。