切换到SOLID后管理和组织大量增加的课程?


49

在过去的几年中,我们一直在缓慢地转换为逐渐更好的书面代码,每次只需几个小步骤。我们终于开始切换到至少与SOLID类似的东西,但是我们还没有到那儿。自从进行切换以来,开发人员最大的抱怨之一就是他们无法忍受同行审查和遍历数十个文件,而以前的每个任务只需要开发人员触摸5-10个文件即可。

在开始进行转换之前,我们的体系结构非常类似于以下内容(已授予,但又增加了一个或两个数量级的文件):

Solution
- Business
-- AccountLogic
-- DocumentLogic
-- UsersLogic
- Entities (Database entities)
- Models (Domain Models)
- Repositories
-- AccountRepo
-- DocumentRepo
-- UserRepo
- ViewModels
-- AccountViewModel
-- DocumentViewModel
-- UserViewModel
- UI

从文件的角度来看,所有内容都非常线性且紧凑。显然有很多代码重复,紧密耦合和令人头疼的问题,但是,每个人都可以遍历并弄清楚。完全的新手,从来没有像以前那样打开过Visual Studio的人,就可以在几周内解决这个问题。缺乏整体文件的复杂性,对于新手开发人员和新员工来说,在不增加太多时间的情况下就开始进行贡献相对容易。但这几乎就是代码风格的任何好处都无法体现的地方。

我全心全意地支持我们为改善代码库所做的一切尝试,但是在这样的大规模范式转换中,从团队的其他成员那里得到一些回击是很普遍的。当前几个最大的症结是:

  • 单元测试
  • 班数
  • 同行评审的复杂性

单元测试对于团队来说是一笔难以置信的辛苦卖点,因为他们所有人都认为这是浪费时间,并且他们能够比单个代码更快地整体测试代码。使用单元测试作为SOLID的认可在大多数情况下是徒劳的,并且在这一点上已经成为一个笑话。

上课人数可能是要克服的最大障碍。过去需要5-10个文件的任务现在可以占用70-100!尽管每个文件都有不同的用途,但文件的数量却是巨大的。团队的反应主要是吟和头部抓挠。以前,一项任务可能需要一个或两个存储库,一个或两个模型,一个逻辑层和一个控制器方法。

现在,要构建一个简单的文件保存应用程序,您需要一个类来检查文件是否已存在,一个类来编写元数据,一个类来进行抽象,DateTime.Now以便您可以投入时间进行单元测试,为每个包含逻辑的文件插入接口,包含每个类的单元测试,以及一个或多个文件以将所有内容添加到您的DI容器中。

对于中小型应用程序,SOLID非常容易出售。每个人都看到了可维护性的好处和便利。但是,他们只是没有看到SOLID在超大型应用程序中具有很好的价值主张。因此,我正在尝试寻找改善组织和管理的方法,以使我们度过不断增长的痛苦。


我以为基于最近完成的任务,可以更详细地说明文件量的示例。我被赋予一项任务,以在我们的一种较新的微服务中实现某些功能,以接收文件同步请求。收到请求后,服务将执行一系列查找和检查,最后将文档以及2个单独的数据库表保存到网络驱动器。

要将文档保存到网络驱动器,我需要一些特定的类:

- IBasePathProvider 
-- string GetBasePath() // returns the network path to store files
-- string GetPatientFolderName() // gets the name of the folder where patient files are stored
- BasePathProvider // provides an implementation of IBasePathProvider
- BasePathProviderTests // ensures we're getting what we expect

- IUniqueFilenameProvider
-- string GetFilename(string path, string fileType);
- UniqueFilenameProvider // performs some filesystem lookups to get a unique filename
- UniqueFilenameProviderTests

- INewGuidProvider // allows me to inject guids to simulate collisions during unit tests
-- Guid NewGuid()
- NewGuidProvider 
- NewGuidProviderTests

- IFileExtensionCombiner // requests may come in a variety of ways, need to ensure extensions are properly appended.
- FileExtensionCombiner
- FileExtensionCombinerTests

- IPatientFileWriter
-- Task SaveFileAsync(string path, byte[] file, string fileType)
-- Task SaveFileAsync(FilePushRequest request) 
- PatientFileWriter
- PatientFileWriterTests

因此,总共有15个类(不包括POCO和脚手架)可以执行非常简单的保存。当我需要创建POCO来表示一些系统中的实体,建立一些与与我们其他ORM不兼容的第三方系统进行通信的仓库以及建立用于处理某些操作的复杂性的逻辑方法时,这个数字急剧膨胀。


52
“过去占用5-10个文件的任务现在可以占用70-100个!”到底怎么了?这绝非正常现象。您要进行哪些更改,需要更改那么多文件?
欣快的

43
您必须为每个任务更改更多文件(明显更多!),这意味着您执行SOLID错误。重点是(随着时间的推移)以反映观察到的更改模式的方式组织代码,从而使更改更简单。SOLID中的每条原则都有其背后的某些理由(何时以及为何应采用该原则);看来您已经盲目地应用这些方法了。与单元测试(TDD)相同;如果您在没有很好地了解如何进行设计/体系结构的情况下进行此操作,那么您将陷入困境。
FilipMilovanović

60
您显然已将SOLID视为一种宗教而非实用工具来帮助您完成工作。如果SOLID中的某些内容使工作变得更加繁琐或使工作变得更加困难,请不要这样做。
whatsisname

25
@Euphoric:该问题可能以两种方式发生。我怀疑您是在应对70-100个课程过高的可能性。但这并非偶然,因为这恰好是一个庞大的项目,被挤入5-10个文件中(我之前在20KLOC文件中工作过...),而70-100实际上是适当数量的文件。
Flater

18
我称之为“对象幸福病”,这是一种思想混乱,它认为OO技术本身就是目的,而不仅仅是降低大型代码库工作成本的许多可能技术之一。您有一个特别高级的表格,“ SOLID幸福病”。SOLID不是目标。目标是降低维护代码库的成本。在这种情况下评估您的建议,而不是评估它是否为SOLID。(您的建议可能实际上也不是SOLID原则,这也是考虑的一个好点。)
Eric Lippert

Answers:


104

现在,要构建一个简单的文件保存应用程序,您需要一个类来检查文件是否已存在,一个类用于编写元数据,一个类来抽象化DateTime.Now现在可以为单元测试注入时间,为包含逻辑,包含每个类的单元测试的文件,以及一个或多个将所有内容添加到DI容器的文件。

我认为您误解了单一责任的想法。类的唯一职责可能是“保存文件”。为此,它可能会将职责分解为检查文件是否存在的方法,写入元数据的方法等。然后,这些方法中的每一个都有一个职责,这是类总体职责的一部分。

一个抽象的类DateTime.Now听起来不错。但是您只需要其中之一,就可以将其与其他环境功能一起包装到一个类中,以负责抽象环境功能。同样是单个责任和多个子责任。

您不需要“每个包含逻辑的文件的接口”,您需要具有副作用的类的接口,例如那些读/写文件或数据库的类;即使在那时,也只有该功能的公共部分才需要它们。因此,例如在中AccountRepo,您可能不需要任何接口,可能只需要一个接口就可以将实际的数据库访问注入到该存储库中。

单元测试对于团队来说是一笔难以置信的辛苦卖点,因为他们所有人都认为这是浪费时间,并且他们能够比单个代码更快地整体测试代码。使用单元测试作为SOLID的认可在大多数情况下是徒劳的,并且在这一点上已经成为一个笑话。

这表明您也误解了单元测试。单元测试的“单元”不是代码单元。什么是代码单位?一类?一个方法?一个变量?一条机器指令?不,“单元”是指隔离的单元,即可以与代码的其他部分隔离执行的代码。自动化测试是否为单元测试的一个简单测试就是您是否可以将其与所有其他单元测试并行运行而不影响其结果。围绕单元测试还有很多经验法则,但这是您的关键度量。

因此,如果确实可以对代码的一部分进行整体测试而不影响其他部分,则可以这样做。

始终要务实,记住一切都是妥协。您越坚持DRY,代码就必须变得越紧密。引入抽象的次数越多,代码测试越容易,但是理解起来就越困难。避免意识形态,在理想与简单之间找到一个良好的平衡。开发和维护都具有最高效率的优点。


27
我想补充一点,当人们试图坚持过于反复的“方法只能做一件事情”的口头禅,并最终以大量的单行方法结束时,仅仅因为它可以从技术上讲成是一种方法,同样会引起类似的头痛。 。
Logarr

8
关于“永远保持务实,切记一切都是妥协”:鲍伯叔叔的弟子对此一无所知(无论初衷是什么)。
彼得·莫滕森

13
总而言之,您通常有一个咖啡实习生,而不是一整套的插入式过滤器,翻转开关,检查是否需要加糖,打开冰箱,取出牛奶,外卖,下杯,倒咖啡,加糖,加牛奶,搅拌杯和送杯实习生。; P
贾斯汀时间2恢复莫妮卡

12
OP问题的根本原因似乎是误解了应该执行单个任务的功能与应该承担
alephzero

6
“规则是为了指导智者和服从愚者。” -道格拉斯·巴德(Douglas Bader)
卡兰努斯(Calanus)

29

过去需要5-10个文件的任务现在可以占用70-100!

这与单一责任原则(SRP)相反。为了达到这一点,您必须以非常精细的方式划分功能,但这不是SRP的目的-这样做忽略了凝聚力这一关键思想。

根据SRP,应根据软件可能更改的原因将其划分为模块,以便仅将一个设计更改应用于一个模块,而无需在其他地方进行修改。从这个意义上讲,一个“模块”可能对应一个以上的类,但是如果一项更改需要您触摸数十个文件,那么这实际上就是多项更改,或者您做错了SRP。

最初制定SRP的鲍勃·马丁(Bob Martin)几年前写了一篇博客文章,试图澄清这种情况。它详细讨论了SRP的目的是“改变的原因”。值得整体阅读,但是值得特别注意的是SRP的替代措辞:

将因相同原因而发生变化的事物聚集在一起。分开那些由于不同原因而改变的事物。

(强调我的)。SRP 并不是将事情分解成最小的部分。这不是一个好的设计,您的团队应该抗拒。它使您的代码库更难更新和维护。听起来您可能是出于单元测试的考虑而尝试出售您的团队,但这将使您的工作陷入困境。

同样,接口隔离原则不应视为绝对原则。与SRP相比,将代码划分得如此精细不再是一个理由,并且它通常与SRP完全吻合。一个接口包含了一些该方法的一些客户端不使用不是一个理由来打破它。您再次在寻找凝聚力。

此外,我敦促您不要以开放式原则或Liskov替代原则作为支持深层继承层次结构的理由。没有比其父类的子类更紧密的耦合,并且紧密耦合是一个设计问题。取而代之的是,在任何有意义的地方,都建议使用组合而不是继承。这将减少耦合,从而减少特定更改可能需要处理的文件数量,并且与依赖项倒置很好地匹配。


1
我想我只是想弄清楚线在哪里。在最近的任务中,我必须执行一个相当简单的操作,但是它是在一个没有太多现有脚手架或功能的代码库中。因此,我需要做的一切都非常简单,但是都非常独特,似乎不适合共享类。就我而言,我需要将文档保存到网络驱动器,并将其记录到两个单独的数据库表中。围绕每个步骤的规则都非常特殊。甚至文件名生成(一个简单的GUID)也有一些类可以使测试更加方便。
JD戴维斯

3
再次,@ JDDavis出于纯粹出于可测试性的目的,在一个单一类中选择多个类,这使购物车处于领先地位,并且直接与SRP背道而驰,后者要求将内聚功能分组在一起。我无法提供详细的建议,但是单个功能更改需要修改许多文件的问题是您应该解决(并尝试避免)的问题,而不是您要证明其合理性的问题。
John Bollinger

同意,我添加这个。用维基百科的话来说,“马丁将责任定义为改变的原因,并得出结论,一个类或模块应该只有一个,并且只有一个被改变(即重写)的理由。” 实际上,我相信这意味着SRP中的“责任”是指利益相关者,而不是功能。一个班级应仅负责一个利益相关者(要求您更改程序的人员)所要求的更改,以便您可以尽可能地更改FEW,以响应不同的利益相关者要求的更改。
Corrodias

12

过去需要5-10个文件的任务现在可以占用70-100!

这是个谎言。任务从未只占用5-10个文件。

您没有解决少于10个文件的任何任务。为什么?因为您使用的是C#。C#是一种高级语言。您正在使用10个以上的文件来创建hello world。

哦,确保您没有注意到它们,因为您没有写它们。所以你不要看它们。你相信他们。

问题不在于文件数量。这是因为您现在有太多事情要做,您不信任。

因此,弄清楚如何使这些测试正常进行,以使它们通过后就可以信任这些文件,就像您信任.NET中的文件一样。这样做是单元测试的重点。没有人关心文件的数量。他们关心自己不信任的事物的数量。

对于中小型应用程序,SOLID非常容易出售。每个人都看到了可维护性的好处和便利。但是,他们只是没有看到SOLID在超大型应用程序中具有很好的价值主张。

无论您做什么工作,在大型应用程序上更改都是很难的。在这里应用的最好的智慧并非来自鲍勃叔叔。它来自Michael Feathers在他的《有效地使用旧版代码》一书中。

不要启动重写节。旧代码代表了来之不易的知识。抛弃它是因为它有问题并且没有在新的和改进的范式X中表达出来,这只是在寻求一系列新的问题,而没有任何来之不易的知识。

而是找到使您的旧不可测试代码可测试的方法(Feathers所说的旧版代码)。在这个比喻中,代码就像一件衬衫。大型零件在自然接缝处连接,可以将其撤消以按照删除接缝的方式分离代码。这样做是为了允许您连接测试“套管”,以便隔离其余的代码。现在,当您创建测试袖子时,您会对袖子充满信心,因为您是用工作衬衫完成的。(现在,这个比喻开始受到伤害)。

这个想法是基于这样的假设,即像大多数商店一样,最新的要求是在工作代码中。这样,您就可以将其锁定在测试中,从而允许您对经过验证的工作代码进行更改,而不会丢失其经过验证的工作状态的每一点。现在,随着第一波测试的到位,您可以开始进行更改,以使“旧版”(无法测试)代码可测试。您可能会大胆,因为接缝测试通过说这一直是您的工作,并且新测试表明您的代码实际上按照您的想法进行了工作,从而为您提供了支持。

这有什么关系:

切换到SOLID后管理和组织大量增加的课程?

抽象。

您可以让我讨厌抽象不好的任何代码库。不好的抽象使我看起来很内在。当我向里看时,不要惊讶我。达到我的预期。

给我一个好名字,可读的测试(示例)说明如何使用该界面,并对其进行组织,以便我可以查找内容,并且我不在乎是否使用10、100或1000个文件。

您可以帮助我找到具有良好描述性名称的内容。把名字好的东西放在名字好的东西中。

如果正确执行所有操作,则将文件提取到仅完成3-5个其他文件即可完成任务的位置。70-100个文件仍然存在。但是他们躲在3比5后面。只有当您相信3比5才能做到这一点时,这才有效。

因此,您真正需要的是为所有人信任的所有事物和测试拿出好名字的词汇表,这样他们就不必再费神气了。没有那个,你也会让我发疯。

@Delioth 很好地说明了成长的烦恼。当您习惯将餐具放在洗碗机上方的橱柜中时,需要一些习惯才能将它们放在早餐吧上方。使某些事情变得更难。使某些事情变得容易。但是,如果人们不同意菜肴的去向,它将引起各种噩梦。在大型代码库中,问题是您一次只能移动某些餐具。因此,现在您在两个地方都有菜。令人困惑。很难让人相信菜应该在哪里。如果您想克服这个困难,那么唯一要做的就是继续移动盘子。

这样做的问题是,您真的想知道在早餐吧吃完饭是否值得,然后再进行所有这些胡说。我可以推荐的就是露营。

首次尝试新范式时,您应采用的最后一个方法是在大型代码库中。这适用于团队中的每个成员。没有人应该相信SOLID有效,OOP有效或功能编程有效。每个团队成员都应该有机会在玩具项目中尝试新想法,无论它是什么。它至少让他们看到它是如何工作的。它让他们看到它做得不好。它使他们在大麻烦之前就学会了做到这一点。

给人们一个安全的玩耍场所将帮助他们接受新的想法,并给他们信心,这些菜确实可以在新家中工作。


3
可能值得一提的是,这个问题的某些痛苦也可能只会变得越来越痛苦-是的,是的,他们可能需要为此制作15个文件...现在,他们不必再编写GUIDProvider或BasePathProvider ,或ExtensionProvider等。这是您在开始新的未开发项目时遇到的障碍-一堆支持功能,这些功能多数都是微不足道的,难以编写的,但仍然需要编写。糟透了才能建造它们,但是一旦它们在那里,您就不必考虑它们了。
迪欧斯

@Delioth我非常倾向于相信这种情况。以前,如果我们需要一些功能子集(例如,我们只希望将URL放置在AppSettings中),则只需要传递和使用一个庞大的类。使用新方法,没有理由AppSettings只为了获取URL或文件路径而进行传递。
JD戴维斯

1
不要启动重写节。旧代码代表了来之不易的知识。抛弃它是因为它有问题并且没有在新的和改进的范式X中表达出来,这只是在寻求一组新的问题,而没有任何来之不易的知识。这个。绝对。
Flot2011

10

听起来好像您的代码没有很好地解耦,并且/或者您的任务规模太大。

除非执行codemod或大规模重构,否则代码更改为5-10个文件。如果单个更改涉及很多文件,则可能意味着您的更改是级联的。一些改进的抽象(更多的单一职责,接口隔离,依赖倒置)应该会有所帮助。也有可能您承担的责任过于单一,可能会使用更多的实用主义-更短更薄的类型层次结构。那也应该使代码也更容易理解,因为您不必了解许多文件即可知道代码在做什么。

这也可能表明您的工作量太大。而不是“嘿,添加此功能”(需要UI更改和api更改,数据访问更改以及安全性更改和测试更改,等等)将其分解为更有用的部分。这变得更易于查看和理解,因为它要求您在各个位之间建立体面的合同。

当然,单元测试可以帮助所有这一切。它们迫使您建立体面的界面。它们迫使您使代码足够灵活,以注入测试所需的位(如果很难测试,就很难重用)。而且它们使人们远离过度设计的东西,因为您设计的越多,您需要进行的测试就越多。


2
5-10到70-100的文件比假设的要多。我的最后一个任务是在我们的一种新的微服务中创建一些功能。新服务应该可以接收请求并保存文档。为此,我需要使用类来表示2个单独的数据库中的用户实体,并为每个数据库存储库。代表我需要写入的其他表的存储库。用于处理文件数据检查和名称生成的专用类。而这样的例子不胜枚举。更不用说,每个包含逻辑的类都由一个接口表示,因此可以为单元测试进行模拟。
JD戴维斯

1
就我们较旧的代码库而言,它们都是紧密耦合的,并且是令人难以置信的整体。使用SOLID方法时,类之间的唯一耦合是POCO,其他所有内容都通过DI和接口传递。
JD戴维斯

3
@JDDavis-等待,为什么一个微服务直接与多个数据库一起工作?
Telastyn

1
与我们的开发经理妥协。他非常喜欢整体和程序软件。因此,我们的微服务的宏要多得多。随着我们基础架构的不断完善,事物将逐渐迁移到自己的微服务中。目前,我们在某种程度上遵循扼杀器方法将某些功能移至微服务中。由于多种服务需要访问特定资源,因此我们也将它们转移到了自己的微服务中。
JD戴维斯

4

我想阐述这里已经提到的一些事情,但更多是从绘制对象边界的角度出发。如果您遵循的是域驱动设计,那么您的对象可能会代表您业务的各个方面。 CustomerOrder(例如)将是对象。现在,如果我要根据您作为起点的类名进行猜测,那么您的AccountLogic类中的代码将针对任何帐户运行。但是,在OO中,每个类都具有上下文和标识。您不应该获取Account对象,然后将其传递给AccountLogic类并让该类对Account对象进行更改。那就是所谓的贫血模型,并且不能很好地表示面向对象。相反,您的Account类应具有诸如Account.Close()或的Account.UpdateEmail()行为,并且这些行为只会影响该帐户的实例。

现在,如何(在很多情况下应该)将这些行为的处理方式卸载到由抽象(即接口)表示的依赖项上。 Account.UpdateEmail例如,可能要更新数据库或文件,或将消息发送到服务总线等。并且将来可能会发生变化。因此,您的Account类可能依赖于,例如IEmailUpdate,它可能是AccountRepository对象实现的许多接口之一。您不希望将整个IAccountRepository接口传递给Account对象,因为它可能会做太多事情,例如搜索并找到其他(任何)帐户,您可能不希望Account对象能够访问这些帐户,但是尽管AccountRepository可能同时实现两者IAccountRepositoryIEmailUpdate界面,Account对象只能访问其所需的一小部分。这有助于您维护接口隔离原则

实际上,正如其他人所提到的那样,如果您要处理大量的类,则很可能以错误的方式使用SOLID原理(并扩展为OO)。SOLID应该可以帮助您简化代码,而不是使其复杂化。但是要真正了解SRP之类的含义需要花费时间。不过,更重要的是,SOLID的工作方式将非常取决于您的域和有限的上下文(另一个DDD术语)。没有万能的灵丹妙药。

我还要向与之共事的人强调一件事:同样,一个OOP对象应该具有行为,并且实际上是由其行为而不是其数据定义的。如果您的对象除了属性和字段之外什么都没有,则它仍然具有行为,尽管可能不是您想要的行为。一个没有其他设置逻辑的可公开写入/可设置的属性表示其包含类的行为是,允许任何地方的任何人,无论出于何种原因,在任何时候都可以修改该属性的值,而无需任何必要的业务逻辑或之间的验证。这通常不是人们想要的行为,但是如果您有一个贫血的模型,那通常就是您的类向使用它们的任何人宣布的行为。


2

因此,总共有15个类(不包括POCO和脚手架)可以执行非常简单的保存。

太疯狂了……。但是这些类听起来像我自己写的东西。因此,让我们来看看它们。现在让我们忽略接口和测试。

  • BasePathProvider-恕我直言,任何处理文件的重要项目都需要它。因此,我想已经有这样的事情了,您可以按原样使用它。
  • UniqueFilenameProvider -当然,您已经拥有了,不是吗?
  • NewGuidProvider -同样的情况,除非您只是想使用GUID。
  • FileExtensionCombiner -一样
  • PatientFileWriter -我猜这是当前任务的主要课程。

对我来说,这看起来不错:您需要编写一个新类,其中需要四个辅助类。所有四个帮助程序类听起来都非常可重用,所以我敢打赌它们已经在您的代码库中了。否则,要么是运气不好(您是团队中真正的人来写文件并使用GUID吗??),要么是其他问题。


关于测试类,请确保在创建或更新新类时应对其进行测试。因此,编写五个类也意味着编写五个测试类。但这并没有使设计变得更加复杂:

  • 您永远不会在其他地方使用测试类,因为它们将自动执行,仅此而已。
  • 您想要再次查看它们,除非您更新了被测类或将它们用作文档(理想情况下,测试清楚地表明了应如何使用一个类)。

关于接口,仅当您的DI框架或测试框架无法处理类时才需要它们。您可能会认为它们是不完善工具的损失。或者,您可能会认为它们是有用的抽象,使您忘记了还有更复杂的事情-读取接口的源时间比读取接口实现的时间要少得多。


我很感谢这种观点。在这种情况下,我正在将功能写入相当新的微服务中。不幸的是,即使在我们的主要代码库中,虽然我们确实使用了上面的某些功能,但实际上并没有一种可以远程重用的方式。需要重用的所有内容都以某个静态类结束,或者只是将其复制并粘贴到代码周围。我确实认为我还有很长的路要走,但是我同意并不是所有的东西都需要完全分解和分离。
戴维斯

@JDDavis我试图写一些不同于其他答案的东西(我大部分都同意)。每当您复制粘贴内容时,您都在防止重用,而不是泛泛地创建另一个不可重用的代码,这将迫使您一天复制粘贴更多内容。恕我直言,这是仅次于规则的第二大罪过。您需要找到自己的最佳位置,在其中遵循以下规则可以提高您的生产力(尤其是将来的更改),并且在工作不适当的情况下偶尔打破这些规则会有所帮助。都是相对的。
maaartinus

@JDDavis一切都取决于您工具的质量。示例:有些人声称DI是企业级的并且很复杂,而我声称DI 基本上是免费的+++关于违反规则:在一些地方,我需要四个类,这些地方只能在进行重大重构后注入它们,从而使代码更加难看(至少在我看来),因此我决定将它们变成单例(更好的程序员)。可能会找到更好的方法,但我对此感到满意;这些单身人士的数量自古以来就没有变化。
maaartinus

这个答案几乎表达了我在OP向问题中添加示例时的想法。@JDDavis让我补充一点,您可以通过使用简单情况下的功能工具来保存一些样板代码/类。例如,一个GUI提供程序-与其为此引入一个新的类而不是为其引入一个新的类,为什么不仅仅Func<Guid>为此目的而利用它,并且将一个匿名方法()=>Guid.NewGuid()注入到构造函数中呢?无需测试此.Net框架功能,这是Microsoft为您完成的。总共可以为您节省4个课程。
布朗

...,您应该检查是否可以用相同的方式简化您介绍的其他案例(可能不是全部)。
布朗

2

依赖于抽象,创建单一职责的类以及编写单元测试并不是一门精确的科学。学习时,朝一个方向摆动太远,达到极限,然后找到有意义的准则是完全正常的。听起来您的钟摆摆得太远了,甚至可能被卡住了。

这是我怀疑这将脱离轨道的地方:

单元测试对于团队来说是一笔难以置信的辛苦卖点,因为他们所有人都认为这是浪费时间,并且他们能够比单个代码更快地整体测试代码。使用单元测试作为SOLID的认可在大多数情况下是徒劳的,并且在这一点上已经成为一个笑话。

大多数SOLID原则带来的好处之一(肯定不是唯一的好处)是,它使为我们的代码编写单元测试变得更加容易。如果一个类依赖于抽象,我们可以模拟这些抽象。分离的抽象更容易模拟。如果一个类做一件事,它的复杂度可能会降低,这意味着更容易知道和测试所有可能的路径。

如果您的团队没有编写单元测试,那么将会发生两个相关的事情:

首先,他们正在做很多额外的工作来创建所有这些接口和类,而没有意识到全部的好处。花一点时间和实践才能看到编写单元测试如何使我们的生活更轻松。有一些原因使学习编写单元测试的人坚持使用它,但是您必须坚持足够长的时间才能自己发现它们。如果您的团队不尝试这样做,那么他们会觉得他们正在做的其他多余工作是没有用的。

例如,当他们需要重构时会发生什么?如果他们有一百个小类,但没有测试来告诉他们所做的更改是否有效,那么这些额外的类和接口将看起来像是一种负担,而不是改善。

其次,编写单元测试可以帮助您了解代码真正需要多少抽象。就像我说的,这不是一门科学。我们从糟糕的开始,到处都是转向,并且变得更好。单元测试具有补充SOLID的独特方式。您如何知道何时需要添加抽象或将某些东西分开?换句话说,您如何知道自己“足够扎实”?答案通常是什么时候您无法测试。

也许您的代码无需创建太多微小的抽象和类就可以进行测试。但是,如果您不编写测试,该如何判断?我们走多远?我们可能会变得越来越着迷于将事情分解得越来越小。这是一个兔子洞。为我们的代码编写测试的能力可以帮助我们了解何时完成我们的目标,这样我们就可以停止痴迷,继续前进,并在编写更多代码的过程中获得乐趣。

单元测试并不是解决所有问题的灵丹妙药,但是它们确实是一个很棒的子弹,可以使开发人员的生活更好。我们不是完美的,我们也不是测试。但是测试给了我们信心。我们希望我们的代码是正确的,如果出错了,我们会感到惊讶,而不是相反。我们不是完美的,我们的测试也不是。但是,当我们的代码经过测试时,我们就会充满信心。部署代码后,我们不太可能e舌,不知道这次会破坏什么,是否会成为我们的错。

最重要的是,一旦掌握了技巧,编写单元测试将使开发代码更快而不是更慢。我们花更少的时间重新审视旧代码或进行调试,以发现类似大海捞针的问题。

错误减少,我们可以完成更多工作,并且可以充满信心地代替焦虑。这不是时尚或蛇油。它是真实的。许多开发人员将证明这一点。如果您的团队没有经历过,那么他们需要努力克服学习过程中的困难。抓住机会,意识到他们不会立即获得结果。但是,一旦发生这种情况,他们会很高兴自己做到了,而且他们永远不会回头。(否则,他们将成为孤立的贱民,并撰写有关单元测试和大多数其他积累的编程知识如何浪费时间的愤怒博客文章。)

自从进行切换以来,开发人员最大的抱怨之一就是他们无法忍受同行审查和遍历数十个文件,而以前的每个任务只需要开发人员触摸5-10个文件即可。

当所有单元测试均通过时,同行评审会容易得多,而该评审的很大一部分只是确保测试有意义。

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.