你有没有把东西借给别人只是为了以不同的状态归还给你?
本周我在工作中看到了这种情况。一位用户报告说,涉及一系列 API 调用的 SDK 方法在第一次调用后失败。调查显示,SDK 使用的 HTTP 客户端正在悄悄修改 API 调用之间共享的标头对象。
该软件相当于借用朋友的汽车,然后随便用不同颜色的引擎盖还车。
啧啧啧。
解决方法是,至少在 HTTP 客户端修复错误之前,先防御性地复制标头对象,然后再将其传递给客户端。但整个事件让我开始思考可变性和代码礼仪。
你如何处理输入问题。
可变性是一个特性,而不是一个错误。
对于 HTTP 客户端,改变标头对象意味着更少的内存分配和更高的性能。这是可变性的一大好处。但它可能带来重大风险。
对我来说,底线是:
这是 HTTP 客户端犯的错误。
输出应该被清楚地记录下来,包括像副作用这样的隐含输出。 Julia 的惯例是附加!
函数名称的符号会改变它们的参数,这让我印象深刻,因为它是一种有用的模式,可以大声揭露这种行为。的!
每次使用该功能时都会提醒用户注意副作用。
不过,从不改变对象是有充分理由的。强制不变性解决了很多令人头疼的问题,因为它:
- 消除整类虫子和脚枪。
- 增加对代码正确性的信心,尤其是在可变性需要远距离操作的复杂架构中。
- 使并发更容易处理。
以下是有关不变性优势的一些资源:
Programming Safety Tips: Why You Should Use Immutable Objects by Charles Kann 展示了如何使用不变性轻松修复难以捉摸的内存错误。
The Sins of Perl Revisited by Mark-Jason Dominus,讨论了术语“远距离操作”的起源。
麻省理工学院软件构造课程中的线程安全阅读对不变性、线程安全和并发性进行了很好的概述。
NASA 的十的力量:Gerald Holzmann 编写的开发安全关键代码的规则给出了防御性编程的基本原理。
您是否应该严格保持代码的不变性是您必须自己或与您的团队一起回答的问题。不过,毫无疑问,不变性有帮助的一种情况是处理不受信任的代码。
关键是要做好防守。
在某种程度上,HTTP 客户端的事件对我来说是及时的。
我一直在阅读 Eric Normand 的Grokking Simplicity 。本书着眼于函数式编程在实践中的应用方式,通过关注函数式概念如何改善现实世界的系统而在很大程度上避开了理论。不到两周前,我阅读了关于防御性复制的部分。
这正是 SDK 团队处理 HTTP 客户端问题的方式:
让我澄清一下不受信任的代码是什么意思。
我内心的悲观主义者认为所有代码都是不可信的。但这种假设在编写代码的日常业务中并不总是可行的。至少,我认为不受信任的代码是任何代码:
- 显示不良行为,例如 HTTP 客户端,或
- 无法验证是否符合您的期望,或者修改它的成本太高,遗留代码通常就是这种情况。
防御性复制在这两种情况下都有帮助。
这是 SDK 团队用 Python 表达的相同问题:
>>> headers = {"some_key": "some_value"} >>> response = http_client.get( "/endpoint", headers=headers ) >>> headers {'some_key': 'some_value', 'boo!': 'did\'t expect me, did ya?'}
关键"boo!"
价值"didn't expect me, did ya?"
意外地出现在headers
字典中。当在另一个请求中使用headers
时,意外的内容导致请求失败。
要在此处实施防御性复制,请在将标头传递给http_client.get()
之前复制headers
:
>>> headers = {"some_key": "some_value"} >>> from copy import deepcopy >>> response = http_client.get( "/endpoint", headers=deepcopy(headers) ) # ^^^^^^^^^^^^^^^^^ >>> # / >>> # A copy of `headers` is sent to the client >>> headers {'some_key': 'some_value'}
headers
的内容没有改变,因为标headers
的副本——一个用Python 的deepcopy()
函数制作的完全不同的对象——被发送给了客户端。
现在您可以安全地将headers
的新副本传递给下一个请求。客户端仍然改变副本,但原始对象受到保护。这种保护是有代价的:增加内存开销。但在这种情况下,以及许多其他情况下,可靠性的提高证明了权衡。
防御性复制不仅仅保护从您的代码传递到不受信任代码的对象。它还可以防止更改从不受信任的源传递到代码中的可变对象。
从 HTTP 客户端的角度来看,标头字典源自使用客户端 API 的代码——不可信代码的典型示例。在改变参数之前复制传递给客户端的参数可以为每个人省去一些麻烦。
也许这只是我,但这似乎也是一件体贴的事情。
接下来要读什么
了解如何在您自己的代码中识别和删除隐式输出,例如本周示例中的变异标头对象:
深入挖掘
在 Eric Normand 的书Grokking Simplicity中了解更多关于防御性复制和其他强制不变性的策略。
从Manning * 获得即时访问,或从亚马逊* 购买印刷版。
*附属链接。有关更多信息,请参阅我的关联披露。
原文: https://davidamos.dev/never-modify-inputs-without-permission/