泄漏抽象的含义?

时间:2010-10-07 15:00:54

标签: programming-languages functional-programming memory-leaks abstraction leaky-abstraction

“Leaky Abstraction”一词是什么意思? (请用例子解释。我经常很难完成一个理论。)

11 个答案:

答案 0 :(得分:82)

这是一个meatspace示例:

汽车有司机的抽象。最纯粹的形式是方向盘,加速器和制动器。这个抽象隐藏了很多关于引擎盖下的细节:发动机,凸轮,同步带,火花塞,散热器等。

关于这种抽象的巧妙之处在于,我们可以用改进的部件替换部分实现,而无需重新训练用户。假设我们用电子点火器更换分配器盖,我们用可变凸轮替换固定凸轮。这些改变改善了性能,但用户仍然使用方向盘并使用踏板来启动和停止。

实际上非常了不起......一个16岁或80岁的人可以操作这个复杂的机器而不太了解其内部的工作方式!

但是有泄漏。传动是一个小泄漏。在自动变速箱中,您可以感觉到汽车在切换档位时会瞬间失去动力,而在CVT中您可以感受到平稳的扭矩。

也有更大的泄漏。如果您将发动机转速过快,可能会对发动机造成损坏。如果发动机缸体太冷,汽车可能无法启动或性能不佳。如果你同时摇动收音机,前大灯和AC,你会看到你的汽油里程下降。

答案 1 :(得分:41)

它只是意味着您的抽象公开了一些实现细节,或者您在使用抽象时需要了解实现细节。该术语归于Joel Spolsky,大约2002年。有关详细信息,请参阅维基百科article

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

答案 2 :(得分:12)

维基百科有pretty good definition这个

  

漏洞抽象是指任何已实现的抽象,旨在减少(或隐藏)复杂性,其中底层细节未被完全隐藏

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

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

答案 3 :(得分:11)

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

但如果你超越最基本的使用场景,Page抽象开始泄漏,除非你理解了类的实现细节,否则很难处理页面。

一个常见示例是动态地向页面添加控件 - 动态添加的控件的值将不会为您映射,除非您在恰当的时间添加它们:在底层引擎映射之前传入的表单值到适当的控件。当你必须了解它时,抽象已经泄露了

答案 4 :(得分:7)

嗯,在某种程度上,这是纯理论上的事情,虽然并非不重要。

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

漏洞抽象是一种不隐藏其隐藏的细节的抽象。如果在Java或.NET中对5个字符的字符串调用string.Length,我可以从5到10获得任何答案,因为实现细节,这些语言称为字符的是真正的UTF-16数据点,它们可以代表1或.5的角色。抽象已经泄露。虽然没有泄漏,但意味着找到长度要么需要更多的存储空间(存储实际长度),要么从O(1)变为O(n)(以确定实际长度)。如果我关心真正的答案(通常你并不真的),你需要了解真实情况。

更多有争议的案例发生在诸如方法或属性允许您进入内部工作的情况下,无论它们是抽象泄漏,还是明确定义的方式转移到较低级别的抽象,有时可能是一个问题的人不同意。

答案 5 :(得分:5)

我将继续使用RPC提供示例。

在RPC的理想世界中,远程过程调用看起来应该像本地过程调用一样(或者故事如此)。它应该对程序员完全透明,以便当他们调用SomeObject.someFunction()时,他们不知道SomeObject(或仅someFunction是否在本地存储和执行,或远程存储和执行。该理论认为这使编程更简单。

现实情况不同,因为在进行本地函数调用(即使您使用世界上最慢的解释语言)和:

之间存在巨大差异
  • 通过代理对象进行调用
  • 序列化您的参数
  • 建立网络连接(如果尚未建立)
  • 将数据传输到远程代理
  • 让远程代理恢复数据并代表您调用远程功能
  • 序列化返回值
  • 将返回值传输到本地代理
  • 重新组装序列化数据
  • 从远程函数
  • 返回响应

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

  • 您可能无法实例化本地代理
  • 您可能无法实例化远程代理
  • 代理可能无法连接
  • 您发送的参数可能无法完整或完全
  • 远程发送的返回值可能无法使其完好无损或完全

所以现在你的RPC调用“就像一个本地函数调用”有一个完整的额外故障条件,你在进行本地函数调用时不必反击。抽象再次泄露,甚至更难。

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

答案 6 :(得分:3)

django ORM many-to-many example中的一个示例:

在示例API用法中请注意,在将“发布”对象添加到“多对多”属性之前,需要.save()基础文章对象a1。请注意,更新多对多属性会立即保存到基础数据库,而在调用.save()之前,更新单数属性不会反映在数据库中。

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

答案 7 :(得分:0)

在某个时候,这将取决于您的规模和执行情况,您需要熟悉抽象框架的实现细节,才能理解其为何如此行事它表现得很好。

例如,考虑以下SQL查询:

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

这是替代选择:

SELECT * FROM student_details;

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

这是一个简单的例子,但最终回到了乔尔·斯波斯基(Joel Spolsky)的话:

  

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

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

答案 8 :(得分:0)

什么是抽象?

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

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

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

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

泄漏抽象

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

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

换句话说,仅凭飞行员仅在不知道其他任何内容的情况下控制方向盘是不够的.........不,......她必须了解潜在的风险和局限性在他乘飞机之前,......她必须知道飞机的工作原理以及飞机的飞行方式;他必须知道 实施细节 .....她必须知道,过分猛烈的拉动会导致失速,或者过分着陆会破坏飞机。

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

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

以下是编码示例:

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

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

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

答案 9 :(得分:-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 []。消费者必须理解对象数组的第一个字段具有颜色值,第二个字段是模型值。这里抽象已从库泄露到消费者代码。

其中一个解决方案是返回一个封装Device的Model和Color的对象。消费者可以调用该对象来获取模型和颜色值。

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

答案 10 :(得分:-2)

泄漏抽象就是封装状态。泄漏抽象的非常简单的示例:

$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;
    }
}

更多描述here