Geo*_*nne 81 memory-leaks abstraction programming-languages functional-programming leaky-abstraction
"泄漏抽象"一词是什么意思?(请用例子解释.我经常很难完成一个理论.)
Mar*_*ase 90
这是一个meatspace示例:
汽车有司机的抽象.最纯粹的形式是方向盘,加速器和制动器.这个抽象隐藏了很多关于引擎盖下的细节:引擎,凸轮,同步带,火花塞,散热器等.
关于这种抽象的巧妙之处在于,我们可以用改进的部件替换部分实现,而无需重新训练用户.假设我们用电子点火器更换分配器盖,我们用可变凸轮替换固定凸轮.这些变化提高了性能,但用户仍然使用方向盘并使用踏板启动和停止.
这实际上非常了不起......一个16岁或80岁的人可以操作这个复杂的机器而不太了解它的内部工作原理!
但是有泄漏.传动是一个小泄漏.在自动变速箱中,您可以感觉到汽车在切换档位时会瞬间失去动力,而在CVT中,您可以感受到平稳的扭矩.
还有更大的泄漏.如果您将发动机转速过快,可能会对发动机造成损坏.如果发动机缸体太冷,汽车可能无法启动或性能不佳.如果你同时摇动收音机,前大灯和AC,你会看到你的汽油里程下降.
tva*_*son 45
它只是意味着您的抽象公开了一些实现细节,或者您在使用抽象时需要了解实现细节.该术语归功于Joel Spolsky,大约2002年.有关更多信息,请参阅维基百科文章.
一个典型的例子是网络库,它允许您将远程文件视为本地文件.使用此抽象的开发人员必须意识到网络问题可能会导致本地文件失败的方式失败.然后,您需要开发代码来处理网络库提供的抽象之外的错误.
Jef*_*nal 11
这是.NET开发人员熟悉的一个例子:ASP.NET的Page类试图隐藏HTTP操作的细节,特别是表单数据的管理,这样开发人员就不必处理发布的值(因为它会自动将表单值映射到服务器控制).
但是,如果你超越最基本的使用场景,Page抽象就会开始泄漏,除非你理解了类的实现细节,否则很难处理页面.
一个常见示例是动态地向页面添加控件 - 除非您在恰当的时间添加控件,否则不会为您映射动态添加的控件的值:在基础引擎将传入的表单值映射到适当的控件之前.当你必须了解它时,抽象已经泄露.
……那是在理想的世界里。事实上,驾驶飞机要复杂得多。因为许多细节并没有被“抽象掉”。
实际上,飞行员必须担心很多事情:风速、推力、迎角、燃料、高度、天气问题、下降角。计算机可以帮助飞行员完成这些任务,但并不是所有事情都是自动化/简化的……并不是所有事情都是“抽象出来的”。
例如,如果飞行员在立柱上拉得太用力 - 飞机会服从,但随后飞机可能会失速,这真的很糟糕。
换句话说,飞行员在不了解其他任何事情的情况下仅仅控制方向盘是不够的......不......飞行员必须了解潜在的风险和局限性在飞行员驾驶飞机之前……飞行员必须知道飞机是如何工作的,以及飞机是如何飞行的;飞行员必须了解实施细节......拉得太用力会导致失速,或者着陆太陡会毁坏飞机等。
这些东西并没有被抽象掉。很多东西都是抽象的,但不是一切。抽象是“泄漏的”。
......你的代码中也是同样的事情。如果你不知道底层的实现细节,那么你就会遇到问题。
ORM 消除了处理数据库查询时的许多麻烦,但如果您曾经做过类似的事情:
User.all.each do |user|
puts user.name # let's print each user's name
end
Run Code Online (Sandbox Code Playgroud)
然后你会意识到这是杀死你的应用程序的好方法。您需要知道,User.all与 2500 万用户进行通话将会增加您的内存使用量,并且会导致问题。您需要了解一些基本细节。抽象是有漏洞的。
嗯,在某种程度上,这是一个纯理论上的事情,虽然不是不重要的.
我们使用抽象来使事情更容易理解.我可能会使用某种语言对字符串类进行操作,以隐藏我正在处理作为单个项目的有序字符集的事实.我处理一组有序的字符来隐藏我正在处理数字的事实.我处理数字来隐藏我正在处理1和0的事实.
漏洞抽象是一种不隐藏其隐藏的细节的抽象.如果在Java或.NET中对5个字符的字符串调用string.Length,我可以从5到10获得任何答案,因为实现细节,这些语言称为字符的是真正的UTF-16数据点,它们可以代表1或.5的角色.抽象已经泄露.虽然没有泄漏,但意味着找到长度要么需要更多的存储空间(存储实际长度),要么从O(1)变为O(n)(以计算实际长度).如果我关心真正的答案(通常你并不真的),你需要研究真实情况的知识.
更有争议的案例发生在诸如方法或属性允许您进入内部工作的情况下,无论它们是抽象泄漏,还是明确定义的方式转移到较低的抽象层次,有时可能是人们不同意的问题.
我将继续使用RPC提供示例.
在RPC的理想世界中,远程过程调用应该看起来像本地过程调用(或者说故事如此).对程序员来说应该是完全透明的,这样当他们打电话时,SomeObject.someFunction()他们不知道SomeObject(或仅仅someFunction是)就是在本地存储和执行,还是远程存储和执行.该理论认为这使编程更简单.
现实是不同的,因为在进行本地函数调用(即使您使用世界上最慢的解释语言)和以下之间存在巨大差异:
仅在时间上,大约有三个(或更多)数量级的差异.这三个+数量级将在性能上产生巨大差异,这将使您的过程调用的抽象泄漏相当明显,这是您第一次错误地将RPC视为真正的函数调用.进一步的实际函数调用,除了代码中的严重问题之外,在实现错误之外的失败点很少.RPC调用具有以下所有可能的问题,这些问题会因为失败案例而超出您对常规本地调用的期望:
所以现在你的RPC调用"就像一个本地函数调用"有一个完整的额外故障条件,你不必在进行本地函数调用时遇到这种情况.抽象再次泄露,甚至更难.
最后,RPC是一个糟糕的抽象,因为它在每个级别都像筛子一样泄漏 - 成功时和两者都失败时.