SQLite之父呼吁废除CRLF:为何回车键应该被时代淘汰?

发表时间: 2024-10-14 19:51

编译 | 苏宓

出品 | CSDN(ID:CSDNnews)

作为一名程序员,想必你对 CRLF 并不陌生。

CRLF,全称 Carriage Return Line Feed,中文翻译为回车换行。它由两个字符组成:CR (\r,回车) 和 LF (\n,换行),其中回车是将光标移动到当前行的最左侧,而换行则是将光标下移一行。这里还需要提及的一个概念是——新行 (NL,NewLine),它是指将光标下移一行,并移动到当前行的最左侧。

CRLF 的存在主要是为了兼容不同操作系统的文件格式。通常,Windows 使用 CRLF 作为换行符,而 Unix/Linux 和 macOS 只使用 LF。

然而,在实际开发中,CRLF 和 LF 的差异常常导致不少开发团队在处理文件时出现令人头疼的编码错误和冲突。越来越多的开发者认为:CRLF 已经过时,应该被直接废除。

这个观点引发了不少争议,但也让人开始重新思考:在现代开发环境中,我们是否真的需要继续支持 CRLF?

SQLite 之父发起呼吁

这则声明由美国软件开发者 D. Richard Hipp 发起的,他不仅创建了 SQLite 开源嵌入式关系数据库,也开发了分布式版本控制系统 Fossil 和网络服务器 Althttpd 等软件。

D. Richard Hipp 表示,“回车”和“新行”都是有用的控制字符。NL(新行)是最常见的操作,表示开始新的一行并从行首开始写。单独的 CR 有时也有用,尤其当你想覆盖已经写好的文字时。而 LF(换行)基本上没什么用。没有人希望在一行的中间停止,然后向下移动一行并从同一列继续写。没有任何实际程序会这么做。

LF 之所以存在,是在计算机终端还是电传打印机的时候遗留下来的东西。

D. Richard Hipp 称,LF 诞生于大约 70 年前的机械电传打字机时代。当时的电传打字机没有使用晶体管,而是完全由齿轮、凸轮、马达、继电器和伺服装置组成的。它们非常神奇,可以将通过两根铜线传输的二进制代码转换为纸上的打印文本。

电传打字机就像是普通打字机一样工作,每秒打印大约 5 个字符。打印头是一个包含字母的圆柱体或椭圆形小球。打印头与纸之间有一个浸有墨水的布带。为了打印一个字符,打印头会旋转到正确的位置,然后向前撞击,使布带上的墨水在纸上形成所需字符的形状。每打印一个字符,整个打印头机构(小球、墨带以及各种控制凸轮和齿轮)都会向右移动一个字符的位置。这一切每秒发生五次。这些机器运作时噪音很大,并且会明显震动。

在一行文本的末端,打印头必须返回到最左侧。打印头移动得很快,但移动到最左边仍需要时间。当时没有内存,所以打印头必须在下一个字符到来之前完全移到左边。

为了实现这一点,NL(新行)操作被分为两个子操作:CR(回车)和 LF(换行)。CR(回车)先行,启动打印头向左移动。当打印头还在移动时,LF(换行)会到达,导致纸张滚动一行。这个额外的 LF(换行)字符为打印头争取到足够的时间,在下一个字符到达之前完全移到最左侧。

回看过去,文本行以 CRLF 结尾的传统可以追溯到 20 世纪 50 年代电传打字机的机械限制。这是一个典型的例子,说明底层实现的细节如何暴露在用户界面中。

到 20 世纪 60 年代末和 70 年代初的 Multics 和 Unix 时代,大多数人意识到使用 CRLF 作为 NL(新行)是没有意义的。

因此,发送单独的 CR 和 LF 字符的任务被移交给了电传打字机的设备驱动程序,因为硬件缺陷的解决应该在驱动程序层处理。计算机只需保存一个 NL(新行)字符,并采用与电传打字机相同的 LF(换行)代码来表示 NL,这里真正的 LF 在实际应用中没有任何意义,因此它的数字代码被重新用于表示 NL。

如今,CR 用 Unicode 编码中的 U+000d 作为代码点来表示,LF 和 NL 都用 U+000a 表示。几乎所有现代机器都仅使用 U+000a 表示 NL,这个意义也嵌入在大多数编程语言中,通常使用反斜杠转义符 \n。

尽管如此,仍有少数机器坚持在 NL 之前发送 CR,而 U+000a 的官方 Unicode 名称仍然是 LF。此外,一些协议(如 HTTP、SMTP、CSV)仍然“要求”每行以 CRLF 结尾。如今几乎所有软件都会接受单独的 NL 字符(没有前置 CR)来表示行结束。你必须非常仔细地寻找,才能找到真正将 U+000a 解释为换行的设备或应用程序。

D. Richard Hipp 直言:

“这一传统,即在每个 NL(新行)之前发送无用的 CR(回车),起源于旋转拨号电话时代,甚至是在集成电路发明之前。这种做法在我们的现代世界中已经没有理由继续存在了。额外的 CR 没有任何实际用途,只是给程序员带来不必要的麻烦,浪费带宽。”

CRLF 已经过时!

在这样的背景下,D. Richard Hipp 发起呼吁——「所有追求简洁、和平,并希望促进人类繁荣的人,请与我一起反对使用 CRLF,帮助它迅速成为历史的遗迹。」

为此,他提出了四点建议:

  1. 停止将“linefeed”(LF)作为 U+000a 代码点的名称。过去二十年内构建的大多数技术,以及过去半个世纪的大多数技术,已经将 U+000a 理解为“newline”(新行)而不是“linefeed”(换行)。虽然“linefeed”是它的历史名称,但那又有什么关系呢?在几乎所有实际应用中,它表示的是“新行”,因此请直接称它为“newline”。

  2. 停止发送不必要的 CR(回车)。仅在你确实需要用新内容覆盖当前行时才使用 CR。在 NL(新行)之前加上 CR 完全是浪费带宽。除非你必须与某些顽固的系统通信,而这些系统执意停留在 1950 年代,否则不要在 NL 前加上 CR。

  3. 即使某些现有协议(如 HTTP、SMTP、CSV、FTP)技术上要求以 CRLF 作为行尾,也不要遵从。只发送 NL。尽管技术上不正确,但几乎所有这些协议的实现都会接受单独的 NL 作为行尾标记。不要屈服于 CRLF 的控制。

  4. 修复那些在接收到没有前置 CR 的 NL 时表现异常或报错的软件。所有现代软件都应接受单独的 U+000a 字符作为有效的行尾标记。系统可能会为了向后兼容而接受 CR 加 NL,但要求 CR 加 NL 的软件是有问题的。

在 D. Richard Hipp 看来,CRLF 的终结早该到来了,因为它早就过时了。

带来的影响

万万没想到,此话一出,引发程序员强烈的共鸣,同时也有不少人持有不同的看法。

有开发者称,「我完全同意。这会导致无尽的混乱,尤其是在跨平台文本文件中。更不用说以编程方式解析了。」

不过,也有来自 HN 上的网友 forrestthewoods 表示:

我强烈反对这个观点。

简单来说——别抱怨,自己解决。处理不同或混合的行结尾确实是个轻微的小麻烦,但并不复杂或困难。不要为了让自己轻松一点就让别人承担不必要的麻烦。接受它,继续前行。

@Animats 认为,「与其呼吁,倒不如说服微软。因为这正是 DOS 遗留导致让这一切得以延续」。

@bmitc 称:除了那些设计糟糕的 Unix 工具和 Git,谁还会被这种问题困扰?为了适应 Linux,我将编辑器配置为无论在哪个操作系统上都使用 LF,并确保 Git 不再混淆行结尾。在处理串行协议时,我从来没有遇到过问题。

随着争议的发酵,D. Richard Hipp 迫于无奈之下,于今日更新了自己的声明,他表示:

看起来(1)目前主流中的软件依赖于过时的 CRLF 行结尾的数量比我原先预想的还要多;

(2) 很多人并不认同我创建一个无 CRLF 世界的热情。

唉,这让我有些失望,但现实就是如此。感谢所有愿意尝试这个想法的人,本来几乎成功了!因此,我在此撤回这项提议,并已将我所有的系统恢复为在规范要求的情况下生成 CRLF。真是遗憾。

不过,这次实验带来一个意外的好处,我在 Fossil 和 althttpd 中发现并修复了一些问题,这些程序此前要求必须使用 CRLF,且不允许使用单独的 NL 作为替代。

那么,你是否在开发中遇到过 CRLF 问题?

来源:

https://fossil-scm.org/home/ext/crlf-harmful.md

https://news.ycombinator.com/item?id=41830717