将 Ifs 上推,Fors 下推 Nov 15, 2023

关于两条相关经验法则的简短说明。

将 Ifs 上推

如果函数内部存在 if 条件,请考虑是否可以将其移动到调用者处:

// GOOD
fn frobnicate(walrus: Walrus) {
  ...
}
// BAD
fn frobnicate(walrus: Option<Walrus>) {
 let walrus = match walrus {
  Some(it) => it,
  None => return,
 };
 ...
}

如上面的示例所示,这通常出现在前提条件中:函数可能会在内部检查前提条件,如果不满足则“不执行任何操作”,或者它可以将前提条件检查的任务推送到其调用者,并通过类型(或断言)强制执行前提条件。特别是对于前提条件,“上推”可能会变得像病毒一样传播,并最终减少总体检查次数,这是这条经验法则的动机之一。

另一个动机是,控制流和 if 语句很复杂,是 bug 的来源。通过上推 if 语句,您通常最终会将控制流集中在单个函数中,该函数具有复杂的分支逻辑,但所有实际工作都委托给直线子程序。

如果 你有复杂的控制流,最好将其放在单个函数的一个屏幕上,而不是分散在整个文件中。更重要的是,将所有流程集中在一个地方通常可以注意到冗余和死条件。比较:

fn f() {
 if foo && bar {
  if foo {
  } else {
  }
 }
}
fn g() {
 if foo && bar {
  h()
 }
}
fn h() {
 if foo {
 } else {
 }
}

对于 f,比对于 gh 的组合更容易注意到死分支!

这里的一个相关模式是我称之为“溶解 enum”的重构。有时,代码最终看起来像这样:

enum E {
 Foo(i32),
 Bar(String),
}
fn main() {
 let e = f();
 g(e)
}
fn f() -> E {
 if condition {
  E::Foo(x)
 } else {
  E::Bar(y)
 }
}
fn g(e: E) {
 match e {
  E::Foo(x) => foo(x),
  E::Bar(y) => bar(y)
 }
}

这里有两个分支指令,通过将它们拉上来,很明显这是完全相同的条件,重复了三次(第三次被具体化为数据结构):

fn main() {
 if condition {
  foo(x)
 } else {
  bar(y)
 }
}

将 Fors 下推

这来自面向数据的思想流派。少量的事物就是少量,大量的事物就是大量。程序通常处理成批的对象。或者至少热路径通常涉及处理许多实体。正是实体的数量使路径成为热路径。因此,引入“批量”对象的概念通常是谨慎的,并使对批量的操作成为基本情况,而标量版本是批量版本的特例:

// GOOD
frobnicate_batch(walruses)
// BAD
for walrus in walruses {
 frobnicate(walrus)
}

这里的主要好处是性能。大量的性能,在极端情况下

如果您要处理整批事物,则可以分摊启动成本,并且可以灵活地确定处理事物的顺序。实际上,您甚至不需要以任何特定顺序处理实体,您可以执行向量化/结构数组技巧,首先处理所有实体的一个字段,然后再继续处理其他字段。

这里最有趣的例子可能是基于 FFT 的多项式乘法:事实证明,同时评估多个点的多项式可能比大量单独的点评估更快!

关于 forif 的两条建议甚至可以组合!

// GOOD
if condition {
 for walrus in walruses {
  walrus.frobnicate()
 }
} else {
 for walrus in walruses {
  walrus.transmogrify()
 }
}
// BAD
for walrus in walruses {
 if condition {
  walrus.frobnicate()
 } else {
  walrus.transmogrify()
 }
}

GOOD 版本是好的,因为它避免了重复重新评估 condition,从热循环中删除了一个分支,并可能解锁了向量化。这种模式在微观层面和宏观层面都有效——好的版本是 TigerBeetle 的架构,在数据平面中,我们同时对批量的对象进行操作,以分摊控制平面中决策制定的成本。

虽然性能可能是 for 建议的主要动机,但有时它也有助于表达。 jQuery 在当时非常成功,并且它对元素集合进行操作。抽象向量空间的语言通常是比大量逐坐标方程更好的思考工具。

总而言之,将 if 语句上推,将 for 语句下推!