在什么时候,YAGNI应该优先于良好的编码习惯,反之亦然?我正在研究一个正在工作的项目,并希望向我的同事缓慢介绍良好的代码标准(目前还没有,并且一切都只是毫无道理地混在一起),但是在创建了一系列类之后(不要执行TDD,或者根本不进行任何类型的单元测试。)我退后一步认为这违反了YAGNI,因为我非常确定地知道我们不需要扩展其中的某些类。 />
这是我的意思的一个具体示例:我有一个数据访问层,其中包装了一组存储过程,该过程使用具有基本CRUD功能的基本存储库样式模式。由于所有存储库类都需要一些方法,因此我为存储库创建了一个通用接口,称为IRepository。但是,然后我为每种类型的存储库(例如ICustomerRepository)创建了一个“标记”接口(即未添加任何新功能的接口),具体类实现了该接口。我已经通过Factory实现完成了同样的事情,以便从存储过程返回的DataReaders / DataSets中构建业务对象。我的存储库类的签名通常看起来像这样:

public class CustomerRepository : ICustomerRepository
{
    ICustomerFactory factory = null;

    public CustomerRepository() : this(new CustomerFactory() { }

    public CustomerRepository(ICustomerFactory factory) {
        this.factory = factory;
    }      

    public Customer Find(int customerID)
    {
        // data access stuff here
        return factory.Build(ds.Tables[0].Rows[0]);
    }
}


我在这里担心的是我违反了YAGNI,因为我以99%的确定性知道除了向该存储库提供具体内容外,从没有其他理由。因为我们没有单元测试,所以不需要CustomerFactory或类似的东西,并且拥有如此多的界面可能会使我的同事感到困惑。另一方面,使用工厂的具体实现方式似乎具有设计异味。

在适当的软件设计和不过度设计解决方案之间是否有折衷的好方法?我在问我是否需要所有的“单一实现接口”,或者是否可以牺牲一些好的设计,而仅拥有例如基本接口和单个具体的接口,而不用担心对该接口进行编程接口,如果实现将被使用。

评论

您说“因为我们没有单元测试,所以我不需要MockX”,自然会导致“我不需要IX,我只需要X”。我想说的是,您没有单元测试这一事实凸显了您需要IX和MockX的事实,因为这些东西将帮助您进行单元测试。不要接受没有测试的现实,将其视为暂时的问题,可以在(很长一段时间)内解决。

即使使用Google进行琐事,也应该有人提到YAGNI代表“您将不需要它”

我想如果您正在编写像这样的新类,则需要添加一些单元测试。即使您的同事不会经营他们。至少以后您可以说:“看!当您破坏我的代码时,我的单元测试抓住了它!看看多么棒的单元测试!”在这种情况下,使其可仿冒可能在这里值得。 (不过,如果可以在不定义接口的情况下模拟对象,我会更喜欢)

单元测试是否会迫使我创建(或使用)模拟框架,以免影响实时存储过程?这就是我倾向于不添加测试的主要原因-我们每个人都有一个生产数据库的本地副本,可以对其进行测试并针对其编写代码。

@Anthony嘲笑是否总能证明它带来的额外复杂性开销?模拟是一个很好的工具,但其实用性也必须相对于成本进行权衡,有时过多的间接间接会影响规模。当然,有工具可以帮助解决额外的复杂性,但它们并不能使复杂性消失。给定“不惜一切代价进行测试”的趋势似乎越来越大。我相信这是错误的。

#1 楼


是否有一个很好的方法可以在适当的软件设计与不过度设计解决方案之间做出折衷?


YAGNI。


我可以牺牲一些良好的设计


错误的假设。


和基础界面,然后是单个混凝土,


那不是“牺牲”。那是好的设计。

评论


达到完美,不是在没有其他可添加的东西时,而是在没有其他东西可取的时候。安托万德圣艾修伯里

– Newtopian
2011年9月16日14:23在

@Newtopian:人们对苹果的最新产品有不同的看法。 :)

–罗伊·廷克(Roy Tinker)
2011-09-16 17:17



我对这种答案有很大的疑问。 “ X是真实的,是因为Y说了,Y得到了社区的良好支持”吗?如果现实生活中有人反对YAGNI,您是否会向他展示这个答案作为论点?

–vemv
2011年9月17日在11:05

我的意思不是关于YAGNI,而是关于答案质量。我并不是说有人特别在抱怨,这是我推理的一部分。请再次阅读。

–vemv
2011-09-17 18:55

同意vemv,这是一个糟糕的答案,没有任何实际信息。 69位支持者应该感到be愧。就像体育博客“ Go YAGNI,他们都在摇滚!”中的评论一样。这不是一个很好的答案。

–user949300
14-10-2在21:09



#2 楼

在大多数情况下,避免使用不需要的代码可以带来更好的设计。最易于维护和面向未来的设计是使用最少数量的满足要求的命名简单的代码。

最简单的设计是最容易演变的。没有什么比无用的,过度设计的抽象层杀死了可维护性。

评论


我发现“避免使用YAGNI代码”非常含糊。这可能意味着您不需要的代码或符合YAGNI原则的代码。 (参见“ KISS代码”)

–sehe
2011年9月16日下午16:52

@sehe:一点都不歧义(尽管“坚持YAGNI原则”是完全自相矛盾的),如果您将其说明清楚:“您不会需要它的代码”可能意味着什么,但可以编写你不需要吗?

–迈克尔·伯格沃德(Michael Borgwardt)
2011-09-16 22:19



我将以大字体打印此答案,并将其挂在所有宇航员都可以阅读的位置。 +1!

–kirk.burleson
2011-09-16 22:20

+1。我仍然记得我的第一个公司项目,该项目用于支持和添加一些新功能。我要做的第一件事是从40,000行程序中删除了32,000行无用的代码,而不会丢失任何功能。此后不久,最初的程序员被解雇了。

– E.J.布伦南
2011年9月17日下午13:28

@vemv:将“无用”读为“当前不被使用,只有很少使用”,即YAGNI的情况。 “过度设计”比“糟糕”要具体得多。具体来说,它意味着“由理论概念或想象的可能要求引起的复杂性,而不是具体的当前要求”。

–迈克尔·伯格沃德(Michael Borgwardt)
2011-09-17 19:16

#3 楼

YAGNI和SOLID(或任何其他设计方法)不是互相排斥的。但是,它们是相反的极性。您不必都坚持100%,但是会有一些让步。您越是在一个位置上看到一个班级使用的高度抽象的模式,并说出YAGNI并将其简化,则设计变得越不牢固。反之亦然。在开发中很多次,设计都是“按信念”实现的。您看不到将如何使用它,但是您只有预感。这可能是正确的(并且您获得的经验越多,就越有可能成为现实),但它也可能使您承担像轻率的“轻而易举”的方法一样多的技术负担。而不是DIL“意大利面条代码”代码库,您可能最终会获得“果仁蜜饼代码”,因为其层数如此之多,以至于简单地添加方法或新数据字段就变成了耗费几天时间遍历服务代理和松散耦合的依赖项的过程。只有一个实现。否则,您可能最终会遇到“ spaghetti-Os代码”,它以如此小巧,结构松散的形式出现,使得体系结构中的上,下,左或右移动可带您通过50条方法,每条3行。 br />我已经在其他答案中说过,但这是:在第一遍,使其生效。在第二遍,使其优雅。在第三遍,将其设置为SOLID。在这一点上,就您所知,这是一次性的。因此,构建“象牙塔”架构以添加2和2并没有任何风格点。做您必须做的,并假设您再也看不到它了。

下次您的光标进入该代码行时,您现在就不再像首次编写那样就驳斥了这一假设。您正在重新访问该代码,可能会将其扩展或在其他地方使用它,因此它不是一次性的。现在,应该实现一些基本原理,例如DRY(不再重复)和其他一些简单的代码设计规则。为重复的代码提取方法和/或形成循环,为常见的文字或表达式提取变量,可能添加一些注释,但是总体上您的代码应自行记录。现在,您的代码井井有条,甚至可能是紧密耦合的,其他任何查看它的人都可以通过阅读代码轻松地了解您在做什么,而不是逐行跟踪。

第三次您的光标输入该代码时,可能会很麻烦;您要么再次扩展它,要么在代码库中的至少其他三个不同地方变得有用。在这一点上,它是系统的关键(如果不是核心的话)元素,因此应如此设计。在这一点上,您通常还了解到目前为止如何使用它,这将使您可以就如何设计设计以简化这些用法和任何新用法做出良好的设计决策。现在,SOLID规则应输入方程式;提取包含特定目的代码的类,为具有相似目的或功能的任何类定义通用接口,在类之间建立松耦合的依赖关系,并设计依赖关系,以便您可以轻松地添加,删除或交换它们。

从这一点开始,如果您需要进一步扩展,重新实现或重用此代码,则将它们很好地打包并以我们都知道和喜欢的“黑匣子”格式抽象;将其插入您需要的其他位置,或在主题上添加新的变体作为该接口的新实现,而不必更改该接口的用法。

评论


我第二棒。

–菲利普·杜帕诺维奇(FilipDupanović)
2011-09-18 10:05

是。当我以前进行重用/扩展设计时,我发现当我想重用或扩展它时,它将以与预期不同的方式进行。预测是困难的,尤其是关于未来。因此,我支持您的三击规则-到那时,您对如何重用/扩展它有了一个合理的认识。注意:如果您已经知道会如何(例如,来自以前的项目,领域知识或已经指定),则是一个例外。

– 13ren
2011-09-19 23:15



在第四遍,使其超赞。

–理查德·尼尔·伊拉根(Richard Neil Ilagan)
2011年9月20日20:31在

@KeithS:约翰·卡马克(John Carmack)做了类似的事情:“ Quake II的源代码……将Quake 1,Quake World和QuakeGL统一为一个漂亮的代码体系结构。” fabiensanglard.net/quake2/index.php

– 13ren
2011-09-21 3:13



+1,我想将您的规则命名为:“实用重构” :)

– Songo
2012年11月20日18:01

#4 楼

我更喜欢WTSTWCDTUAWCROT而不是这两种方法?

(我们能做的最简单的事情是什么有用,我们可以在星期四发布?)要做的事情,但不是优先事项。

评论


该首字母缩写词违反了YAGNI原则:)

–riwalk
2011年9月16日16:06

我恰好用了我需要的字母-越来越多。这样,我有点像莫扎特。是的我是首字母缩写的莫扎特。

–迈克·谢里尔(Mike Sherrill)的“猫召回”
2011-09-20 11:54

我从来不知道莫扎特在缩写时会感到可怕。我每天都在SE网站上学到一些新东西。 :P

–卡梅伦·麦克法兰
2011年10月7日在9:14

@Mike Sherrill'CatRecall':也许应该将其扩展到WTSTWCDTUWCROTAWBOF =“最简单的方法是有用的,我们可以在周四发布,而在周五不会休息?” ;-)

–乔治
2015年1月6日9:25



@ Stargazer712否:)违反POLA。

–v.oddou
16年6月30日在7:10

#5 楼

YAGNI和良好的设计没有冲突。 YAGNI即将(不)支持未来的需求。好的设计是要透明化您的软件当前所执行的操作以及其执行方式。

引入工厂会使您现有的代码更简单吗?如果没有,请不要添加它。如果可以,例如在添加测试时(应该做!),就添加它。复杂性,同时仍支持所有当前功能。

#6 楼

它们没有冲突,您的目标是错误的。

您要完成什么?

您要编写高质量的软件,并希望做到这一点您的代码库很小,没有问题。

现在我们陷入了冲突,如果我们不编写不打算使用的案例,那么如何掩盖所有案例?

这是您的问题所在。

(任何有兴趣的人,这被称为蒸发云)

那么,这是什么引起的? >
您不知道不需要什么
您不想浪费时间和膨胀代码

我们可以解决其中哪一项?好吧,看起来不想浪费时间和膨胀的代码是一个伟大的目标,而且很有意义。那第一个呢?我们能找出我们需要编写什么代码吗?没有任何东西,而且一切都只是在没有韵律或理由的情况下被黑了)[...]我退后一步,认为这违反了YAGNI,因为我非常确定地知道我们不需要扩展某些这些类。


让我们重新措辞所有这些


没有代码标准
没有正在进行的项目计划
哇,到处都是牛仔在做自己该死的事情(而你正试图在荒野的西部扮演警长)。软件设计,而不是过度设计解决方案?


您不需要妥协,您需要有人来管理有能力且对整个项目有远见的团队。您需要一个可以计划您需要的人,而不是每个人都投入您不需要的东西,因为您对未来如此不确定,因为……为什么?我告诉你为什么,这是因为在你们所有人中间没有人制定该死的计划。您正在尝试引入代码标准来解决一个完全独立的问题。您需要解决的主要问题是明确的路线图和项目。一旦有了这些,您就可以说“代码标准可以帮助我们更有效地实现团队目标”,这是绝对的事实,但不在此问题的范围之内。

获取项目/团队经理谁可以做这些事情。如果您有一个,则需要问他们一张地图,并解释YAGNI问题,即没有地图。如果他们严重不称职,请自己编写计划并说:“这是我为您提供的关于我们需要的报告,请仔细阅读并告知我们您的决定。”

评论


不幸的是,我们没有一个。开发经理要么更关心快速完成工作,要么不关心标准/质量,要么制定标准做法,例如在从类直接返回的所有地方使用原始数据集,VB6样式编码以及所有代码中的所有内容,并复制和粘贴所有重复逻辑过度。

–韦恩·莫利纳(Wayne Molina)
2011-09-16 14:56

很好,它会变慢一些,但是那时候您需要对他们的担心说话。解释这个YAGNI /无用代码问题是在浪费时间,建议路线图,给他一个路线图,并说明它将使工作更快。当他被收购时,就会遇到标准差对发展速度产生的问题,建议更好的问题。他们想完成项目,他们只是不知道该如何管理,您要么需要替换他,要么给他一个孩子……或者辞职。

–隐身
2011-09-16 14:59

嗯.....我的主要目标是“您想拥有一个好的,有价值的产品”?

–sehe
2011年9月16日下午16:56

@sehe那不是他的决定:p。

–隐身
2011-09-16 17:59

好答案。他正面临一场艰苦的战斗。如果没有管理层的支持,将很难完成它。您说对了,这个问题为时过早,不能解决真正的问题,这是正确的。

–塞斯·斯皮尔曼(Seth Spearman)
2011-09-17 15:32

#7 楼

YAGNI绝不会涉及允许使用单元测试扩展代码,因为您将需要它。但是,我不认为您对单一实现接口的设计更改实际上会提高代码的可测试性,因为CustomerFactory已经从接口继承,并且可以随时替换为MockCustomerFactory。

评论


+1是有关实际问题的有用评论,而不仅仅是关于Good Design和YAGNI之间的宇宙之战和/或相爱。

–psr
2011年9月16日在18:07

#8 楼

这个问题提出了一个错误的困境。正确应用YAGNI原则并非无关紧要。这是良好设计的一方面。 SOLID的每个原则也都是良好设计的方面。您不能总是在任何学科中都完全应用所有原则。现实中的问题对您的代码施加了很大的力量,其中一些力量朝相反的方向发展。设计原理必须解决所有这些问题,但是没有几个原理可以适合所有情况。

现在,让我们看一下每个原理,并理解它们有时可能会朝不同的方向发展,它们决不是天生就存在冲突。

YAGNI旨在帮助开发人员避免一种特殊的返工:这种返工是由错误的东西引起的。它通过指导我们避免基于对我们认为将来会发生变化或需要的假设或预测做出过早的错误决策来做到这一点。集体经验告诉我们,当我们这样做时,我们通常是错误的。例如,除非您立即知道需要多个实现者,否则YAGNI会告诉您不要为可重用性创建接口。同样,YAGNI会说不要创建“ ScreenManager”来管理应用程序中的单个表单,除非您现在知道将要拥有多个屏幕。

与许多人的看法相反,SOLID与可重用性,通用性甚至抽象无关。 SOLID旨在帮助您编写为更改做好准备的代码,而无需说明任何具体更改。 SOLID的五项原则创建了一种构建代码的策略,该策略既灵活又不会过于泛泛,而又简单又不会太幼稚。正确应用SOLID代码会产生具有明确定义的角色和边界的小型,专注的类。实际结果是,对于任何所需的需求更改,都需要触摸最少数量的类。同样,对于任何代码更改,到其他类的“涟漪图”的数量最少。

看看您遇到的示例情况,让我们看看YAGNI和SOLID可能要说些什么。由于所有存储库从外部看起来都相同,因此您正在考虑使用通用的存储库接口。但是,通用接口的价值在于可以使用任何实现程序,而无需知道具体是哪个接口。除非您的应用程序中有某个地方是必要或有用的,否则YAGNI表示不要这样做。

有5条SOLID原则需要考虑。 S是单一责任。这没有说明接口,但是可能说明了具体的类。可以争辩说,最好将数据访问本身作为处理一个或多个其他类的责任,而存储库的职责是从隐式上下文(CustomerRepository是对Customer实体隐式存储库)转换为对指定客户实体类型的通用数据访问API。

O是开闭的。这主要是关于继承。如果您尝试从实现通用功能的通用库中获取存储库,或者希望从其他存储库中进一步获取存储库,则适用此方法。但是您不是,所以不是。

L是Liskov可替换性。如果您打算通过公共存储库界面使用存储库,则适用此规则。它对接口和实现施加了限制,以确保一致性并避免对不同实现者进行特殊处理。原因是这种特殊处理破坏了接口的目的。考虑此原则可能很有用,因为它可能会警告您不要使用公共存储库界面。这与YAGNI的指导相吻合。

我是接口隔离。如果您开始向存储库添加其他查询操作,则可能适用。接口隔离适用于您可以将类的成员分为两个子集,其中一个将由某些使用者使用,另一个由其他使用者使用,但是没有使用者可能会同时使用两个子集的情况。指南是创建两个单独的接口,而不是一个通用的接口。在您的情况下,获取和保存单个实例的可能性不太可能被用于一般查询的同一代码占用,因此将它们分成两个接口可能会很有用。

D是依赖注入。在这里,我们回到与S相同的地方。如果您将对数据访问API的使用划分为一个单独的对象,则此原则表示,在创建对象时应将其传递给它,而不是仅更新该对象的实例。存储库。这样可以更轻松地控制数据访问组件的生存期,从而可以在存储库之间共享对它的引用,而不必走单例之路。需要注意的是,大多数SOLID原则不一定适用于应用程序开发的特定阶段。例如,是否应该中断数据访问取决于它的复杂程度,以及是否要在不访问数据库的情况下测试存储库逻辑。听起来这不太可能(不幸的是,在我看来),所以它可能没有必要。

因此,经过所有考虑,我们发现YAGNI和SOLID实际上确实可以立即提供一个通用的实体-相关建议:可能不必创建通用的通用存储库接口。

所有这些仔细的考虑对于学习都是非常有用的。随着学习的进行,这非常耗时,但是随着时间的推移,您会发​​展直觉并变得非常快。您会知道做对的事情,但是除非有人要求您解释原因,否则您无需考虑所有这些单词。

评论


我认为本页中的大多数讨论都忽略了GRASP原理的两个主要竞争者“低耦合”和“高凝聚力”。昂贵的设计决策源于“低耦合”原理。就像何时出于低耦合的目的“激活” SRP + ISP + DIP。示例:在MVC模式中,一类-> 3类。甚至更昂贵:拆分为.dll / .so模块/程序集。由于构建的影响,项目,makelist,构建服务器,源代码版本的文件的添加,这非常昂贵。

–v.oddou
16年6月30日在7:22

#9 楼

您似乎相信,“好的设计”意味着遵循某种思想和规则集,即使无用的情况也必须始终遵循。

IMO这是糟糕的设计。 YAGNI是优良设计的组成部分,从不与它矛盾。

#10 楼

在您的示例中,我想说YAGNI应该占上风。如果您以后需要添加接口,则不会花费那么多钱。顺便说一句,如果根本没有任何目标,按类提供一个接口真的是一种好的设计吗?以下是有关该主题的非常有趣的帖子序列:


足够的设计
足够的设计意味着好的设计
当足够的设计意味着差的设计


评论


您的第一个和第三个链接将转到同一位置。

– David Thornley
2011年9月16日14:18在

#11 楼

有人认为,接口名称不应以I开头。具体原因之一是,实际上是在泄漏对给定类型是类还是接口的依赖性。

是什么阻止了您首先成为CustomerFactory的类,然后又将其更改为将由DefaultCustormerFactoryUberMegaHappyCustomerPowerFactory3000实现的接口?您唯一需要更改的就是实现实例化的地方。而且,如果您的设计不太理想,那么最多就是几个地方。

重构是开发的一部分。与为每个单个类声明一个接口和一个类相比,最好使代码少一点,易于重构,这迫使您同时至少在两个位置更改每个方法名。

使用接口的真正目的是实现模块化,这可能是良好设计的最重要支柱。但是请注意,模块不仅由其与外界的去耦定义(即使这是我们从外部角度来看的方式),而且同样由其内部工作定义。
我要指出的是,去耦的事物,本质上属于在一起的事物,没有多大意义。从某种意义上来说,这就像一个橱柜,每个杯子都有一个单独的架子。

重要性在于将一个大而复杂的问题设计成更小,更简单的子问题。而且,您必须停下来,在它们变得足够简单而无需进一步细分的情况下,否则它们实际上将会变得更加复杂。这可以看作是YAGNI的必然结果。这绝对意味着好的设计。

目标不是要以某种方式通过单个存储库和一个工厂来解决本地问题。目的是,此决定对您的其余应用程序不起作用。这就是模块化的意义。
您希望您的同事看着您的模块,看到带有几个不言自明的调用的外观,并感到自信,他们可以使用它们,而不必担心所有潜在的复杂内部管道。

#12 楼

您将创建一个interface,以期将来实现多种实现。您可能还对代码库中的每个类都有一个I<class>。不要。

只使用根据YAGNI的单个具体类。如果发现需要用于测试的“模拟”对象,请将原始类转换为具有两个实现的抽象类,一个具有原始的具体类,另一个具有模拟的实现。

显然,您将不得不更新原始类的所有实例化以实例化新的具体类。您可以通过在前面使用静态构造函数来解决此问题。

YAGNI表示在需要编写代码之前不要编写代码。 br />
可以同时拥有。类是抽象。

#13 楼

为什么标记界面?令我惊讶的是,它所做的不过是“标记”。每个工厂类型都有不同的“标签”,这有什么用?说话。因此,如果您所有具体的存储库类型的行为都像相同的IRepository(它们都实现了IRepository),那么它们可以由其他代码以完全相同的代码以相同的方式处理。此时,您的设计是可扩展的。添加更多具体的存储库类型,所有类型都作为通用IRepository处理-相同的代码处理所有具体类型,与“通用”存储库相同。

接口用于基于通用性处理事物。但是自定义标记界面a)不添加任何行为。 b)强迫您处理它们的唯一性。

就您设计有用的接口而言,您将获得OO的好处,即无需编写专门的代码来处理具有以下特征的专用类,类型或自定义标记接口与具体类别的比例为1:1。那是毫无意义的冗余。

例如,如果您需要包含许多不同类的强类型集合,则可以看到标记接口。在集合中,它们都是“ ImarkerInterface”,但是当您将它们拉出时,必须将它们转换为适当的类型。

#14 楼

您现在可以写下一个合理的ICustomerRepository吗?例如,(过去确实到达这里,可能是一个不好的例子)您的客户过去一直使用PayPal吗?还是公司中的每个人都对与阿里巴巴建立联系充满了敬意?如果是这样,您可能现在想使用更复杂的设计,并且对老板来说是有远见的。 :-)

否则,请稍等。在拥有一个或两个实际实现之前,在接口上进行猜测通常会失败。换句话说,除非您有几个示例可以概括,否则不要概括/抽象/使用奇特的设计模式。