如何编写 IMMUTABLE 代码,避免再次陷入调试困境

2025-05-25

如何编写 IMMUTABLE 代码,避免再次陷入调试困境

在我的职业生涯中,我用过各种不同的语言编写过生产代码,包括 Haskell、Scala、Go、Python、Java 和 JavaScript。虽然每种语言都有其明显的优势,但作为一名跨多种不同范式的多语言人士,我编写代码的方式发生了改变。某些技能和概念无论使用哪种语言编写,都是可以迁移的。我认为不可变性就是其中一个关键概念。通过编写不可变代码,可以使程序更易于推理、编写和调试。

这里,我们来看一下三件事:

  • 海象吃奶酪可以解释不变性是如何运作的,
  • 为什么你应该关心,以及
  • 为什么反对不可变代码的反驳不值得考虑。

什么是不变性?#

“随着时间的推移而不变或无法改变。”——牛津语言定义。

不变性是指对象或变量一旦创建,其值就永远不应改变或被任何事物更新。对于对象或类而言,这也包括所有字段;确切地说,任何事物都不应改变!该对象实际上是只读的。

不过,用这种风格编写代码有时需要转变思维方式。我第一次遇到这个想法时,觉得它完全没有意义,简直疯了。我当时很困惑,想立刻把它全部拆开,用我熟悉的方式编写。Gary Bernhardt 在他关于“界限”的演讲中,举了一个绝佳的例子,解释了为什么这种风格感觉如此不对劲。

他谈到了给海象喂奶酪。

海象

在一个可变的版本中,我们可能会指示每只海象吃一些奶酪。然后,这些奶酪会被添加到它们的胃里。这很有道理,对吧?

在不可变版本中,我们必须执行一项令人费解的操作。为了喂海象,我们必须:

  • 创建一个与旧胃相同的全新胃,但其中含有一些奶酪。
  • 然后,创造一只新的海象,它与旧的海象相同,只是胃被替换了。
  • 然后,把所有旧海象都扔掉。

乍一看,这听起来很疯狂,但请继续听我说 - 让我们看看是什么让编写这样的代码变得有价值。

它如何避免调试时的痛苦?#

您是否遇到过:

  • undefined is not a function在 JavaScript 中?
  • NullPointerException在 Java 中?
  • SegFault在 C/C++ 中?
  • panic在 Go 中?
  • NoneType has no attribute foo在 Python 中?

如果你曾经使用过这些语言中的任何一种,那么你很可能遇到过类似的问题。问题是,所有这些错误都是由同一件事引起的:数据缺失或为空。

缺失数据和空值绝对是最难追踪和修复的错误类型之一。过去,我花了无数时间仔细检查 JavaScript 代码,试图找出为什么我认为应该存在的值却不存在。为什么我的应用程序在一切看似正常的情况下突然崩溃了。Tony Hoare 爵士甚至将 null 描述为“价值数十亿美元的错误”,因为它导致了无数的错误、安全漏洞和崩溃。

让我们一致同意:空值可能是邪恶的。

这些 bug 之所以难以排查和修复,是因为其结果(异常)与原因(引入 null)相差甚远。实际上,在引入 null 之后的某个时间点,都会抛出空指针错误,而且我们undefined在访问某个属性时遇到的错误,与我们预想的设置位置相差甚远。调试变成了一种反复阅读代码,直到找到原因的过程。

代码中发生的状态变化越多,引入这些错误的地方就越多。相反,我们可以尝试减少任何代码的“表面积”。代码库中的变更越少,错误的“表面积”就越小。这会导致错误减少。

如果一个值只被设置一次,那么只有一个地方可能出错。如果在对象传递过程中对其进行更改,那么任何一个地方都可能引发潜在问题。如果我们的一只海象出了问题,我们知道这只能发生在我们制造最新的一只海象(包括新的胃)时。更早的一只海象不可能有问题——它们早已消失了。

因此,实际上,不变性,或者永远不会改变值,确实可以使我们避免陷入调试的困境。

为什么性能不是问题

一些眼尖的人可能会想:“之前的那些海象……把它们都扔进垃圾桶,再造新的,不是很贵吗?这不会让我的代码变慢吗?”

答案并不简单。

你说得对,一直丢弃海象并非完全必要,而且它有时会让程序运行速度稍微慢一些。不过这里的关键词是“有时”。有时编译器足够聪明,会用更高效的方法来优化这种行为。有些语言甚至默认使用不可变性。不可变性在多线程或并行化方面也有很大的优势,因为它允许无锁共享,并且确保值不会被更改

尽管如此,即使用你使用的语言创建新的海象速度较慢,分配新对象的成本与应用程序中的其他操作相比几乎肯定是微不足道的。除非你正在进行基准测试并主动测量性能,否则你几乎肯定不应该在意这一点。

结论

不变性是编程中一个强大的工具。它使我们能够编写更易于调试和推理的代码。这需要一些思维上的转变,但根据我的经验,这绝对值得。

尝试一下,然后告诉我你的想法:)。


还在寻找其他方法来提升代码的清晰度吗?不妨看看我关于“永远不要使用 else 语句”的文章


喜欢这篇文章吗?想分享你的想法吗?觉得这篇文章有帮助吗?不同意我的观点吗?请在 Twitter 上留言告诉我

文章来源:https://dev.to/dglsparsons/how-to-write-immutable-code-and-never-get-stuck-debugging-again-4p1
PREV
React 和 JavaScript 中的未来无限滚动
NEXT
3 个出色的 REACT HOOKS,让你的代码井井有条