假设我有一个具有“类型”属性的实体。可能有20多种可能的类型。

现在,我被要求实现一些允许从A-> B更改类型的东西,这是唯一的用例。

那么我应该实现一些允许任意改变类型的东西,只要它们是有效类型吗?
或者我应该只允许它根据要求从A-> B改变而拒绝任何更改其他类型的更改,例如B-> A或A-> C?

我可以从双方看到正反两面的优势,如果出现类似的要求,通用解决方案将减少工作量未来,但这也意味着出错的机会更大(尽管此时我们100%控制了调用者)。
一种特定的解决方案不太容易出错,但是如果出现类似的要求,将来需要做更多的工作。

我一直在听说,优秀的开发人员应该尝试预期更改并设计系统,以便将来可以轻松扩展,这听起来像是通用解决方案? />
编辑:

在我不太具体的示例中添加更多细节:
在这种情况下,“通用”解决方案比“特定”解决方案所需的工作更少,作为标本fic解决方案要求对旧类型和新类型都进行验证,而通用解决方案仅需要对新类型进行验证。

评论

这是一个有趣的问题。我已经和我的gramps(一个非常非常老的计时器程序员)讨论了类似的问题,他的回答是“解决您的特定问题的最通用的东西”,最终归结为“这取决于”。软件开发中的一揽子声明很少起作用-始终应视情况而定。

@ T.Sar将此添加为答案,我会投票:-)

您认为什么是“通用解决方案”?另外,请澄清“仅用例”的含义。您的意思是仅允许从A-> B进行转换,还是仅指定该转换,并且从任何其他状态转换为任何其他状态都不是错误条件。作为开发人员,您需要请用例的作者进行澄清。如果不允许其他过渡,并且无论谁控制调用者,您的代码都允许,则您的代码无法满足要求。

如果X是一个好主意,那么始终只有X必须是最优的原则,似乎对开发人员(或其中至少有一个声音团队)具有特别的吸引力,但请考虑以下几点:编程的经验法则是像谚语一样,因为您经常会发现一对含义相反。这表明您应该运用自己的判断力(如此处答案所倡导的那样),并提防教条。

过早的泛化会导致与过早的优化一样多的胃灼热-您最终编写了大量可能永远不会使用的代码。首先解决特定问题,然后根据需要进行概括。

#1 楼

我的经验法则:


第一次遇到问题,只能解决特定的问题(这是YAGNI原理)
第二次遇到相同的问题,请考虑对第一种情况进行一般化,如果工作量不大
,一旦您有三种可以使用一般化版本的特殊情况,那么您应该开始真正地计划一般化版本-到目前为止,您应该对问题有足够的了解,以便能够将其概括起来。

当然,这只是一个准则,而不是一成不变的规则:真正的答案是运用您的最佳判断,根据具体情况。

评论


您能解释一下YAGNI是什么吗?

–伯恩哈德
17年1月1日下午5:09

@Bernhard你不需要它。一个非常有用的软件设计原则。

–Angew不再以SO为荣
17年12月1日在8:15

“在thing1,thing2,考虑使用一个数组。在thing1,thing2,thing3,几乎可以肯定地使用thing []数组。”是决定多个变量还是单个数组的相似经验法则。

– Joker_vD
17年12月1日在23:19

陈述规则1的另一种方式是:“你不能一概而论。”

–韦恩·康拉德
17/12/4在20:52

@SantiBailors:正如布朗博士在回答中所说,我们经常高估通过建立第一个扔掉的东西可能浪费的精力。弗雷德·布鲁克斯(Fred Brooks)在《神话人月》中说:“计划将它扔掉-无论如何,你会的。”就是说:如果您立即遇到某件事的多个用途(例如,通过交付一组需求,而您显然需要多次解决同一问题),那么您已经有多个案例可以概括从,这是完全可以的,并且与我的回答没有冲突。

–丹尼尔·普赖登(Daniel Pryden)
17-12-5在13:12



#2 楼


如果需要类似的要求,则特定的解决方案[...]将来需要做更多的工作


我已经听过数十次,并且-根据我的经验-经常证明是谬论。如果现在或以后归纳一下,当第二个类似的要求出现时,总的工作量几乎是相同的。因此,当您不知道这种努力是否会奏效时,绝对没有必要花更多的精力进行概括。

(很明显,当更通用的解决方案比特定解决方案更简单,所需的工作更少时,这将不适用,但以我的经验来看,这些情况很少见。这种情况后来被编辑为问题,而不是我的答案。)

当“第二个相似的情况”出现时,就该开始考虑概括了。正确地归纳起来会容易得多,因为第二个要求为您提供了一个场景,您可以在其中验证是否使正确的事物成为通用。当试图只概括一种情况时,您就是在黑暗中射击。您很有可能过度概括了某些不需要概括的内容,而错过了其他应该概括的部分。然后当第二种情况出现时,您意识到自己概括了错误的内容,那么您需要做更多的工作来解决此问题。

因此,我建议延迟做事的诱惑,以防万一。 ”。如果您错过了概括三,四次或更多次的机会,而又需要维护一堆看起来相似(因此重复)的代码,那么这种方法只会导致更多的工作和维护工作。

评论


对我来说,这个答案在OP的背景下是没有意义的。他是说他现在将花费较少的时间进行泛化,而在未来将花费较少的时间,除非将来需要具体实现。您反对“在概括上投入更多的精力”,而OP仅在他们没有概括时才会投入更多的精力。 (认为​​)....很奇怪:$

–msb
17年11月30日在22:14

@msb:该上下文是在我写完答案后添加的,这与OP之前所说的相反,特别是在我引用的部分中。看到我的编辑。

–布朗博士
17年11月30日在22:25



而且,广义的解决方案将更加复杂,因此,当您不得不将其适应于第二种情况时,将需要更长的时间才能再次理解它。

– AndreKR
17年1月1日在9:36

Doc不在话下,但永远不会以为你是非母语人士。您的答案总是写得很好,论据也很好。

–user949300
17年12月1日23:27



当我阅读您未来的答案时,我会想像您的口音。 :-)

–user949300
17年2月2日在2:36

#3 楼

TL; DR:这取决于您要解决的问题。

我和Gramps进行了类似的交谈,而我们在谈论C#中的Func和Action多么出色。 My Gramps是一位非常古老的计时器程序员,因为软件在占用整个房间的计算机上运行,​​所以它一直围绕源代码编写。

他一生中多次改变了技术。他用C,COBOL,Pascal,BASIC,Fortran,Smalltalk,Java编写代码,并最终以业余爱好开始C#。我学习了如何与他一起编程,那时我还只是个小家伙,就坐在他的膝盖上,在IBM SideKick的蓝色编辑器上剔除了我的第一行代码。到20岁时,我已经花了更多的时间在编码上,而不是在外面玩。

这些是我的一些回忆,请原谅我,如果我在转述它们时不太实际。我有点喜欢那些时刻。

他对我说的是:


“我们应该推广问题,还是在特定范围内解决问题,您问吗?好吧,这是一个问题。”

Gramps停了一下,想了一会儿,同时将眼镜的位置固定在脸上。他正在电脑上玩三消游戏,同时在旧音响系统上听着Deep Purple的LP。

“好吧,这取决于您要解决的问题”,他告诉我。 “令人信服的是,存在针对所有设计选择的单一,神圣的解决方案,但没有一个。您看到的软件体系结构就像奶酪。”

” ...奶酪,Gramps ?“

”不管您如何看待自己喜欢的东西,总会有人认为它很臭。

我困惑地眨了眨眼,但是在我没能说出任何声音的情况下。
“当你在制造汽车时,如何挑选材料呢?一个零件?”

“我...我想这取决于所涉及的成本以及该零件应该做什么。”

“这取决于零件要解决的问题。您不会制造钢制轮胎或皮革制挡风玻璃。您会选择最能解决当前问题的材料。现在,什么是通用解决方案还是特定解决方案?针对什么问题,针对何种用例?是否应该采用完整的功能方法,以使仅使用一次的代码具有最大的灵活性?您应该编写非常专业,易碎的代码来解决问题吗?您的系统中会有很多用途,甚至可能会有很多变化的部分吗?这样的设计选择就像您为汽车零件选择的材料或您用来建造小房子的Lego砖的形状一样。哪种乐高积木是最好的呢?“

年长的程序员在继续学习之前已经达到了他桌上摆着的一个小乐高火车模型。

”你只能回答如果您知道自己需要什么,那该怎么办呢?反之亦然,如果您甚至不知道要解决什么问题?您看不到过去无法理解的选择。“

” ..您只是引用《黑客帝国》吗?“

”什么?“

“没事,继续。”

“好吧,假设您正在尝试为国家发票系统构建一些东西。您会从内部知道该地狱的API及其三万行XML文件的样子。用于创建该文件的“通用”解决方案看起来将如何?该文件充满了可选参数,充满了只有非常特殊的业务部门才应该使用的情况。在大多数情况下,您可以放心地忽略它们。如果您只需要创建通用的发票系统,永远不会卖出鞋子。只要建立一个销售鞋子的系统,使其成为目前最好的销售鞋子的发票系统即可。现在,如果您必须为任何类型的客户创建一个发票系统,那么在更广泛的应用上-可以作为一个独立的通用销售系统转售-现在,有趣的是实施那些仅用于汽油,食品或酒精的选项。现在这些是可能的用例。在某些假设之前,请勿使用案例,而您不想实施不要案例。不要使用是不需要的小兄弟。“

抽奖活动将乐高火车放回原处,回到了他的三消游戏。

“因此,为了能够为给定问题选择通用或特定的解决方案,您首先需要了解该问题到底是什么。否则,您只是在猜测,而猜测是经理而不是程序员的工作。



因此,这一切都取决于IT。”因此,您就拥有了“取决于”。在考虑软件设计时,这可能是最强大的两字表达。 br />

评论


我敢肯定那个故事中有一些有用的东西,但是读起来太痛苦了,对不起

– GoatInTheMachine
17年12月1日上午10:47

您的第一篇文章是一篇有趣的短篇小说-当然,这是见仁见智的-但是,除非我真的喜欢某人的写作风格,否则我会发现这样的长篇故事确实很让人放纵,并且在个人博客之外有点不合适

– GoatInTheMachine
17年12月1日在11:05

@ T.Sar不要听那些讨厌的人的话,您的帖子是来自上师的有用建议的瑰宝。 +1

–LLlAMnYP
17年12月1日在12:27

“软件架构就像奶酪”这一部分真是太棒了。确实,这个答案是一颗宝石!

–马修·金登(Mathieu Guindon)
17年12月1日在13:10

也许这个答案也像奶酪吗? ;)但是,“ SmallTalk”应为“ Smalltalk”,是的,我是那个人,对不起。

– fede。
17年12月1日在14:30

#4 楼

首先,您应该尝试预期是否会发生这样的更改-不仅仅是线下的可能性很小。

如果没有,通常最好现在选择简单的解决方案,然后再进行扩展。这样一来,您很有可能对需要的东西有了更清晰的了解。

#5 楼

如果您使用的是您不熟悉的领域,那么绝对应该应用Daniel Pryden提到的三项规则。毕竟,如果您是该领域的新手,应该如何构建有用的抽象?人们很少能把握抽象的能力,尽管这种情况很少发生。以我的经验,过早的抽象至少比代码复制有害。错误的抽象确实让人很难理解。有时甚至更难以重构。

有一本书解决了我关于开发人员正在从事的未知领域的观点。它由特定领域组成,并提取了有用的抽象。

评论


问题是关于一个具体问题的。

– RibaldEddie
17年11月30日在19:28

答案足以解决这个具体问题。这个问题不够具体,无法给出具体的收据:如您所见,问题中没有提及任何域详细信息。甚至没有指定类名(A和B不计算在内)。

–Vadim Samokhin
17年11月30日在19:35

“ stackoverflow答案应该尽可能通用或尽可能具体吗?”

–林登·怀特(Lyndon White)
17年12月1日在1:59



@LyndonWhite一个stackoverflow问题应该尽可能通用(太宽泛!)还是尽可能具体(无论该失败称为什么!)?哈。

–user251748
17年12月1日在16:51



#6 楼

考虑到您所问问题的本质,假设我理解正确,我实际上将其视为中央系统功能的设计问题,而不是有关通用解决方案和特定解决方案的问题。

关于中央系统的功能,最可靠的是那些不存在的功能。在极简主义方面犯错是值得的,尤其是考虑到通常,长期以来,集中地添加功能要比删除长期以来不希望的,具有大量依赖项的有问题的功能要容易得多,因为这样做使系统工作变得更加困难

实际上,由于缺乏对将来是否会经常使用它的强烈预期,因此我将避免将其视为如果可能,将类型从A替换为B,而只是寻求它作为转换A状态的一种方式。例如,在A中设置一些字段以使其变形并像B一样显示给用户,而无需实际更改为其他“类型”的东西-当A处于状态时,可以使用B中的合成和调用函数来使A私下存储B设置为表示应模仿B以简化实现。那应该是一个非常简单且侵入性最小的解决方案。

所以无论如何,与其他很多观点相呼应,我建议在这种情况下避免使用通用解决方案,但更多的是因为我认为考虑到在中央系统中添加一个非常大胆的功能,因此我建议您将它遗漏在一边,尤其是现在。

评论


“您会错过所有未编写的错误。” (摘自篮球海报:您会错过所有未拍摄的镜头)

–user251748
17年12月1日在16:53

#7 楼

对于这个特定问题,很难给出一个通用的答案;-)

它越通用,您就获得了更多的时间来应对未来的变化。例如,由于这个原因,许多游戏程序都使用实体组件模式,而不是为游戏中的角色和对象构建非常复杂但僵化的类型系统。

另一方面,制作通用产品需要在设计上进行前期的时间和精力投入,这要比非常特定的产品要高得多。这承担了过度工程的风险,甚至可能因未来的潜在需求而迷失。

总是值得一看的是,有一种自然的概括可以使您取得突破。但是,最后,您现在可以花费的精力与将来可能需要的努力之间是一个平衡的问题。

#8 楼



混合。这不必是一个/或一个问题。您可以为一般类型转换设计API,同时仅实现您现在需要的特定转换。 (只需确保如果有人通过不支持的转换调用您的常规API,它就会失败并显示“不支持”错误状态。)


测试。对于A-> B转换,我将不得不编写一个(或少量)测试。对于一般的x-> y转换,我可能不得不编写一个完整的测试矩阵。即使所有转换共享一个通用实现,这实际上也要花更多的功夫。
另一方面,如果找到了测试所有可能转换的通用方法,则没有更多的工作要做,并且我可能会倾向于尽快使用通用解决方案。


耦合。从A到B的转换器可能需要了解有关A和B的实现细节(紧密耦合)。如果A和B仍在发展,这意味着我可能不得不继续回顾很麻烦的转换器(及其测试),但至少它仅限于A和B。
如果我放弃了通用解决方案需要访问所有类型的详细信息,然后即使C和D不断发展,我可能仍要不断调整通用转换器(和大量测试),即使没有人需要转换为C,这也会使我慢下来还是D。
如果泛型转换和特定转换都可以仅与类型的详细信息松散耦合地实现,那么我就不必为此担心。如果其中一种可以通过松散耦合的方式完成,而另一种需要紧密耦合,那么对于松散耦合的方法而言,这是一个强有力的论据。



评论


测试是一个好点。如果您基于类别理论中的概念设计通用解决方案,那将是一个很大的优势,因为您经常会得到免费的定理,这些定理证明签名的唯一可能实现(编译器的类型检查器接受)是正确的,或者至少是正确的。如果算法适用于任何特定类型,则它也必须适用于所有其他类型。

–leftaround关于
17年11月30日23:13



我猜有一个特定于域的原因为什么只要求一种类型转换,这意味着大多数类型转换在问题域中都是无效操作,因此,应用程序不应该支持它们,除非它们被正式允许。这个答案最接近那个论点。

–拉尔夫·克莱伯霍夫(Ralf Kleberhoff)
17年1月1日在10:25

#9 楼


解决方案应该尽可能通用或尽可能具体吗?


这不是一个可以回答的问题。

您可以合理地获得最好的解决方案是一些试探法来决定制定给定解决方案的一般性或特定性。通过下面的过程,通常一阶近似是正确的(或足够好)。如果不是,则原因很可能是特定于域的,因此无法在此处详细介绍。



一阶近似:通常的YAGNI规则这是Daniel Pryden,Doc Brown等人所述的三个方法。

这是一种通用的启发式方法,因为它可能是您不依赖域和其他变量的最佳方法。 >
因此,最初的假设是:我们做最具体的事情。


二阶近似:根据您对解决方案领域的专业知识,您说


在这种情况下,“通用”解决方案比“特定”解决方案所需的工作更少


,因此我们可能会重新解释YAGNI,因为建议我们避免不必要的工作,而不是避免不必要的普遍性。因此,我们可能会修改最初的假设,而做最简单的事情。

但是,如果您的解决方案领域知识表明最简单的解决方案很可能会打开很多错误,或者很难进行充分测试,或引起任何其他问题,那么更容易编写代码并不一定是更改我们原来选择的充分理由。


三阶近似:您的问题领域知识是否表明最简单的解决方案实际上是正确的,还是您允许进行许多您认为毫无意义或错误的转换?

如果简单但通用的解决方案看起来有问题,或者您对自己的能力不自信判断这些风险,做一些额外的工作,并保持最初的猜测可能会更好。

四阶近似:您是否了解客户的行为,或者该功能与他人的关系,项目管理的优先级,或者...其他任何非严格的技术考虑因素都会影响您当前的工作决策?


#10 楼

用简单的答案回答这个问题并不容易。许多答案都给试探法建立了3或类似的规则。超越这样的经验法则是困难的。

要真正回答您的问题,您必须考虑到您的工作很可能不会实施会改变A-> B的事情。如果您是承包商,也许这是必要条件,但是如果您是员工,则被雇用来为公司执行许多其他较小的任务。更改A-> B只是这些任务之一。您的公司将在意将来的更改效果如何,即使请求中未注明。要找到“ TheBestImplementation(tm)”,您必须查看真正被要求执行的操作的放大图,然后使用它来解释您被要求更改A-> B的小请求。 br />
如果您是刚从大学毕业的低级入门程序员,通常建议完全按照要求做。如果您被聘为有15年经验的软件架构师,通常建议您考虑一下大事。每项实际工作都将介于“准确地完成狭窄的任务”和“思考全局”之间。如果您与人进行足够的交谈并为他们做足够的工作,您就会感觉自己的工作适合该领域。

我可以举几个具体的例子,其中您的问题根据上下文有明确的答案。考虑在编写安全性至关重要的软件的情况。这意味着您需要一支测试团队,以确保产品按预期执行。这些测试团队中的一些团队需要测试代码中的每个可能路径。如果与他们交谈,您可能会发现,如果对行为进行一般化,则他们的测试成本将增加30,000美元,因为他们将不得不测试所有这些额外的路径。在这种情况下,即使您必须重复工作7或8次,也不要添加通用功能。节省公司资金,并完全按照要求进行操作。

另一方面,请考虑您正在制作API,以允许客户访问您公司制作的数据库程序中的数据。客户请求允许更改A-> B。 API通常具有金色的手铐:向API添加功能后,通常不应该删除该功能(直到下一个主要版本号)。您的许多客户可能不愿意为升级到下一个主要版本号支付费用,因此您可能会长期选择使用任何解决方案。在这种情况下,我强烈建议从头开始创建通用解决方案。您真的不想开发出一个带有一次性行为的不良API。

#11 楼

嗯。。。没有什么要继续回答的内容...回荡了以前的答案,“这取决于”。

从某种意义上讲,您必须依靠自己的经验。如果不是您的,则该领域的资深人士。您可以质疑接受标准的状态。如果是类似“用户应该能够将类型从“ A”更改为“ B””而不是“用户应该能够将类型从其当前值更改为任何允许的替代值”的东西。

经常会接受解释标准,但是优秀的质量检查人员可以编写适合于当前任务的标准,从而最大程度地减少需要的解释。

是否存在不限制领域的限制?是否允许从“ A”更改为“ C”或任何其他选项,但只能将“ A”更改为“ B”?还是这只是一个狭spec的要求,不是“超前思考”?

如果一般情况下比较困难,我会在开始工作之前去问一下,但如果您是我,可以“预测”将来还会有其他“类型”变更请求,我很想:
a)写一些可在一般情况下重用的东西,然后
b)将其包装在一个

很容易在自动化测试中验证当前案例,并且很容易在以后出现不同用例时打开其他选项。

br />

#12 楼

对我来说,我不久前建立的指导方针是:“对于假设的要求,只写假设的代码。”也就是说,如果您预期有其他要求,则应考虑如何实现这些要求,并对当前代码进行结构设计,以使其不会那样阻塞。

但是,现在不要为这些编写实际的代码-只需考虑一下您将要做什么。否则,您通常会使事情变得不必要地复杂,并且当实际需求与您的预期有所不同时,稍后可能会感到烦恼。

四个例子:如果您具有convert方法的所有用法,请参见您的控件,您现在可以只将其称为convertAToB,并计划在IDE中使用“重命名方法”重构来重命名它,如果以后需要更多常规功能。但是,如果转换方法是公共API的一部分,则可能会大不相同:因为该特定方法会在以后阻止泛化,因为在这种情况下很难重命名。

#13 楼


我一直听到,优秀的开发人员应该尝试预测变化并设计系统,以便将来可以轻松扩展。


原则上可以。但这并不一定会导致通用解决方案。

就我而言,在软件开发中有两种类型的主题,您应该在它们中预见未来的变化:


打算供第三方使用的库,以及
整体软件体系结构。

第一种情况是通过观察您的内聚/耦合,依赖项注入或其他方法解决的。第二种情况更抽象,例如为大型应用程序选择面向服务的体系结构,而不是大量的单一代码块。

您的情况是,您需要一种特定的解决方案一个特定的问题,对未来没有任何可预见的影响。在这种情况下,YAGNI和DRY是适合拍摄的好座右铭:


YAGNI(您将不需要)告诉您实现所需的绝对最低限度的基本知识,立即使用。如果您应该使用TDD / BDD / FDD样式开发,则意味着要实现使当前测试套件从红色变为绿色的最低要求。再没有一行。
(不要重复自己)意味着如果您再次遇到类似的问题,那么您将认真考虑是否需要通用解决方案。

与其他现代实践相结合(例如良好的测试覆盖范围以便能够安全地进行重构),这意味着您最终可以根据需要快速地编写,精简,平均的代码。


其中听起来像是通用解决方案吗?


不,听起来您应该拥有编程环境,语言和工具,可以在需要时轻松便捷地进行重构。通用解决方案不提供此功能;它们使应用程序与实际域脱钩。

看一下现代ORM或MVC框架,例如Ruby on Rails;在应用程序级别,所有焦点都放在进行非常规工作上。 Rails库本身显然是几乎100%通用的,但是在这方面,域代码(您的问题是关于域代码)应该做的是最少的恶作剧。

#14 楼

思考问题的另一种方法是考虑什么才有意义。例如,我正在开发的某个应用程序的部分执行几乎相同的操作,但权限规则不一致。由于没有理由让它们保持不同,因此当我重构该部分时,我使它们都以相同的方式执行权限。使整个代码更小,更简单,界面也更加一致。

当管理层决定允许其他人访问某个功能时,我们只需更改一个标志就可以做到。 >
显然,进行特定类型的转换很有意义。进行附加类型转换是否也有意义?

请记住,如果通用解决方案的实施速度更快,那么特殊情况也很容易,只需检查一下它是否是唯一允许的类型转换即可。

如果应用程序处于高度管制的区域(医疗或财务应用程序),请尝试让更多的人参与您的设计。