抽象:解决问题和一般解决方案之间的战争[关闭]


33

作为一名程序员,我感到自己陷入困境,希望使我的程序尽可能抽象和通用。

这样做通常可以让我重用我的代码,并为可能(或可能不会)再次出现的问题提供更通用的解决方案。

然后我脑海中的声音说,解决问题虚拟化就这么简单!为什么要花比您更多的时间?

我们所有人的确都遇到过这个问题,抽象在您的右肩上,而“解决问题的愚蠢”在左边。

听哪个,多久听一次?您对此有何策略?您应该抽象一切吗?


1
“抽象而一般”通常被称为程序员的傲慢:)但是,由于墨菲定律,在执行下一个任务时需要的一种适应程度是您没有提供的适应性。
Matthieu M.

1
有史以来最好的问题之一!
explorest

1
您总是会猜错,所以很简单。将简单案例扩展到“足够多的时间”后,您就会开始看到一种模式。在那之前,不要。

Answers:


27

听哪个,多久听一次?

除非必须,否则请不要抽象。

例如,在Java中,必须使用接口。它们是抽象的。

在Python中,您没有接口,没有Duck Typing,也不需要高耸的抽象级别。所以你就是不。

您对此有何策略?

在您写了三遍之前,请不要抽象。

有一次-很好-有一次。只需解决它并继续前进即可。

两次表明此处可能存在某种模式。或可能没有。可能只是巧合。

三次是模式的开始。现在,它超越了巧合。您现在可以成功抽象。

您应该抽象一切吗?

没有。

的确,除非您有绝对的证据证明您在进行正确的抽象,否则永远不要抽象任何东西。没有“三个重复的规则”,您将以无用的方式编写无用抽象的内容。

这样做通常会让我重用我的代码

这种假设通常是错误的。抽象可能根本没有帮助。这可能做得不好。因此,除非必须,否则请不要这样做。


1
“除非必须,否则请不要抽象。” -我认为您避免使用类,方法,循环或if语句?这些东西都是抽象的。如果您考虑一下,不管您是否喜欢,用高级语言编写的代码都是非常抽象的。问题不在于是否要抽象。使用的是抽象。
杰森·贝克

9
@Jason Baker:“我认为您避免使用类,方法,循环或if语句”。这似乎不是问题所在。如果我们避免过度抽象设计,为什么要荒谬地宣称所有编程都是不可能的?
S.Lott

1
我想起了一个老笑话,一个男人问一个女人,如果她愿意和他一起睡一百万美元。当她说“是”时,他问她是否愿意和他同床5美元。当她说“你要带我去什么样的女人?” 回答是:“好吧,我们已经确定您会和某人性交。现在,我们只是在讨价还价。” 我的观点是,永远不要决定是否抽象。它涉及抽象多少以及选择什么抽象。除非您正在编写纯汇编程序,否则您不能选择推迟抽象。
杰森·贝克

9
@Jason Baker:“除非选择编写纯汇编程序,否则您不能选择推迟抽象”。汇编是对机器语言的抽象。这是对硬件电路的抽象。请不要过多地阅读本来就没有问题的答案。问题不在于“抽象”是一种概念还是一种智力工具。它是关于作为OO设计技术的抽象-经常被错误地使用。我的回答不是说抽象是不可能的。但是这种“过度抽象”是不好的。
S.Lott

1
@JasonBaker这不是和某人睡觉的未知原因。也许您在想“钱”?

19

啊,雅尼 最滥用的编程概念。

使代码通用与执行额外工作之间有区别。您是否应该花费额外的时间使您的代码松耦合并轻松地适应其他事情?绝对。您是否应该花时间实施不必要的功能?否。您是否应该花时间使您的代码与尚未使用的其他代码一起工作?没有。

俗话说“做可能可行的最简单的事情”。问题是人们总是将简单容易混淆。简单需要工作。但是值得付出努力。

你会落水吗?当然。但是我的经验是,很少有公司比他们现在已经需要更多的“立即完成”的态度。大多数公司需要更多的人,他们会提前思考问题。否则,他们总是会遇到一个自我维持的问题,那就是没有人有时间去做任何事情,每个人都总是在匆忙中,没有人做任何事情。

这样考虑:很有可能再次使用您的代码。但是您不会正确地预测这种方式。因此,使您的代码干净,抽象且松耦合。但是,请勿尝试使您的代码与尚不存在的任何特定功能一起使用。即使您知道将来还会存在。


simple和之间有很好的区别easy
Matthieu M.


“但是我的经验是很少有公司”-有趣的是,在学术界,情况可能恰恰相反。
史蒂夫·贝内特

12

三段论:

  1. 一般性是昂贵的。

  2. 您正在花别人的钱。

  3. 因此,必须将普遍性的代价证明给利益相关者。

问问自己,您是否真的在解决更一般的问题,以便以后节省利益相关者的钱,或者您只是觉得做出不必要的一般性解决方案是一项理智的挑战。

如果确定普遍性是可取的,则应像其他任何特征一样设计和测试它。如果您无法编写测试来证明您已实现的通用性如何解决了规范要求的问题,那么就不要着急了!不符合设计标准且无法测试的功能是任何人都无法依赖的功能。

最后,没有“尽可能通用”的东西。假设您只是为了争辩而使用C#编写软件。您要使每个类都实现一个接口吗?每个类都是抽象基类,每个方法都是抽象方法?这很笼统,但距离“尽可能笼统”不远。那只是允许人们通过子类更改任何方法的实现。如果他们想更改方法的实现而不进行子类化怎么办?您可以使每个方法实际上是委托类型的属性,并带有一个setter,以便人们可以将每个方法更改为其他方法。但是,如果有人想添加更多方法呢?现在,每个对象都必须是可扩展的。

此时,您应该放弃C#并使用JavaScript。但是您还没有达到足够普遍的程度。如果有人想更改成员查找针对此特定对象的工作方式怎么办?也许您应该改为使用Python编写所有内容。

增加通用性通常意味着放弃可预测性,并大量增加测试成本,以确保所实施的通用性实际上能够成功满足实际用户的需求。这些成本是否可以通过对付钱的利益相关者的利益来证明?也许他们是;您可以与利益相关者讨论。我的涉众根本不愿意我放弃静态类型以追求完全不必要和极其昂贵的通用性,但也许您愿意。


2
+1是有用的原则,-1是赚钱的全部要素。
梅森惠勒

4
@梅森:这不是金钱,而是努力金钱是努力的实际手段。效率是指每产生一次努力应获得的收益;再次,货币利润是实际产生的收益的一种度量。讨论货币的抽象比提出某种方法来衡量工作量,成本和收益要容易得多。您是否愿意以其他方式衡量工作量,成本和收益?随时提出更好的建议。
埃里克·利珀特

2
我同意@Mason Wheeler的观点。我发现这里的解决方案是不让涉众参与项目的那个级别。显然,这在很大程度上取决于行业,但是客户通常看不到代码。此外,所有这些答案似乎都是反努力,似乎很懒。
2011年

2
@Orbling:我坚决反对不必要,昂贵,浪费的工作,这些工作会浪费宝贵的重要项目的资源。如果您认为这使我“懒惰”,那么我认为您以一种不寻常的方式使用“懒惰”一词。
埃里克·利珀特

1
以我的经验,其他项目不会消失,因此通常值得在当前项目中投入尽可能多的时间再继续进行。
2011年

5

需要明确的是,进行概括和实现抽象完全是两件事。

例如,考虑一个复制内存的函数。

该函数是一个抽象,隐藏了如何复制这4个字节。

int copy4Bytes(char * pSrc,char * pDest)

概括而言,可以使此函数复制任意数量的字节。

int copyBytes(char * pSrc,char * pDest,int numBytesToCopy)

抽象使自己可以重用,而泛化只是使抽象在更多情况下有用。

更具体地讲,与您的问题有关,抽象不仅从代码重用的角度来看很有用。通常,如果操作正确,它将使您的代码更具可读性和可维护性。使用上面的示例,如果您浏览代码copyBytes()或一次遍历一次将数据移动一个索引的数组的for循环,那么更容易阅读和理解呢?我认为抽象可以提供一种自我文档,使代码更易于使用。

根据我自己的经验,如果我能给出一个很好的函数名称来准确描述我打算执行的一段代码,那么无论是否我会再次使用它,我都会为其编写一个函数。


+1用于区分抽象和概括。
Joris Meys 2011年

4

此类事物的一个好的通用规则是零,一,无穷大。就是说,如果您需要多次编写内容,则可以假设您将需要更多次并进行概括。这条规则意味着您第一次写东西时就不必理会抽象。

此规则的另一个很好的理由是,第一次编写代码时,您不一定知道要抽象什么,因为您只有一个示例。墨菲定律意味着,如果您第一次编写抽象代码,则第二个示例将具有您没有预料到的差异。


1
这听起来很像我的《重构规则》版本:击中两个!重构!
Frank Shearar 2010年

@弗兰克:这可能是您的重构规则,但第二段是根据个人经验添加的。
拉里·科尔曼

+1:我相信在实用程序员几乎逐字逐句地IIRC中也已经注意到了这一点。
史蒂文·埃弗斯

哦,肯定的。仅举一个示例就没有什么要抽象的:一旦有了第二个示例,您就可以看到什么是共同的(共享),什么不什么,您可以抽象掉!
Frank Shearar 2010年

在我看来,只有两个样本太小而无法泛化为无穷大。换句话说,为时过早。

2

埃里克·利珀特(Eric Lippert)指出了我想在他的文章将来对设计进行校对的三点内容。我认为,如果您遵循它,您将处于良好状态。

第一:过早的普遍性是昂贵的。

第二:在模型中仅表示那些始终在问题域中并且其类关系不变的事物。

第三:使策略远离机制。


1

这取决于您编码的原因,项目的目的。如果您的代码的价值是可以解决一个具体的问题,那么您想要完成该工作并继续下一个问题。如果您可以做一些快速简便的事情来使代码的未来用户(包括您自己)更轻松,那么请务必进行合理的调整。

另一方面,在某些情况下,您正在编写的代码具有更一般的用途。例如,当您为其他程序员编写库时,他们将在各种应用程序中使用该库。潜在的用户是未知的,您不能确切询问他们想要什么。但是您想使您的库广泛使用。在这种情况下,我会花更多时间尝试支持通用解决方案。


0

我非常喜欢KISS原则。

我专注于提供要求执行的操作,而不是最佳解决方案。我不得不放弃完美主义(和强迫症),因为这让我很痛苦。


为什么不喜欢完美主义?(顺便说一句,我没有对你投反对票)
2011年

不追求完美对我有用。为什么?因为我知道我的程序永远不会完美。没有完美的标准定义,所以我只是选择提供一些效果很好的东西。
Pablo

-1

抽象”位于您的右肩上,“ 求解困难”位于左侧。

我不同意“愚蠢地解决它”,我认为它可以更“聪明地解决它”

更聪明的是:

  • 编写一个可以支持多种情况的复杂的通用解决方案
  • 写短effecient代码,解决手头的问题,易于维护,而且可以扩展,将来如果它是必需的。

默认选择应该是第二个。除非您可以证明需要世代相传。

仅当您知道将在多个不同的项目/案例中使用通用解决方案时,才应使用通用解决方案。

通常,我发现一般的解决方案最好用作“核心库代码”


如果您可以在此处进行所有假设,则不会有问题。简短和易于维护是相互排斥的。如果您知道某些东西将被重用,则通用解决方案将解决该问题。
JeffO 2011年

@杰夫,我不太确定我是否理解您的评论
。-暗夜2011年

您从上下文中删除了“解决愚蠢”。
布莱恩·哈灵顿
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.