下划线排第三:键盘布局是给英文散文设计的,可我们打的是代码

August Dvorak 说过一句话,被引用了九十年:同一篇文章,打字员的手指在 QWERTY 上要走 20 英里,在他的键盘上只走 1 英里。

二十比一。这是个大到不需要解释的数字——如果它是真的,换布局就不是一个偏好问题,而是一个健康问题。

我手上正好有全套材料可以验这件事:Linux 自带每一种布局的权威定义,而硬盘上有几千万字符的真实文本。于是我把它测了一遍。

老式打字机的圆形键帽,金属杆从下面顶上来


# 一、先把布局定义拿到手

键盘布局这种东西最容易被"凭印象"搞错——网上流传的 Dvorak 键位图有好几个互相矛盾的版本,Colemak 有 DH 变体、Wide 变体、ISO 变体。我不想用记忆里的图,所以直接从系统里读:

/usr/share/X11/xkb/symbols/us

这是 xkeyboard-config 装的 XKB 符号表,里面有 basic(QWERTY)、dvorak、colemak、workman 四套完整定义,每个物理键位写着它的两级字符(key <AC11> { [ minus, underscore ] } 就是 Dvorak 主排最右那个键)。本文所有布局都从这个文件解析出来,包括 Workman 那种 include "us(basic)" 再覆盖的写法。

顺手验了一个流传很广的说法:"Colemak 只改了 17 个键"。按字符数,QWERTY 的 94 个可打印字符里有 34 个在 Colemak 上换了物理键位——34 除以 2 正是 17 个键。说法是对的。同样口径下,Dvorak 动了 66 个字符(33 个键),Workman 动了 42 个(21 个键)。

键位的物理坐标用 ANSI 标准建模:键距 1 单位 = 19.05 mm,四排的横向错位分别是 0 / 0.5 / 0.75 / 1.25 单位。指法用标准触摸打字分配,Shift 由对侧手的小指按。

# 二、四份语料,7000 万字符

语料 来源 文件数 字符数
英文散文 node_modules 里的 README/Markdown 3,088 19,799,408
JavaScript node_modules 里的 .js(排除压缩产物) 9,148 25,524,594
C /usr/include 下的 .h 2,042 25,050,314
我的博客 本站 src + scripts + services 242 1,141,568

前三个是"大样本",最后一个是"我自己的"。

# 三、代码不是英文

这是整件事的起点。把每份语料的字符频率算出来,只看可打印 ASCII,单位是千分比:

字符 JavaScript C 英文散文 我的博客
_ 4.91 44.57 1.65 1.94
- 4.91 2.29 13.93 24.27
; 11.97 5.48 1.67 1.25
( 16.41 7.91 9.28 10.82
{ 7.08 1.48 1.28 3.99
. 17.88 4.83 18.67 17.70
/ 8.26 6.62 24.99 20.38
= 13.50 1.66 5.93 6.40

C 那一列值得停下来看:下划线是 C 语言里第三常用的字符,仅次于空格(199.4‰)和 e(66.2‰)。它比 i(37.9)、n(36.0)、r(34.0)、a(33.4)都高。而在英文散文里,下划线只有 1.65‰——相差 27 倍。

原因不复杂:C 没有命名空间,标识符全靠 snake_case 区隔,_ 既是分隔符又是前缀约定(#ifndef _FOO_H、__attribute__、size_t)。这个字符在英文写作里几乎不存在。

屏幕上的代码,密密麻麻的标识符

再看 ;。在 JavaScript 语料里它是 11.97‰,英文散文里 1.67‰,7.2 倍。它在 JS 里比 k(4.3)、w(4.3)、x(5.8)都常用。

需要说明的是:这个 JS 语料来自 node_modules,大量是转译产物和老代码,分号用得很足。今天的手写风格(Prettier 默认不加分号、StandardJS)会显著拉低这个数。但下划线那一列不会变——那是 C 的命名规则决定的,不是风格问题。

# 四、四种布局,四份语料

指标是手指路径长度:每根手指记住自己上一次停在哪个键,只有同一根手指再次动作时才产生位移,累加所有位移(含 Shift 键的按压),最后摊到每个字符上。1 单位 = 19.05 mm。

语料 QWERTY Dvorak Colemak Workman
英文散文 1.1703 1.0782(92.1) 1.0903(93.2) 1.0873(92.9)
JavaScript 1.2869 1.1722(91.1) 1.2025(93.4) 1.2153(94.4)
C 1.3231 1.1497(86.9) 1.2318(93.1) 1.2846(97.1)
我的博客 1.3215 1.2333(93.3) 1.2694(96.1) 1.2666(95.8)

括号里是以 QWERTY 为 100 的归一化值。

Dvorak 确实赢,而且赢得很稳。但赢的幅度是 7.9%(英文)到 13.1%(C)。不是二十倍,是百分之八。二十比一和一点零九比一,差了两个数量级。

顺带说一句,Dvorak 那句"20 英里 vs 1 英里"我自己也能量化。写这个博客的 631,743 个可键入字符,在 QWERTY 上是 15,903 米的手指行程,Dvorak 是 14,842 米。少了 1.06 公里。而我写这个博客写了好几年。

# 五、一个 1936 年的布局,为什么在 C 上表现最好

表里有个细节比"Dvorak 赢了多少"更有意思:Dvorak 在 C 上的成绩(86.9)比它在英文散文上的成绩(92.1)还要好。 一个为英文设计的布局,在 C 语言上反而更接近它自己的理想。

这不是设计,是撞上的。看看单个键的成本就清楚了——下表是"按一次这个键,手指从原位出发再回来"的行程,单位同上:

字符 QWERTY Dvorak Colemak / Workman
; 0.000 2.236 2.062
_ 6.853 4.136 6.853
- 4.717 2.000 4.717
} 6.167 8.157 6.167
" 4.136 7.215 4.136
( ) 6.167 6.167 6.167
e 2.062 0.000 0.000

两个方向完全相反的事实:

QWERTY 的 ; 是免费的。 它就长在右手小指的原位上,敲它手指一动不动。Dvorak 把 ; 扔到了左下角(AB01),改成左手小指下探一排,成本 2.236。在 JavaScript 语料里,; 是第 22 常用的字符——QWERTY 在这一个键上占的便宜,被 Dvorak 和结构相同的 Colemak 一起丢掉了。

Dvorak 的 - 和 _ 在主排。 - 在 AC11,右手小指原位旁边,成本 2.000;_ 是同一个键加 Shift,4.136。QWERTY 和 Colemak 都得让右手小指伸到数字排最右端,_ 要 6.853。C 语料里 _ 占 44.57‰,44.57‰ × 2.717 单位的差价——光是这一个字符,就解释了 Dvorak 在 C 上多出来的那 5 个百分点。

同时它也有反向的账:} 在 Dvorak 上要 8.157(QWERTY 6.167)," 要 7.215(QWERTY 4.136)。Dvorak 把常用标点从主排挪去了别处,换来了 -/_。对英文来说这笔交易划算,对写 C 的人来说更是白捡。

所以"Dvorak 在 C 上表现最好"是一句很容易被误读的话。它不代表 Dvorak 为代码做了什么,只代表 1936 年那次为英文做的最优化,恰好在 1972 年发明的语言上撞对了运气。

一台 Dvorak 布局的实体键盘

# 六、如果真的为代码设计一个布局呢

上面都是评价别人做的布局。更有意思的问题是:如果不预设"目标是英文",直接对代码语料做最优化,会得到一个什么样的键盘?

我写了个模拟退火。搜索空间是全部 94 个可打印字符在 94 个物理格子(47 个键 × 2 级)上的任意排列——QWERTY、Dvorak、Colemak 都只是这个空间里的点,所以优化器没有被限死在某个布局的 pairing 结构里。目标函数是同指二连的位移之和,加上一个同指惩罚(0.5 单位)和对侧手 Shift 的成本。每份语料跑 300 万次交换,四个随机种子取最好。

给 JavaScript 优化出来的布局长这样(只画未按下 Shift 的那一层):

  R  I  b  0  h  S  g  w  E  v  C  =  (
  ,  s  i  _  n  f  '  o  t  U  B  u  :
   r  d  e  F  l  "  c  a  m  T  .
  ;  x  y  /  }  p  {  )  k  P

它把 . 和 " 放上了主排,把 _ 放在左手食指正上方,把 ; 塞进左下角。看着完全没有道理——但这正是重点:它是从 JavaScript 的字符转移概率里长出来的,不是从人的审美里长出来的。

然后把三份语料各自优化出的布局,和四种人类设计的布局,放在一起交叉评估:

语料 QWERTY Dvorak Colemak Workman opt(英文) opt(JS) opt(C)
英文散文 100 92.1 93.2 92.9 85.5 89.6 92.2
JavaScript 100 91.1 93.4 94.4 90.3 84.7 91.1
C 100 86.9 93.1 97.1 93.5 91.0 83.5
我的博客 100 93.3 96.1 95.8 84.9 86.9 88.0

对角线很清楚:每个布局在自己被优化的语料上都是最好的。

代价也算得出来。拿一个为别的语料优化的布局来打字,损失是 7.6%(英文)、7.6%(JavaScript)、12.0%(C)。

请把这个数字和第四节那个数字并排放:

  • 换成 Dvorak 能拿到的收益:7.9% ~ 13.1%
  • 为错误的语料做优化要付的代价:7.6% ~ 12.0%

这两个数是同一个量级。

也就是说,"你打的是什么"这件事,和"你用什么布局"这件事,对手指行程的影响差不多大。而过去九十年的讨论几乎全部集中在后者上。

一个附带观察:opt(英文) 在我自己博客这份语料上拿到了 84.9,比 opt(JS)(86.9)和 opt(C)(88.0)都好。这大概不奇怪——"英文散文"语料是 README,我的博客语料是 Markdown,两者共享一整套 Markdown 语法字符:- 做列表、* 做强调、` 做行内码、# 做标题、/ 出现在每条图片路径里。真正的差异(汉字、LaTeX、代码块)反而不是由布局决定的。换句话说,决定"哪个布局对我最好"的,很大程度上是我写的东西是什么格式,而不是我用什么语言思考。

# 七、那些没人愿意引用的实验

键盘布局的讨论里有个很怪的现象:工程证据很薄,传说很厚。

Dvorak 布局 1932 年完成,1936 年拿到美国专利 2,040,248(August Dvorak 与他的妹夫 William Dealey)。二战期间 Dvorak 在海军做实验,声称打字员 10 天就能完成改造——维基百科的记录里提到,他此前至少丢弃过两次结果未知的更早研究。那个"Dvorak 打字员快 74%"的说法正是这一时期的产物,后来被推翻了。

真正该被记住的是 1956 年那次。美国总务署(GSA)委托 Earle Strong 做一个受控实验:把一批 QWERTY 打字员改造到 Dvorak,同时把另一批水平相当的 QWERTY 打字员送去练 QWERTY。Dvorak 组花了 100 小时才恢复到改造前的速度——远超 Dvorak 自己在海军实验里声称的 10 天。而全部训练结束后,这个精心设计的研究没能显示出 Dvorak 在打字速度或学习速度上的任何优势。

1990 年,经济学家 Stan Liebowitz 与 Stephen Margolis 在《Journal of Law and Economics》上发表《The Fable of the Keys》,把史料重查了一遍,结论是支持 Dvorak 优越性的证据很薄,且大量可以溯源到持有专利的 Dvorak 本人。

至于 QWERTY 本身,"故意把常用字母拆开好让打字员慢下来、防止打字杆卡住"这个说法,基本已经被档案研究推翻了。京都大学的 Koichi Yasuoka 与 Motoko Yasuoka 在 2011 年的《On the Prehistory of QWERTY》里追完了 Sholes 的原型和 Remington 的修改:这个排列是被电报员听抄摩尔斯电码的习惯、打字杆篮的机械结构、以及 Remington 的制造取舍共同塑造的,不是一场针对打字员的蓄意拖慢。机械上"把常连用的字母分开以减少卡杆"是真的,但那和"故意让人打不快"是两件事,长期被混为一谈。

1873–1878 年的 Remington No.1,第一台 Sholes & Glidden 打字机

# 八、我的判断

先把话说死:我没打算换布局。

理由不是"QWERTY 更好"——数据很清楚,QWERTY 在四种语料上都是最后一名,同指二连率 13.4%~17.3%,是四种布局里最高的。理由是量级。

一个 7.9%~13.1% 的手指行程改善,需要用几周的产出下降去换(1956 年那批人用了 100 小时才回到起跑线)。对一个每天敲八小时代码的人来说,这笔账也许划算;对我来说不。我更愿意把这几周花在别处。

真正让我改变看法的是第六节那个交叉评估。我原本以为"换布局"和"选错优化目标"是两个不同量级的问题,测出来它们一样大。这让我重新理解了这件事的性质:

键盘布局不是一个"好 / 坏"问题,是一个"匹配"问题。 一个布局里藏着一份隐式的语料假设——Dvorak 藏着 1930 年代的英文,Colemak 藏着 2006 年的英文,我那个 opt(JS) 藏着 node_modules。当你的输入和它藏的那份东西不一致时,它承诺的收益就会打折,折扣的幅度和收益本身一样大。

还有一层,对我这个写中文博客的人尤其实在:本站语料里只有 68.4% 的字符是可打印 ASCII,24.9% 是汉字,另有 2.6% 是全角标点。 汉字走输入法,敲的是拼音字母——那部分按键完全不在这套优化模型的射程内,而且拼音的键位分布和任何一种英文布局的假设都不一样。换句话说,就算我把 ASCII 那部分的手指行程优化到零,我也只优化了自己三分之二的按键。

1940 年代 Woodstock 打字机的键盘

# 九、所以该关心什么

不是布局。

如果目标是"少敲点键",那么直接消灭按键的手段(补全、snippet、模板、宏)有效得多:它们把一次按键变成一个 Tab,布局只是让每次按键少走几毫米。前者动的是按键数量,后者动的是每次按键的成本——而前者能降一个数量级,后者降不到一成半。

如果目标是"手不疼",那么这个模型里根本没有 RSI。键盘高度、手腕角度、掌托、椅子、显示器高度,以及"每 45 分钟站起来一次",这些东西的影响比键位排列大,只是没法用一个漂亮的数字表达出来,所以在讨论里长期缺席。

如果目标是"打得快",那 1956 年的结论还立着:没有可靠证据表明布局是瓶颈。QWERTY 上的顶尖打字员超过 150 WPM。

现代背光键盘

键盘布局这场持续了九十年的争论,争的是一个百分之八。它不是骗局——Dvorak 确实更好,我测出来了。但它是一个被讲成了二十倍的百分之八,而一旦你把数字从传说里拽出来,就会发现真正值得讨论的东西(你打的是什么、你的中文占多少、你的椅子多高)一直站在讨论之外。


# 方法与局限

  • 布局来源:/usr/share/X11/xkb/symbols/us(xkeyboard-config),解析 basic / dvorak / colemak / workman 四段,Workman 的 include "us(basic)" 按覆盖处理。
  • 几何:ANSI 物理排布,键距 19.05 mm,四排横向错位 0 / 0.5 / 0.75 / 1.25 单位,纵向 1 单位。指法为标准触摸打字分配,Shift 由对侧手小指按压,Enter 归右手小指,空格由拇指负责且不产生手指位移。
  • 路径模型:每根手指记录上次停留位置,仅当同一根手指再次动作时计入位移。这忽略了手指回位习惯和真实手型的差异,但排除了"每次都回原位"这种不现实的假设。
  • 未建模:重复击键(RSI)、真实打字速度(Fitts 律)、学习成本、键盘硬件、以及所有非 ASCII 输入(中文走输入法,未计入)。
  • 语料偏差:JavaScript 语料来自 node_modules,含大量转译产物,; 频率高于现代手写风格。C 语料是系统头文件,宏和类型名偏多,_ 频率高于一般应用代码。英文散文语料来自 README,含大量 URL 和反引号,所以 / 和 ` 偏高。
  • 优化器:目标为同指二连位移 + 0.5 单位同指惩罚 + 对侧手 Shift 成本,300 万次交换 × 4 种子取最优。它是行程的近似,不是速度模型;它产出的布局没有做任何人体工学约束(没人验证过它是否舒适),本文只用它做交叉对比,不推荐任何人使用。

文中所有数字均由本地脚本算出,输入是上述四份语料与系统自带的布局定义。若想复现,核心是三件事:从 XKB 解析键位、把每个字符映射到(物理键,级别)、然后按手指累加相邻位移。


# 图片来源

本文配图来自 Wikimedia Commons,均为免费许可,已按要求署名:

原图按 16:9 裁切并压缩。