HeadlinesBriefing favicon HeadlinesBriefing.com

确定性核心 非确定性外壳架构

Hacker News •
×

十四年前,Gary Bernhardt提出了‘功能核心 命令式外壳’的概念。虽然这个想法在计算机领域并非全新,但其清晰的阐述具有很大的价值,为我们讨论现有系统中的测试和确定性提供了 excellent 的基础。简而言之,功能核心 命令式外壳架构将代码分为两个部分。功能核心是纯函数式的——即没有IO操作,也没有破坏性的状态更新。它关注应用程序的业务逻辑。命令式外壳路径相对较少,但维护状态、协调外部依赖并处理外部世界——即IO操作。它的工作是用值查询核心,接收值作为某种黑箱决策的结果,并用于与外部世界交互;无论是写入数据库、发送请求还是更新GUI。该模型中的核心与外壳具有 distinct 的特征:核心 外壳 做出决策 协调依赖 许多分支执行路径 较线性的执行 隔离于世界 中 集成于世界 这使得核心非常适合测试。由于它是纯函数式的,相同的输入总是会得到相同的输出。由于它是隔离的,没有可mock或stub的东西。并且由于它处理复杂的业务逻辑,测试可以向我们大量了解系统行为。功能纯度与确定性 描述纯函数式可被测试的属性的简短方式是它们是确定的。也就是说——给定一系列输入,纯函数总是返回相同的输出系列;它们的行为是可重复的。但纯函数式编程并非唯一通往此目的的途径。如果我们稍作倾听,我们可以看出一系列值和一系列赋值是以不同方式表达同样东西的,状态机可以为我们带来相同的好处。考虑以下代码:function add(ns) {return ns.reduce((a, b) => a + b, 0)} class Add Machine {#state = 0 transition(input) {this.#state += input} get state() {return this.#state}} add函数容易理解;它是纯的,因此是确定的。但Add Machine也是确定的——给定对transition函数的相同调用序列,Add Machine会返回相同的状态。它是命令式的,这并不改变这一点。 const output = add([1, 2, 3]) const a = new Add Machine() a.transition(1) a.transition(2) a.transition(3) const output = a.state 纯函数式编程是一种良好的范式,但由于语言或性能考虑,它并不总是实用——我不会在C语言中尝试这样做!但从纯函数式松化要求到仅仅是确定性,我们保留了功能核心 命令式外壳的可测试性优势,同时扩大了其适用范围。因此,本文的标题:确定性核心 非确定性外壳。确定性可能比函数纯度更抽象。你如何辨认它?我发现从非确定性的东西开始倒推更容易。这里有一些常见的非可重复行为的例子:调用未初始化的随机数生成器 异步和多线程操作 网络通信 与其他进程的通信 读取 写入本地存储 数据库交互 向操作系统请求日期或时间 所有这些都属于非确定性外壳。当你在业务逻辑中发现它们时,就有一个自然的目标用于碎片化——要么在它们周围将函数分为两个,要么将其提升到上一层并注入结果作为参数。将外壳比喻字面化思考是有意义的;它应该环绕逻辑,查询应用程序的心脏以获取所需内容。与你现有的东西一起工作 这一切听起来不错,你可能会想,但对我这个在遗留代码中滋生的开发者来说,它有什么用处呢?