30 秒看懂
在 Effective ML 演讲的第二部分,讲者认为,可读且健壮的软件应明确表达失败,并抽象重复模式,同时避免引入不必要的复杂度。他还讨论了可变状态、代码审查、团队规模、交易中人的参与,以及历史上选择 OCaml 的原因。
- 1option 与异常根据调用者的需求,查找操作可以返回 option,也可以提供名称明确的异常版本。
- 2样板代码掩盖变化重复代码难以一致更新,审查起来也很枯燥。
- 3复杂度才是敌人如果精巧的类型级不变量让接口无法理解,它就没有帮助。
- 4作为通信的状态修改共享可变状态将程序的不同部分连接起来,可能使局部推理更困难。
- 5审查关键代码讲者介绍了交易系统关键路径上的多人审查和测试。
要点建议
- 当调用者需要处理预期中的失败时,应在类型中表达失败,并清楚命名会抛出异常的替代函数。
- 抽象重复模式,让共同的行为和有意义的变化都清晰可见。
- 如果额外的静态保证让接口复杂得难以理解,它并不一定更好。
- 避免不必要的状态修改,同时认识到有用的程序需要副作用。
- 代码审查、测试和规模较小且目标清晰的项目有助于构建可靠系统。
- 语言选择取决于应用的性能和运行需求。
按章节阅读整理后的全文
明确表达错误
讲者对比返回 option 的接口与异常,并解释为什么错误处理选择取决于失败的后果。
有效使用 ML 编程的关键思想之一,是让被调用函数中的常见错误清晰而明确。人们常说只应在异常情况下使用异常,这背后有两个顾虑:异常产生非局部控制流,而隐式错误处理容易让人忽略失败。
我并不完全反对异常;它们很有用。但失败是否属于异常情况取决于调用者的上下文。一段代码可能确信列表查找总能找到元素,而另一个调用者可能确实不知道元素是否存在。
列表接口可以同时提供返回 option 的 find 和可能抛出异常的 find_exn。前者在类型中表明结果可能不存在,迫使调用者处理;后者通过名称表明可能发生异常。我们的约定是,对于可能因输入而经常失败的操作,使用 _exn 后缀。
通常返回 option 的版本更好,因为类型系统承载了失败的可能性,而不只是依靠命名约定。我们从实践中学到了这一点,并按这种风格修改了大量代码,以提高健壮性。
上下文对错误处理设计很重要。MLton 编译器的作者 Stephen Weeks 加入 Jane Street 后,发现自己的看法发生了变化:编译器因异常退出并打印堆栈跟踪固然不便,但交易系统在交易日中途崩溃是严重得多的问题。
有些接口完全避免了这个问题。fold 可以处理空列表,不像要求列表头部存在或查找成功的操作。错误行为简单的函数更容易放心使用;明确的失败条件也让其余情况更容易推理。
避免样板代码
重复模式可能掩盖错误,也难以更新。ML 的抽象可以清楚表达共同模式及其变化。
接下来,要避免样板代码:反复复制相似代码,并做一些细小但重要的修改。即使优秀的程序员也会这样做,因为语言可能没有提供容易表达该模式的抽象,或者他们不知道如何表达这种抽象。
重复造成两个问题。模式会演变,开发者修改时可能遗漏众多副本中的某一个。重复代码也很枯燥,难以审查;人们可能仔细检查第一处,却忽略后续副本中的重要差异。
对于这类模式,C 提供的抽象工具相对有限,常见选择是宏或复制代码。ML 提供函子和高阶函数。我建议对重复保持低容忍度:即使只有两份副本,也值得寻找抽象。
即使抽象让代码变长,也可能值得。局部函数将共同模式写一次,参数则显示哪些部分会变化。与几份几乎相同的代码相比,结果可能更明确、更容易理解。
控制复杂度
类型级保证和纯度很有价值,但过度精巧会让代码更难理解。目标是取得实际的平衡。
强大的类型系统带来相反的诱惑:因为有趣且可行,就不断编码更复杂的不变量。我见过一些接口,即使解释过,有经验的 ML 程序员仍无法理解。到了这一步,额外的静态保证可能增加了整体复杂度,而不是提供帮助。
软件工程的核心挑战是管理复杂度。学习巧妙的技术,在有帮助时使用它们,但也要有所克制。代码必须适合维护者理解,而不只是符合优雅的抽象模型。
副作用和状态修改也需要类似的平衡。应优先避免不必要的修改,因为共享引用是程序不同部分之间的通信通道。这些通道连接相距较远的代码,使孤立地理解一个组件更加困难。
不要把纯度变成宗教。有用的程序要与外部世界交互:读取 socket、写入文件或查看时钟都是副作用。Haskell 有纪律地分离纯代码和有副作用的代码,但并没有从有用程序中消除副作用。
将副作用推向程序外围可能有帮助,但前提是结果不能比直接的命令式方案更复杂。有时命令式算法更容易理解,或者不需要精巧机制就能获得良好性能。目标是构建满足任务所需的最简单系统。
关于工程与交易的问答
讨论涵盖代码审查、小团队和人在交易系统中的作用。讲者最后回顾了历史上选择 OCaml 的原因。
有人问到审查时,我解释说 Jane Street 将从交易决策到进入市场之间的代码视为关键路径。多个人——至少两人,通常三人——被指定在代码进入生产环境前审查变更,内部系统负责追踪审查。单元测试和回归测试也很重要,但审查是保持系统安全和一致的核心。
我们尽量将开发组织成目标清晰的小组项目,人数大致从一人到最多六人。较小、相对独立的部分可以装进个人的脑海,并组合成更大的系统。我认为让一个大团队直接共同处理同一块基础设施会很难管理。
有人问到自动化时,我说业务差异很大。有些工作涉及人们通过电话谈判,另一些系统则连续运行数小时,人们主要观察而不是不断修改。大多数系统都需要大量人的参与;公司并不是一台无人看管、自动赚钱的机器。
交易是一场博弈,每笔交易的另一端都有一个参与者。市场模型必须考虑对方。交易员和技术人员的注意力,是人和技术相结合的系统中的核心部分。
演讲时 Jane Street 已使用 OCaml 约九年,在此之前曾使用 Excel、Java 和 C# 等系统。选择这种语言部分是因为我熟悉它。OCaml 也提供了一种有用的平衡:代码具有声明式风格,同时编译策略相对直接且高效。
我们没有选择 Haskell 的一个原因是,以当时的需求来看,它的性能,尤其是空间使用,更难推理。它的实现已有改进,但为提高效率所需的工作会增加复杂度。语言选择取决于所构建系统的需求。
同频道的其他文章
1:48:29EN → 简体中文
1:36:37EN → 简体中文
1:05:45EN → 简体中文
1:25:00EN → 简体中文
1:16:29EN → 简体中文
27:39EN → 简体中文