计算机科学中的难题

2025-06-07

计算机科学中的难题

如果您在 IT 领域拥有多年的经验,您可能偶然发现过以下引言:

计算机科学中只有两件难事:缓存失效和命名事物。

——菲尔·卡尔顿

然后,因为这句话太精彩了,它就演变成了:

不过,我认为最初的引言具有误导性。计算机科学中有很多难题。这篇文章旨在描述其中的一些。

缓存失效

在我的职业生涯中,我遇到过一些缓存用例。当时,主要是为了缓存 Hibernate 实体。我只实现过一次自己的缓存。当时我使用了一个简单的缓存HashMap,因为它用于批处理作业,缓存大小很小:不需要进行失效操作。

大多数缓存实现都采用了 Cache-Aside 策略。在 Cache-Aside 策略中,应用程序会尝试从缓存中加载数据。如果缓存中有数据,则返回;如果没有,则应用程序从数据源(通常是数据库)读取数据。

虽然数据存储在缓存中,但它们在数据库中可能会发生变化。简而言之,随着时间的推移,缓存中的数据与真实数据源出现差异的概率会增加。因此,我们有时需要将数据与真实数据源同步。这可以通过清除缓存中的数据来实现——缓存失效(也称为TTL)

TTL 指定条目的有效时间。超过有效时间后,缓存会将其删除,下次读取时将从真实数据源加载数据。

在这种情况下,棘手的事情是选择正确的 TTL:

  1. 如果事实源中的参考数据在失效之前发生变化,客户端将读取过时的数据
  2. 如果在更改之前发生失效,则会发生不必要的重新加载。

本质上,TTL 越小,读取陈旧数据的机会就越少,但缓存的用处就越小。

命名事物

如果你有过开发经验,你大概会相信命名确实很有挑战性。如果没有,我们再引用一句名言:

程序是供人类阅读的,只是偶尔供计算机执行

—— 唐纳德·克努斯

这是另一个稍微更具煽动性的说法:

即使是傻瓜也能写出计算机能理解的代码。优秀的程序员能写出人类能理解的代码。

——马丁·福勒

例如,这里有一些乱码:

data class Foo(val bar: String)

class Baz : IllegalArgumentException()

data class Qux(val quux: Double, val foo: Foo) {
    operator fun plus(qux: Qux) =
        if (foo != qux.foo) throw Baz()
        else Qux(quux + qux.quux, foo)
}
Enter fullscreen mode Exit fullscreen mode

由于使用了 API,即IllegalArgumentExceptionplus(),您可能可以推断出代码的作用。但是,正确重命名类和字段可以揭示我们的意图:

data class Currency(val symbol: String)

class MismatchCurrencyException : IllegalArgumentException()

data class Amount(val number: Double, val currency: Currency) {
    operator fun plus(amount: Amount) =
        if (currency != amount.currency) throw MismatchCurrencyException()
        else Amount(number + amount.number, currency)
    }
}
Enter fullscreen mode Exit fullscreen mode

我在做项目的时候,跟业务沟通的时候,总是提醒大家注意以下几个问题:

  1. 用不同的词语来描述同一个现实
  2. 用同一个词来描述不同的现实

第二种情况更糟糕,因为你可能以为自己和业务部门或其他开发人员讨论的是同一件事,但事实并非如此。如果谈判最终达成了一致,但各方对现实情况却各执一词,那么未来灾难的根源就在于此。

在口头交流中,可以询问某些词语的含义。但在代码中,却不行!因此,类和变量的命名必须传达准确的含义。

这很难,因为你需要准确而又不冗长。

日期、时间和时区

我已经写过关于日期和时间陷阱的文章。总结一下:

  • 从儒略历到格里高利历的转变发生在不同的国家
  • 一些国家的时区与其地理位置并不完全一致
  • 有些国家实行夏令时,有些则没有。更糟糕的是,有些国家曾经实行夏令时,现在却不再实行了。
  • 各国有时会更改时区。虽然这种情况并不常见,但发生的频率却比大多数人想象的要高。
  • 并非所有时区都相隔一小时。例如,印度是 UTC+5:30,但三个时区之间相隔 45 分钟。

估计

软件开发项目中的估算非常难以准确,以至于一些开发人员发起了“不估算”运动。我不会深入探讨不做估算的利弊;我的观点是,估算一个非同寻常的软件项目本身就很有挑战性。

我认为已经有很多书专门讨论过为什么估算如此苛刻以及如何改进估算。让我们总结一下原因:

  • 不仅仅是软件项目:

    许多参与软件项目但不懂其运作方式的人,总是急于将软件项目与房屋建造进行比较。不幸的是,“房屋”一词涵盖了不同程度的工业化。人们所指的房屋是现成的建筑,其定制程度很低甚至为零。软件开发项目则处于天平的另一端,类似于独一无二的建筑杰作。有关更详细的解释,请参阅以下关于房屋建造和软件开发项目的文章。

  • 过程中遇到的问题:

    项目启动前会提供估算,所有参数均已准备就绪。然而,尽管风险管理会考虑已知的已知因素和已知的未知因素,但无法预测未知的未知因素。由于软件项目的状态瞬息万变,几乎可以肯定会发生意外情况,从而推翻我们之前的估算。

  • 估算的性质:

    从定义上讲,估算只是猜测。问题在于,大多数人会将其视为截止日期。在这种情况下,组织会专注于遵守截止日期,而不顾及任何权衡利弊——这注定会失败。

    我们将要求估价并将其视为最后期限!

分布式系统

一台计算机,即使是多核计算机,也能做很多事情。向计算机添加更多资源会迅速达到收益递减点。此时,您除了将负载分散到多台计算机上之外别无选择。欢迎来到分布式系统的世界!

分布式系统的问题在于它很容易“出错”。以下是一些关于分布式系统的谬误:

  1. 网络可靠
  2. 延迟为零
  3. 带宽无限
  4. 网络是安全的
  5. 拓扑结构没有改变
  6. 有一名管理员
  7. 运输成本为零
  8. 网络是同质的。

——维基百科,分布式计算的谬误

关于分布式系统已经有很多书籍和论文。能够正确理解这些内容的人值得我敬佩。

在我的职业生涯中,我偶然发现了两个分布式系统问题:

  • 双写入
  • 领导者选举

双写入

想象一个有两个分布式数据存储的系统。我们要求它们必须包含相同的数据。

这属于双重写入问题。在单个数据库中,我们可以通过数据库事务来解决。在两个不同的数据库中,可以使用两阶段提交,即使它们可能不可靠。如果您需要写入的存储无法像在微服务架构中那样加入2PC,则需要依赖补偿事务。补偿事务非常脆弱且难以正确实现(即使可以实现)。

从理论角度来看,CAP告诉我们,我们只能在一致性、可用性和分区容错性这三个能力中选择两个。实际上,这根本就是无可奈何的选择。由于系统是分布式的,我们需要选择P;由于现代需求不允许我们进行阻塞,我们需要选择A。权衡的结果是一致性:两个存储都会随着时间的推移收敛到相同的状态

在解决这个问题的过程中,我发现了变更数据捕获 (CDC) 。CDC背后的理念是将更新发送到一个存储,并将新状态的差异流式传输到另一个存储。自己实现它并非易事。我建议使用现有产品:我过去曾成功地在我的演示中使用过Debezium 。

领导者选举

分布式系统依赖于多个节点,并且必须在这些节点之间进行协调。有些系统依赖一个称为“领导者”的特定节点来管理其他节点,而有些系统则没有领导者

大多数现代实现都是基于领导者的:无领导者的实现似乎不太可靠,不过说实话,鉴于我撰写本文时的理解深度,我无法说出原因。这样的实现要求所有节点就哪个节点是其中的领导者达成一致——即共识。当网络分区发生时,某些节点无法与其他节点通信。在分区中达成共识相对容易;当网络重新合并时,达成共识会更具挑战性,因为必须在所有先前的领导者中选出一个。

这就是Paxos算法(或者说 Paxos 算法家族)诞生的原因。然而,专家们似乎认为 Paxos 算法实现起来容易出错:Raft算法是一个更具吸引力且更易于实现的替代方案。无论如何,更容易并不意味着容易

证明代码没有错误

传统软件工程强制进行测试以避免错误。然而,无论您喜欢哪种方法——单元测试、集成测试、端到端测试,还是三者兼而有之——都无法保证您的代码没有错误。事实上,尽管代码覆盖率高达 100% ,但在生产环境中发现错误仍然相当普遍。获得无错误代码的唯一可靠方法是对其进行证明。这需要扎实的数学基础和能够进行形式化证明的编程语言

确实存在几种这样的语言。不幸的是,它们仍然只属于学术界;我听说过的有CoqIdrisIsabelle

除非它们中的任何一个能够进入主流行业,否则编写无错误的代码将是计算机科学中最困难的事情之一。

概括

写“计算机科学中只有两件难事”是一个强有力的论断。在这篇文章中,我尝试列举了我在职业生涯中遇到的几件难事。我相信还有很多其他的:我会对你们遇到的那些感兴趣。

进一步来说:

最初于2022 年6 月 26 日发表A Java Geek

文章来源:https://dev.to/nfrankel/hard-things-in-computer-science-4d0k
PREV
devchallenges.io - 16+ 免费项目,设计精美
NEXT
使用 create-t3-app 构建全栈应用程序