什么是“过早抽象”?


10

我听过这个短语被扔掉了,对我来说,论点听起来完全是疯了(对不起,如果我在这里是稻草人,那不是我的意图),通常它遵循以下原则:

您不希望在知道一般情况之前先创建一个抽象,否则(1)您可能会将不属于您的东西放在抽象中,或者(2)忽略了重要的事情。

(1)对我来说,这听起来像程序员不够务实,他们已经假设事情会出现在最终程序中,而事实并非如此,因此他们使用的抽象程度很低,问题不是过早的抽象,它是过早的凝结。

(2)忽略重要的事情是一回事,完全有可能在规范中省略了某些事情,后来证明这很重要,解决这个问题的方法不是在发现自己时就浪费自己的资源和浪费资源。猜错了,这是从客户端获取更多信息。

我们应该始终从抽象到具体工作,因为这是最实用的做事方式,而不是相反。

如果我们不这样做,那么我们可能会误解客户,并创造需要更改的东西,但是如果我们仅构建客户以其自己的语言定义的抽象,那么我们就永远不会遇到这种风险(至少远不及承担风险)是在黑暗中带着某种凝结的镜头),是的,客户可能会改变对细节的想法,但是他们最初用来传达他们想要的内容的抽象往往仍然有效。

这是一个示例,假设客户希望您创建一个物品装箱机器人:

public abstract class BaggingRobot() {
    private Collection<Item> items;

    public abstract void bag(Item item);
}

我们正在从客户端使用的抽象中构建一些东西,而没有涉及我们不知道的事情的更多细节。这是非常灵活的,我已经看到这被称为“过早抽象”,而实际上假设套袋是如何实施还为时过早,可以说与客户讨论后,他们希望一次将多个袋装成袋。为了更新班级,我只需要更改签名,但是对于自下而上的人可能需要进行大范围的系统检修。

没有过早的抽象,只有过早的凝结。这句话有什么问题?我的推理的缺陷在哪里?谢谢。


1
过早抽象的对立面是YAGNI-您将不需要它,在我看来,这几乎总是错误的。几乎。我已经看到了它的发生,但是很少见。我大部分都同意你在这里所说的话。
user1118321 '19

Answers:


19

至少在我看来,过早的抽象相当普遍,尤其是在OOP的早期。

至少从我看来,出现的主要问题是人们通读了面向对象层次结构的典型示例。他们被告知要做好一切准备以应对将来可能发生的变化(尽管没有特别好的理由相信他们会做到)。一段时间以来,许多文章共有的另一个主题是鸭嘴兽(platypus)之类的东西,它违反了关于“哺乳动物都像这样”或“鸟都像那样”的简单规则。

结果,我们最终得到的代码实际上只需要处理(例如)员工记录,但是如果您雇用了蜘蛛或甲壳类动物,则经过精心编写以准备就绪。


因此,过早的抽象不是抽象的行为,而是超出规范级别的抽象的行为。这更有意义,我可以看到实际发生的情况。我想我只需要向同事们清楚一点,我正在按照经理概述的抽象级别进行工作。
詹姆斯,

1
@James:这也意味着在1.0版的规范之后要“过多”抽象化,因为您认为自己知道更高版本的规范中有一个新要求,因此已经比以前以更通用的方式实现v1.0必要。可以预见,在更高版本中可能会发生什么,但是也存在过度工程的风险。
布朗

@DocBrown我会称其为早熟。抽象的行为是删除信息而不是添加信息。如果要向抽象添加过多的东西,那么您将假定有关较低抽象级别的信息。我想在两者之间进行区分,因为我认为称“抽象”没有多大意义,因为抽象的定义是“提取基本本质的过程”,其中固结是“由即时经验表征或属于即时经验”实际事物或事件”。
詹姆斯,

6

重要的是要记住,抽象是达到目的的一种手段。您可以使用抽象来统一整个程序的行为,并在需要添加新类的情况下(但仅在那一刻需要抽象时)使添加新类变得简单明了。

您不会仅仅因为可能需要抽象就添加了抽象(没有真正的根据,认为将来需要添加新的类)。一个适当的比喻可能在这里。希望您会同意,允许水向上/向下,向东/向西,向北/向南流动的六向管道将是您所拥有的最灵活的管道类型。从理论上讲,您可以在需要管道的任何地方使用6向管道,并阻塞不需要的方向,对吗?

如果您试图解决水槽下漏水的问题,并且发现所有管道部分都是6向管道,那么您想对以这种方式设计的家伙感到沮丧的话就把头发拉出来。您不仅不知道问题出在哪里,而且几乎可以肯定地以正确的方式从头开始就更直接了。

当然,编码并不能解决问题,但是隐喻仍然存在。抽象就像使用那些6向管道。当您诚实地认为您可能会在不久的将来有一天需要从所有6个方向连接管道时,请使用它们。否则,抽象就是复杂,与使用不需要任何模式的模式或尝试执行所有操作的God类没有太大区别。如果它没有被使用,并且有可能永远不会被使用,那么您最终将添加一个附加类。

诚然,编写程序的艺术在概念上非常抽象。值得一提的是,抽象并不是为了成为抽象而存在,而是因为它们以某种实际的方式是实用的。如果您觉得有必要使用抽象,就这样吧,但之后不要让我检查您的管道。;)


您似乎有点以中心为class中心…即使有很多类,抽象也不限于用于/有益于它们。
Deduplicator

1
@Deduplicator还算公平,但这并不能改变我的观点。如果使用抽象,则应该立即受益,或者应该是在不久的将来进行的计划更改。并不是说我们指的是类或函数式编程。
尼尔,

3

多年前,当我了解面向对象的分析和设计时,我们将以简单的英语描述客户所需的系统开始。

从中可以看出,任何名词(或名词短语)都将被视为可能的类别。任何动词(或动词短语)都是类上的潜在方法。因此“装袋机器人”可能是一门课BaggingRobot。“打开袋子”可能成为方法OpenBag

经过几次迭代,这将变成一个类图。

此时,还没有抽象类。客户不需要装袋机器人的抽象概念。他们想要一个可以把东西装进袋子里的机器人。所有类都是具体的,并具有一组方法。

仅在很明显的情况下才引入抽象类:

  • 有几种类似的类可以构成一个层次结构。
  • 它们实际上共享一些共同点,因此基类可以实现有用的目的。

对我来说,“过早抽象”是假设任何人都BaggingRobot 必须继承某些东西BaseRobot,更糟糕的是,BaseRobot在您甚至不了解所有机器人的共同点之前,尝试开发一套方法。


抽象不是抽象类的同义词。接口是一种抽象。我说的是一般的抽象,而不仅仅是抽象类。也许我可以通过使用界面使这一点变得更清楚。如果您在与客户端相同的抽象层上开发系统,那么最终可能会遇到一些抽象类,由于语言自然是抽象的,因此需要对其进行扩展。低于此水平的工作就是我所说的“过早固结”,您天真地假设客户关于系统如何工作的想法与您的想法相同。
詹姆斯

将Item对象放入Bag对象的BaggingRobot对象已经是将东西放入袋中的真实装袋机器人的抽象。
user253751 '19

1

听起来好像您想解决一个尚不存在的问题,并且您没有充分的理由假设会发生此问题,除非您只是认为客户的要求是错误的。如果是这样,我建议您在实施猜测解决方案(抽象或其他方式)时进行更多的交流。尽管只是对类定义打“ abstract”似乎无伤大雅,并且知道以后可以扩展它可以放心,但您可能会发现自己根本不需要,或者您可能会发现自己以使设计复杂化的方式扩展了它。通过提取错误的内容来实现。

以您的示例为例,您是否想像是机器人本身要装袋物品装袋方法会发生变化?

过早的抽象实际上仅意味着您没有充分的理由执行抽象,并且由于在许多语言中抽象远非免费,您可能会在没有理由的情况下产生开销。

编辑为了回答评论中OP的一些要点,我将对此进行更新,以澄清和响应方式,而无需特别指出第(1)和(2)点,因此不需要冗长的评论链。

(1)对我来说,这听起来像程序员不够务实,他们已经假设事情会出现在最终程序中,而not会存在,因此他们的工作水平低至抽象水平,问题不在于过早的抽象,它是过早的凝结。

(强调我的)。假设最终程序中会存在与我在您的示例中看到的不完全相同的东西。客户要求一个装袋机器人。如果在语言上有些含糊,这是一个非常具体的要求。客户需要一个可以装袋物品的机器人,因此您可以生产一对物品

public class Item {
/* Relevant item attributes as specified by customer */
}
public class BaggingRobot {
  private Collection<Item> items;

  public void bag(Item item);
}

您的模型简单而精确,它遵循客户设定的要求,而不会增加解决方案的复杂性,也不会假设客户想要的东西超出了他们的要求。如果,当与此呈现,他们明确需要机器人具有可互换装袋力学,只有那么你有足够的信息来证明创建一个接口或抽象的另一种方法,因为您可以立即指向特定的值被添加到通过抽象的系统。

相反,如果您从纯粹实用主义的抽象开始,那么您要么花时间创建一个单独的接口并以具体的方式实现它,要么创建一个抽象类,或者使用您喜欢的任何抽象方式。然后,如果结果表明客户对此感到满意,并且不需要进一步扩展,那么您就白白浪费了时间和资源,并且/或者毫无意义地将开销和复杂性引入了系统。

关于(2),我同意忽略重要的事情并不是表面上过早抽象的标志。不管是否抽象,您都可以忽略一些重要的东西,除非发现它位于抽象链的末尾,否则以任何一种方式进行分类都不会更难或更容易。

相反,我将其解释为意味着任何抽象都存在混淆域模型的风险。错误的抽象会使系统难以推理,因为您正在创建的关系会严重影响域模型和域理解的进一步发展。通过将模型放在错误的兔子洞中,您可能会冒着忽略有关抽象事物而不是有关整个系统的重要信息的风险。


“这听起来有点像您要解决一个尚不存在的问题,并且您没有充分的理由去假设会发生,除非您只是认为客户的要求是错误的。” -这根本不是我所说的,事实上,我明确表示我只是基于客户使用的词语进行构建。不想这样做的行为促使我使用抽象。
詹姆斯

“我建议您在实施猜测解决方案时多进行交流”-您是说像我建议的那样?引用我在OP中所说的话,“解决此问题的办法不是在发现自己猜错的情况下提出自己的想法并浪费资源,而是要从客户端获取更多信息。”
詹姆斯

“尽管在类定义上打“抽象”似乎很无害,并且可以放心地知道可以稍后再扩展它是安全的。”-当我说“抽象”时,我的意思是“抽象”,而不是“抽象关键词”。接口可以是抽象,抽象可以是模糊逻辑,这就是整篇文章所讨论的。客户端以抽象的方式进行通信,因此我们应该以抽象的方式进行编程,直到我们对域有了更多的了解。
詹姆斯

1
“您可能会发现您根本不需要”-这没有任何意义。客户说“我想要一个可以装袋物品的机器人”,您将创建一个满足该条件的抽象。您从客户那里得到的任何进一步信息将是有关他们希望如何进行装袋的详细信息。这不会使您的抽象无效,客户不会突然改变他们所购买商品背后的核心思想的想法。机器人仍将装袋物品。您创建的抽象将仍然适用。
詹姆斯

“或者您可能会发现自己以抽象错误的东西的方式过度扩展了设计,使之复杂化了。” ...老实说,您读过我说的还是标题吗?认真吗 阅读点(1)和(2)
詹姆斯
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.