副标题:几十年的小事之争,还会结束吗?
原文地址:levelup.gitconnected.com/the-3-wars-…
原文作者:levelup.gitconnected.com/@elye.proje…
发表时间:2020年9月3日-15分钟阅读
图片由ThePixelman从Pixabay提供。
程序员通常是聪明的,有逻辑的,也是有主见的。他们几乎可以辩论任何事情。从架构到命名,到语言偏好,再到语法等等。
有3场战争,大多数程序员要么站在一边,要么站在另一边。
看看你站在哪一边,如果你同意我对最终结果的预测......
1. Tab战--Tab字符与空格
照片由作者提供
缩进是大多数编程语言编码的一个重要部分。它有助于反映一个人清楚地识别代码范围。
例如,通过查看下面的代码,我们可以清楚地看到独立的范围。
下面看到的这一点就更容易了。
没有人争论缩进的作用。但是我们应该用TAB还是SPACE来做呢?
在网上可以找到很多争论,下面举一个例子。
《标签与空格--在任何情况下,什么是正确的缩进字符......》
目前来看,这个问题并不适合我们的问答形式。我们希望答案能有事实依据...
甚至有一个YouTube上。检查出来。挺有意思的。
TAB是第一首选
自打字机诞生之日起,TAB键就被引入并广泛用于缩进。
TAB键是作为一种在打字时快速创建表格的方法而产生的(称为 "制表").打字员在缩进段落时也开始使用TAB键......。
所以,程序员在开始时也使用TAB键进行缩进,这是不言而喻的。原因如下
- 很明显,TAB之所以从第一天开始就存在,是因为输入TAB只需要一个按键就可以得到一个完整的缩进,而使用空格则需要多次按键,如下图😓。
- 用一个键来表示缩进,可以节省持久性空间来存储代码。在早期,持久性空间是昂贵的
- 在普通的编辑器中更容易自定义,因为TAB是一个按键,比单个字符占据更宽的宽度,人们可以定义它的宽度,从而轻松自定义缩进。
SPACEs的出现较晚
随着不同操作系统的出现,他们决定了缩进的宽度不同。
维基百科。Unix系统的编辑一般认为tab相当于8个字符,而Macintosh和Windows环境会将tab设置为4个字符,这就造成了代码在不同环境之间传输时的混乱。
为了解决这个问题,并确保代码无论来自哪个平台,看起来都是一样的,SPACEs被提出来取代代码的TAB。
简而言之,空格是首选,因为它被看作是更确定的(只是每个人的单列字符)。它不仅在不同的环境下是一致的,无论代码编辑器或查看器,它们都是一样的。
有些程序员喜欢没有外部影响(环境、IDE、阅读器、编写者等)的确定性结构。
今天的情况如何?
从最新的挖掘来看,似乎在Github上,用户都在使用Spaces而不是TAB,如下图所示(一个Googler的发现)。
为什么会出现这种情况?是大部分程序员改变了他们的喜好吗?还是新的程序员比上一代更喜欢使用Spaces?
下面是我的看法,我们来看一下TABs的原始优势。
Tab只需要1个按键就可以了,所以是空格的缩进
我们不能否认,现在的新一代大多用IDE来编码,而不是普通的文本编辑器。即使是普通的文本编辑器,在检测到一个人在编码的时候,它也是带有自动缩进功能的。
看看下面的GIF上的一段代码,缩进是自动到位的。而且默认情况下,它自动缩进4个空格。
即使在键盘上输入TAB,它也会自动插入4个空格,而不是一个TAB字符。
自定义空格缩进变得简单
旧时代的工具,改变缩进中的空格是不可用的。但有了新的工具,现在可以轻松实现,如下图所示。
可以从缩进2个空格改为缩进4个空格,反之亦然。
自定义缩进的空格也变得容易实现。
另外注意,如果喜欢的话,还可以把空格改成TAB。
存储空格的成本显著下降
我们知道,磁盘空间和内存的成本已经大幅下降。下面是10年前的图表。即便如此,GB的成本和30年前相比,还是便宜了那么一个量级。
因此,一个TAB字符上有4个空格,不会浪费太多内存或磁盘空间。
Compute Power则相反,我们的计算能力要比一个TAB处理多个空间,而空间所需的额外处理时间可以忽略不计。
解决了SPACEs过去的局限性,同时SPACEs也比外部工具或环境具有确定性的特点,这的确可以认为SPACEs将成为压痕处理TAB的最终赢家。
我想,要让所有的文本编辑器、阅读器和操作系统环境就TAB的默认宽度达成一致,比直接使用Spaces要漫长得多。😝
2. 大括号战争--大括号起始应该在哪里?
照片由作者提供
早在ALGO58(1958年)时代,就引入了积木编程,有明确的begin和end,体现了积木的起点和终点
if x > -1 then
begin
if x ≠ 0 then
x := 1/x
end;
引入大括号
而后来有了C语言,引入了{和}来表示块的开始和结束
if (x > -1)
{
if (x != 0) x = 1/x;
}
这是如此整洁,因为
- 每个字只由一个字组成,而
- 它在左栏上排列得很好,要想清楚地看到它们,请按照ALGO58中的
begin和end惯例。
节省一些线路
不过,后来有人觉得这是占了太多的空间。例如
if (x < 0)
{
print("negative");
}
else if (x > 0)
{
print("positive");
}
else
{
print("zero");
}
如果我们能把它们浓缩成下面的样子,岂不更好?
if (x < 0) {
print("negative");
} else if (x > 0) {
print("positive");
} else {
print("zero");
}
对一些开发者来说,这是紧凑的,还是整洁的。对一些开发商来说,这很糟糕! 看上去很杂乱,也比较难看。
于是,战争开始了!
在这个维基百科中分享的还有更多的变体。想要阅读更多关于大括号的内容,请看下面的博客。
它从哪里来,怎么样,能不能持久?
今天的情况如何?
在编译方面,所有语言的编译器似乎都能接受这两种形式。因此,在很大程度上取决于具体的语言标准委员会的喜好,或者开发者团队定义一个适合他们的标准。
由大家来决定的时候,就会有战争!
尽管如此,幸运的是到目前为止,从语言指南本身来看,还是有一些声明的偏好。
语言指南的偏好
对齐的大括号
混合大括号的方法(两者都有)
紧凑的大括号
- Java编码指南
- Scala编码指南
- Swift编码指南
- Kotlin编码指南
- 诸如此类(Go, R, Rust, JavaScript, Dart)
通过观察哪种风格的语言采用情况,我们可以清楚地看到新式编程语言前进的趋势。当人们开始学习编程语言的时候,会有更多的人接触到紧凑的风格。我们不能否认,人们往往会坚持最初学习的风格。
工具的影响
我们今天所拥有的工具,也扮演着重要的角色,也是塑造了一种风格可能比另一种风格更受欢迎。 有的开发者担心如果{的开头与}不对齐,别人就不容易正确地找到这个代码块。为了解决这个问题,IDE让代码块可以折叠和展开,如下图所示。
使用对齐的大括号
这样就减少了无法识别代码块的顾虑,使代码看起来更加简洁,即:。
companion object
{...}
如果使用紧凑的大括号风格,并执行同样的折叠,我们可以看到代码看起来更加简洁了
companion object {...}
使用紧凑的大括号
因此,大多数遵循语言习惯的IDE默认将紧凑版作为默认格式,其中的自动格式规则会自动将其转换为该格式(紧凑的大括号格式)。
当然,如果用户愿意,IDE也允许用户切换回对齐的大括号,以包容所有程序员的喜好。
一致性和简洁性是首选
哪个更一致?从单一排列的大括号来看,与紧凑的大括号相比,似乎单一排列的大括号更一致。
// Looks more consistent as { and } are aligned
fun doSomething()
{
print("something")
}
// Looks less consistent as { and } are not aligned
fun doSomething() {
print("something")
}
但棘手的是,当我们有一些其他的考虑时,如空函数、if-else-if和链式功能代码。
// Looks wasteful
fun doNothing()
{
}
// Looks more compact
fun doNothing() {}
如果-else-if
// Looks wasteful
if (x < 0)
{
print("negative");
}
else if (x > 0)
{
print("positive");
}
else
{
print("zero");
}
// Looks more compact
if (x < 0) {
print("negative");
} else if (x > 0) {
print("positive");
} else {
print("zero");
}
功能代码链
// Looks wasteful (actually don't even compile in Kotlin)
listOfSomething
.filter
{
it.goodStuff
}
.forEach
{
print(it)
}
// Looks more compact
listOfSomething
.filter { it.goodStuff }
.forEach { print(it) }
因此,为了弥补单行语句越来越成为函数式编程前进的模式,紧凑的大括号是首选,因为它使开始{与语句的状态总是在同一行。这使得它紧凑而又一致。(即不混用两种样式)
缩进已经足以显示代码块?
根据war 1,我们已经知道缩进已经是为了让代码块更明显。有了大括号似乎又填满了另一行,确实有些多余的缩进的作用。像Python这样的语言已经完全放弃了大括号。
但作为程序员的我们,早已根深蒂固地接受了大括号,在编码的时候看不到大括号,我们会很难安心。
我想coder还是很喜欢自己的大括号的。所以它是会存在一段时间的。但我认为在有大括号与无大括号的战争开始之前,紧凑的大括号将是最终的赢家。
3. 分号战争--最后要有分号;?
照片由作者提供
如果你以前学过编程,用GCC编译一个C程序,有可能在编译代码的时候遇到过奇怪的编译错误,后来发现你错过了在你的一条代码语句的末尾添加了一个小小的字符,分号;。
这种做法使得现在所有的程序员在到达代码的语句末尾时,都会自动打入;,甚至不需要思考。在行末没有看到;,让编码者觉得好奇怪。有些不对劲!
什么时候开始接触编程语言的;?
Michael McMillan写了一篇很好的介绍,介绍了分号是如何进入编程世界的。
分号的用法到底从何而来?
这一切都要从ALGO58说起,在ALGO58中,分号是用来分隔语句的。
begin real a;
a := 1;
begin real a;
a := 2;
print(a)
end;
print(a)
end
然而,当BCPL(在B和C语言之前)被引入时,分号在该语言中是不需要的,因为有一些巧妙的规则来识别语句的结尾。
为了使编程过程更有效率,B和C语言将分号变成了强制性的。那是上世纪70年代的事情了。
从C语言的历史来看 : 虽然BCPL程序在概念上是由不受限制的字符流提供的,但巧妙的规则允许大多数分号在结束于行边界的语句后被省略。B和C省略了这一便利,并在大多数语句后用分号结束。
分号作为语句分隔符或终止符
从此,许多编程语言都有了分号。但是,不同的语言对分号的使用是不同的。最明显的区别是,它们要么是
- 作为语句分隔符使用。如果一行有多个语句,我们用分号来区分它们。因此,如果语句的结尾是换行,则不需要分号。
// Semicolon is needed when 2 statements in a line
class KotlinClass {
val a = 10; val b = 20
val c = 20
}
- 作为语句终结者工作。从高层次的角度看,它与语句分隔符类似,但是,它需要在语句的结尾处使用。因此,分号需要在语句的结尾处使用,即使它已经是行的结尾。
// Semicolon is needed at the end of every statement
public class JavaClass {
int a = 10; int b = 20;
int c = 30;
}
更多语言风格的终止,请参考这里。
显然,这并不是最近不同的新趋势,早在Pascal语言时期(比C语言更早),就已经不使用分号作为语句结束符(即不需要分号)。早在Pascal语言时期(比C语言更早),它就已经不使用分号作为语句结束符(即不需要在语句结束时使用)。
摘自 Semicolon-Wars (American Scientis, 2006) : 在 Algol 和 Pascal 中,程序语句必须用分号分隔。例如,在x:=0; y:=x+1; z:=2中,分号告诉编译器一个语句的结束和下一个语句的开始。C程序中也有分号,但在C中它们是语句结束符,而不是分隔符。两者有什么区别呢?C程序在最后一条语句后需要一个分号,但Pascal不需要。
所以在使用分号的编程世界里,也有这样的争论:我们是否应该在每一行的最后使用分号。
多年来的语言偏好
根据这里的列表,我绘制了一张图,看看各语言对分号终止的偏好与其他语言如何。
70年代以前,语言要么使用新行终止,要么用分号分隔(Algo等)。直到70年代开始,C语言是第一个引入语言分号终止语句的语言,理由是语言的处理效率更高。
此后,其他一些语言纷纷效仿C语言,如Basic、Python、PHP、Java等,当然还有C语言的变种,如C++、C#、Objective C等。
从这张图中,我们可以清楚地看到,几十年来,坚持使用分号结束符的新语言的数量要比一直尝试不使用分号结束符的语言少得多。这种情况甚至发生在最近的十年,像Rust和Dart这样的新语言仍然应用了分号语句终止符的需求。
同时,我们也看到越来越多的不使用分号终止的语言在编程界变得越来越突出,比如Scala、Kotlin、Swift、Python等。
战争越来越激烈。
自动分号插入
我们知道JavaScript是一门通用性和容错性都很强的语言(可以说给很多人造成了相当大的痛苦),引入这个自动分号插入的概念,即如果你忘记添加分号,编译器就会添加分号。
鉴于语言本身并没有强制要求开发必须要做什么,而允许有一个比较宽松的引导,JavaScript社区里的分号大战变得超级激烈。很明显,社区里有一群人狂热地认为只有在需要的情况下才打字,而有一些人则严格遵守纪律,因为编译器会这么做。
这种不使用分号的尝试的弊端在于,语言处理有一个bug,没有为下面的代码插入分号,并导致运行时错误。
const name = "World"
["Hello", name].forEach(word => console.log(word))
感觉无分号结束的一方已经输了。
然而,在这种情况下,TypeScript将能够产生编译时错误。因此,一些社区坚定地认为战争还没有结束。
"就用分号吧。这是件小事,让代码更安全。" NO! 事实上,人们对......
尽管如此,语言处理器需要自动插入分号,这意味着这种消除分号的尝试并不是一个真正稳健的考虑,尤其是考虑到仍会有漏掉分号的情况。这极大地阻碍了非分号终结界的尝试前进。
句法糖导致分号的癌症
这是编程语言的先驱之一Alan J Perils的名言。很明显,在那个年代,为了提高编程语言的可读性,语法发生了变化(被称为句法糖)。当时分号是用来补偿这种语法变化,使语言处理器仍然能够将它们处理成适当的结构,Alan J Perlis称它们为癌症。
时至今日,许多程序员已经根深蒂固地认为分号是编程语言的必备条件,而实际上,分号是为了帮助语言处理器而添加的,对于人类阅读代码来说,分号的意义并不那么重要。因此,许多语言继续试图尽可能地消除分号的必要性,同时使语言仍然对人类友好,也就不足为奇了。
早期要达到这个平衡是比较困难的,但随着时间的推移,我们现在可以看到一些受到很多人喜爱的整齐的编程语言(如Kotlin、Swift、Scala)可以做到语言既清晰又简单,同时对语言需要的额外辅助较少,如分号终止的语句。
我喜欢下面这段来自Roman Elizarov(Kotlin语言的创始人之一)的文字。
如果编译器和人类都能很容易地猜到它,那么你就不应该在代码中明确地写出来。
我们的目标不是有时通过使用启发式方法自动在这里和那里插入分号来省略分号,而是要确保每一个遵循语言风格指南的正确格式化的代码都完全不必包含分号。
我确实相信并同意他下面的文章,随着我们在增强编程语言方面的进步,未来的编程语言将不再需要分号作为语句终止符。
我们正生活在分号时代的末期。配方就在那里。只是时间问题,它将成为... ...
战争不一定是坏事,尤其是在编程界。都是因为不同的意见和想要变得更好的愿望,才让我们前进。不是对的,也不是左的,而是从左到右,从右到左,推动我们前进。
尽管如此,自己身处编程界,我也有自己的信念和喜好。但实际上,15年前我的信念和立场是不一样的,我已经改变了。我相信这个世界也已经改变了。时间会证明一切。
我本来是坚信他们的,但现在我已经改邪归正了。