泄漏抽象的含义?


88

“泄漏抽象”是什么意思?(请举例说明。我常常很难理解一个纯粹的理论。)



14
您可能想阅读Joel Spolsky的原始文章“泄漏抽象定律”,据我所知,这是该术语的由来。
丹尼尔·普赖登

1
建议的骗局的大多数答案都与流畅的界面有关。
David Thornley,2010年

@David:投票数第二高的帖子回答了泄漏抽象的含义,并列举了一个很好的例子。
missingfaktor 2010年

4
四年后,当您用谷歌搜索该问题时,很难猜测哪个帖子曾经是投票率第二高的帖子。
约翰·雷诺兹

Answers:


103

这是个Meatspace示例:

汽车有驾驶员抽象。最纯粹的形式是方向盘,油门和制动器。这种抽象隐藏了引擎盖下的很多细节:引擎,凸轮,同步带,火花塞,散热器等。

关于此抽象的整洁之处在于,我们可以用改进的部分替换实现的某些部分,而无需重新培训用户。假设我们用电子点火装置取代了分配器盖,而用可变凸轮取代了固定凸轮。这些变化提高了性能,但用户仍然可以用方向盘操纵并使用踏板来启动和停止。

这实际上是非常了不起的……16岁或80岁的人可以操作这种复杂的机器,而无需真正了解其内部工作原理!

但是有泄漏。变速箱泄漏很小。在自动变速器中,您会感觉到汽车在换档时会暂时失去动力,而在CVT中,您会一直感觉到平稳的扭矩。

也有更大的泄漏。如果发动机转速过快,可能会损坏发动机。如果发动机缸体太冷,则汽车可能无法启动或性能不佳。如果同时打开收音机,前灯和交流电,您会发现油耗降低了。


7
谢谢你的例子。似乎没有人能够提供简单的解释。
塞巴斯蒂安·帕滕

7
这是一个很好的答案,特别是因为它演示了用户的观点,而这正是软件版本所要解决的问题。
2013年

1
肉空间是什么意思?外行人解释?
brumScouse

1
@brumScouse“饮食空间”表示实体的离线世界。它用于与在线网络空间世界进行对比。我将编辑答案以包括指向定义的链接。
Mark E. Haase

我喜欢这篇文章指出“仍然存在泄漏”的方式。这是关于最小化它们的全部。
alaboudi

49

这仅表示您的抽象公开了一些实现细节,或者在使用抽象时您需要了解实现细节。该术语归因于Joel Spolsky(大约在2002年)。有关更多信息,请参见Wikipedia文章

网络库是一个典型的例子,它使您可以将远程文件视为本地文件。使用此抽象的开发人员必须意识到,网络问题可能会导致失败,而本地文件不会失败。然后,您需要开发代码以专门处理网络库提供的抽象之外的错误。


7
@mehaase我不明白您的抽象是由于设计还是由于疏忽而引起的。我用示例和参考文章中的更多信息扩展了答案,以便它可以独立存在。此外,我认为“泄漏抽象”不必一定是贬义词。对我来说,它仅描述了您作为开发人员在使用抽象时需要更加小心的情况。设计可以是好,坏或无关紧要的,而与“泄漏”无关。
tvanfosson 2011年

12

维基百科有一个相当不错的定义,这个

泄漏抽象是指旨在降低(或隐藏)复杂性的所有已实现抽象,其中底层细节未完全隐藏

换句话说,对于软件而言,您可以通过程序中的限制或副作用观察功能的实现细节。

一个简单的例子是C#/ VB.Net闭包及其无法捕获ref / out参数。无法捕获它们的原因是由于提升过程如何发生的实现细节。这并不是说有更好的方法可以做到这一点。


12

这是.NET开发人员熟悉的示例:ASP.NET的 Page类尝试隐藏HTTP操作的细节,尤其是表单数据的管理,以便开发人员不必处理发布的值(因为它会自动将表单值映射到服务器)控件)。

但是,如果您超出了最基本的使用场景,那么Page抽象就会开始泄漏,除非您了解类的实现细节,否则页面将变得难以使用。

一个常见的示例是在页面上动态添加控件-除非您在正确的时间添加它们,否则动态映射控件的值将不会为您映射:在基础引擎将传入的表单值映射到适当的控件之前。当您必须学习时,抽象就泄漏了


Webforms在其存储桶中处于低谷。更糟糕的是,像您在杂物箱中工作一样,薄薄的抽象层相当于使用Http。
brumScouse

8

嗯,从某种意义上说,这虽然不重要,但纯粹是理论上的事情。

我们使用抽象使事情更容易理解。我可能会使用某种语言在字符串类上进行操作,以掩盖我正在处理作为单个项目的一组有序字符这一事实。我处理一组有序的字符以隐藏我正在处理数字的事实。我处理数字以掩盖我正在处理1和0的事实。

泄漏抽象是不会隐藏其要隐藏的细节的抽象。如果在Java或.NET中以5个字符的字符串调用string.Length,则我可以从5到10得到任何答案,因为实现细节表明这些语言称呼字符实际上是可以表示1或16的UTF-16数据点。 .5个字符。抽象已泄漏。但是,不泄漏它意味着找到长度将需要更多的存储空间(以存储实际长度),或者从O(1)变为O(n)(以计算出实际长度)。如果我在乎真正的答案(通常不是真的),则需要了解实际情况。

在某些情况下,更有争议的情况发生,例如方法或属性让您进入内部工作时,无论它们是抽象泄漏,还是定义到较低抽象级别的明确定义的方法,有时都可能会引起人们的不同意见。


2
而您使用1和0来隐藏您正在处理电子和物理的事实(我知道很晚才发表评论)
Davy8 2011年

6

我将继续使用RPC进行示例。

在RPC的理想世界中,远程过程调用应该看起来像本地过程调用(或者这样)。对程序员来说应该是完全透明的,这样,当他们打电话时,SomeObject.someFunction()他们不知道是SomeObject(还是仅someFunction出于此目的)是本地存储和执行还是远程存储和执行。从理论上讲,这使编程更简单。

实际情况有所不同,因为进行本地函数调用(即使您使用的是世界上最慢的解释语言)之间也存在巨大差异:

  • 通过代理对象调用
  • 序列化您的参数
  • 建立网络连接(如果尚未建立)
  • 将数据传输到远程代理
  • 让远程代理还原数据并代表您调用远程功能
  • 序列化返回值
  • 将返回值传输到本地代理
  • 重新组装序列化的数据
  • 从远程函数返回响应

仅在时间上,大约是三个数量级(或更多!)的数量级差异。这三个数量级以上将在性能上产生巨大的差异,这将使您对过程调用抽象的泄漏相当明显,这是您第一次错误地将RPC视为真实函数调用时。再有一个真正的函数调用,除非代码中存在严重问题,否则实现错误之外的故障点将很少。RPC调用具有以下所有可能的问题,这些失败问题会超出常规本地调用所期望的故障范围,而会逐渐失效:

  • 您可能无法实例化本地代理
  • 您可能无法实例化远程代理
  • 代理可能无法连接
  • 您发送的参数可能无法完整保留
  • 远程发送的返回值可能不会使它完整无缺

因此,现在您的RPC调用“就像本地函数调用一样”具有全部额外的失败条件,您在进行本地函数调用时不必面对这些额外的失败条件。抽象再次泄漏,甚至更加困难。

最终,RPC是一个糟糕的抽象,因为它在每个级别都像筛子一样泄漏–成功和失败时都会泄漏。


<pimp>我更喜欢Erlang方法,因为它不会试图隐藏函数调用和向进程发送消息之间的差异,以至于两者使用的语法非常不同。尽管使用相同的通用语法,但是远程过程消息发送与本地过程发送明显不同。</ pimp>
我的正确想法是2010年

2
好吧,这是唯一给出一个很好的例子(阅读理解,伙计)的答案,所以它得到了+1。
Mark E. Haase

3

django ORM多对多示例中的一个示例

请注意,在“示例API使用情况”中,您需要先将.Article()基本对象a1保存(save()),然后才能将发布对象添加到“多对多”属性。并且请注意,更新多对多属性会立即保存到基础数据库,而更新单个属性不会在db中反映出来,除非调用.save()。

抽象是我们正在使用对象图,其中单值属性和多值属性只是属性。但是作为关系数据库支持的数据存储的实现会泄漏……因为RDBS的完整性系统通过对象接口的薄面板出现。


1

什么是抽象?

首先,最好是了解什么是“抽象”?

抽象是简化世界的一种方式。这意味着您不必担心引擎盖下/幕后的实际情况。这意味着有些东西是白痴证明。好的,那是什么意思?最好通过示例说明。

抽象示例:“抽象”了飞行737/747的复杂性

让我们以波音客机为例。这些飞机是非常复杂的机械零件。您拥有喷气发动机,氧气系统,电气系统,起落架系统等,但是飞行员不必担心喷气发动机的复杂性……所有这些东西都“被抽象化了”,这意味着:那天,飞行员只担心轮子和控制柱来控制飞机。从左向左走,从右向右走,向上拉高以升高,向下推以下降。这很简单……实际上我撒了谎:控制方向盘要稍微复杂一点。在理想的世界里,那是他唯一的选择应该担心。但这在现实生活中并非如此:如果您像猴子一样驾驶飞机,而对飞机的运作方式或任何实施细节没有任何真正的了解,那么您很可能会坠毁并杀死机上的所有人。

泄漏抽象

实际上,飞行员确实需要担心很多重要的事情-并非一切都被抽象化了:飞行员必须担心风速,推力,迎角,燃料,高度,天气问题,下降角度,是否飞行员正朝着正确的方向前进,飞机现在就在那里,依此类推。计算机可以帮助飞行员完成这些任务,但并非所有操作都是自动化/简化的。

例如,如果飞行员在立柱上用力过猛-飞机会服从,但是飞行员将有使飞机失速的风险;如果使飞机失速,则在重新坠落至地面之前很难重新获得控制权。

换句话说,仅凭飞行员仅在不知道其他任何内容的情况下控制方向盘是不够的。在他飞行之前。……她必须知道飞机的工作原理以及飞机的飞行方式。他必须知道实施细节.....她必须知道过分用力拉扯会导致失速,或者过分着陆会破坏飞机。

这些东西没有被抽象掉。很多东西都被抽象掉了,但不是全部。飞行员只需要担心转向柱,也许还需要担心一两件事。抽象是“泄漏的”。

......这与您的代码相同。如果您不了解底层的实现细节,那么您经常会陷入困境。

这是编码示例:

ORM在处理数据库查询时会带来很多麻烦,但是如果您曾经做过类似的事情:

User.all.each do |user|
   puts user.name # let's print each user's name
end

然后,您将意识到,如果拥有超过数百万的用户,这是杀死应用程序的好方法。并非一切都被抽象掉了。您需要知道,User.all与2500万用户进行通话会增加您的内存使用率,并会引起问题。您需要了解一些基本细节。抽象是泄漏的。


0

事实上,在某个时候,这将取决于您的规模和执行情况,您需要熟悉抽象框架的实现细节,以便理解其行为方式。

例如,考虑以下SQL查询:

SELECT id, first_name, last_name, age, subject FROM student_details;

及其替代方案:

SELECT * FROM student_details;

现在,它们看起来确实在逻辑上是等效的解决方案,但是由于单独的列名规范,第一个解决方案的性能更好。

这是一个琐碎的例子,但最终回到了Joel Spolsky的话:

在某种程度上,所有非平凡的抽象都是泄漏的。

在某个时候,当您达到一定规模的操作时,您将需要优化数据库(SQL)的工作方式。为此,您将需要了解关系数据库的工作方式。一开始它是抽象给您的,但是很漏水。您需要在某个时候学习它。


-1

假设我们在库中有以下代码:

Object[] fetchDeviceColorAndModel(String serialNumberOfDevice)
{
    //fetch Device Color and Device Model from DB.
    //create new Object[] and set 0th field with color and 1st field with model value. 
}

使用者调用API时,将获得一个Object []。消费者必须了解对象数组的第一个字段具有颜色值,第二个字段是模型值。此处,抽象已从库泄漏到使用者代码。

解决方案之一是返回一个封装了设备型号和颜色的对象。消费者可以调用该对象以获得模型和颜色值。

DeviceColorAndModel fetchDeviceColorAndModel(String serialNumberOfTheDevice)
{
    //fetch Device Color and Device Model from DB.
    return new DeviceColorAndModel(color, model);
}

-3

泄漏的抽象全都与封装状态有关。泄漏抽象的非常简单的示例:

$currentTime = new DateTime();

$bankAccount1->setLastRefresh($currentTime);
$bankAccount2->setLastRefresh($currentTime);
$currentTime->setTimestamp($aTimestamp);

class BankAccount {
    // ...

    public function setLastRefresh(DateTimeImmutable $lastRefresh)
    {
        $this->lastRefresh = $lastRefresh;
    } }

和正确的方式(不是泄漏抽象):

class BankAccount
{
    // ...

    public function setLastRefresh(DateTime $lastRefresh)
    {
        $this->lastRefresh = clone $lastRefresh;
    }
}

这里有更多描述。

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.