[王垠系列]程序语言的常见设计错误(1) - 片面追求短小

我经常以自己写“非常短小”的代码为豪。有一些人听了之后很赞赏,然后说他也很喜欢写短小的代码,接着就开始说 C 语言其实有很多巧妙的设计,可以让代码变得非常短小。然后我才发现,这些人所谓的“短小”跟我所说的“短小”完全不是一回事。

我的程序的“短小”是建立在语义明确,概念清晰的基础上的。在此基础上,我力求去掉冗余的,绕弯子的,混淆的代码,让程序更加直接,更加高效的表达我心中设想的“模型”。这是一种在概念级别的优化,而程序的短小精悍只是它的一种“表象”。就像是整理一团电线,并不是把它们揉成一团然后塞进一个盒子里就好。这样的做法只会给你以后的工作带来更大的麻烦,而且还有安全隐患。

所以我的这种短小往往是在语义和逻辑 层面的,而不是在语法上死抠几行代码。我绝不会为了程序显得短小而让它变得难以理解或者容易出错。相反,很多其它人所追求的短小,却是盲目的而没有原则的。在很多时候这些小伎俩都只是在语法层面,比如想办法把两行代码“搓”成一行。可以说,这种“片面追求短小”的错误倾向,造就了一批语言设计上的错误,以及一批“擅长于”使用这些错误的程序员。

现在我举几个简单的“片面追求短小”的语言设计。

自增减操作

很多语言里都有i++和++i这两个“自增”操作和i--和--i这两个“自减”操作(下文合称“自增减操作”。很多人喜欢在代码里使用自增减操作,因为这样可以“节省一行代码”。殊不知,节省掉的那区区几行代码比起由此带来的混淆和错误,其实是九牛之一毛。

从理论上讲,自增减操作本身就是错误的设计。因为它们把对变量的“读”和“写”两种根本不同的操作,毫无原则的合并在一起。这种对读写操作的混淆不清,带来了非常难以发现的错误。相反,一种等价的,“笨”一点的写法,i = i + 1,不但更易理解,而且在逻辑上更加清晰。

有些人很在乎i++与++i的区别,去追究(i++) + (++i)这类表达式的含义,追究i++与++i谁的效率更高。这些其实都是徒劳的。比如,i++与++i的效率差别,其实来自于早期 C 编译器的愚蠢。因为i++需要在增加之后返回i原来的值,所以它其实被编译为:

(tmp = i, i = i + 1, tmp)

但是在

for(inti =0; i < max; i++)

这样的语句中,其实你并不需要在i++之后得到它自增前的值。所以有人说,在这里应该用++i而不是i++,否则你就会浪费一次对中间变量tmp的赋值。而其实呢,一个良好设计的编译器应该在两种情况下都生成相同的代码。这是因为在i++的情况,代码其实先被转化为:

for(inti =0; i < max; (tmp = i, i = i +1, tmp))

由于tmp这个临时变量从来没被用过,所以它会被编译器的“dead code elimination”消去。所以编译器最后实际上得到了:

for(inti =0; i < max; i = i +1)

所以,“精通”这些细微的问题,并不能让你成为一个好的程序员。很多人所认为的高明的技巧,经常都是因为早期系统设计的缺陷所致。一旦这些系统被改进,这些技巧就没什么用处了。

真正正确的做法其实是:完全不使用自增减操作,因为它们本来就是错误的设计。

好了,一个小小的例子,也许已经让你意识到了片面追求短小程序所带来的认知上,时间上的代价。很可惜的是,程序语言的设计者们仍然在继续为此犯下类似的错误。一些新的语言加入了很多类似的旨在“缩短代码”,“减少打字量”的雕虫小技。也许有一天你会发现,这些雕虫小技所带来的,除了短暂的兴奋,其实都是在浪费你的时间。

赋值语句返回值

在几乎所有像 C,C++,Java 的语言里,赋值语句都可以被作为值。之所以设计成这样,是因为你就可以写这样的代码:

if(y =0) { ... }

而不是

y =0;

if(y) { ... }

程序好像缩短了一行,然而,这种写法经常引起一种常见的错误,那就是为了写if (y == 0) { ... }而把==比较操作少打了一个=,变成了if (y = 0) { ... }。很多人犯这个错误,是因为数学里的=就是比较两个值是否相等的意思。

不小心打错一个字,就让程序出现一个 bug。不管y原来的值是多少,经过这个“条件”之后,y的值都会变成 0。所以这个判断语句会一直都为“假”,而且一声不吭的改变了y的值。这种 bug 相当难以发现。这就是另一个例子,说明片面追求短小带来的不应有的问题。

正确的做法是什么呢?在一个类型完备的语言里面,像y=0这样的赋值语句,其实是不应该可以返回一个值的,所以它不允许你写:

x = y = 0

或者

if(y =0) { ... }

这样的代码。

x = y = 0的工作原理其实是这样:经过 parser 它其实变成了x = (y = 0)(因为=操作符是“右结合”的)。x = (y = 0)这个表达式也就是说x被赋值为(y = 0)的值。注意,我说的是(y = 0)这整个表达式的值,而不是y的值。所以这里的(y = 0)既有副作用又是值,它返回y的“新值”。

正确的做法其实是:y = 0不应该具有一个值。它的作用应该是“赋值”这种“动作”,而不应该具有任何“值”。即使牵强一点硬说它有值,它的值也应该是void。这样一来x = y = 0和if (y = 0)就会因为“类型不匹配”而被编译器拒绝接受,从而避免了可能出现的错误。

仔细想一想,其实x = y = 0和if (y = 0)带来了非常少的好处,但它们带来的问题却耗费了不知道多少人多少时间。这就是我为什么把它们叫做“小聪明”。

思考题:

Google 公司的代码规范里面规定,在任何情况下 for 语句和 if 语句之后必须写花括号,即使 C 和 Java 允许你在其只包含一行代码的时候省略它们。比如,你不能这样写

for(inti=0; i < n; i++)

  some_function(i);

而必须写成

for(inti=0; i < n; i++) {

  some_function(i);

}

请分析:这样多写两个花括号,是好还是不好?

(提示,Google 的代码规范在这一点上是正确的。为什么?)

当我第二次到 Google 实习的时候,发现我一年前给他们写的代码,很多被调整了结构。几乎所有如下结构的代码:

if(condition) {

returnx;

}else{

returny;

}

都被人改成了:

if(condition) {

returnx;

}

returny;

请问这里省略了一个else和两个花括号,会带来什么好处或者坏处?

(提示,改过之后的代码不如原来的好。为什么?)

根据本文对于自增减操作的看法,再参考传统的图灵机的设计,你是否发现图灵机的设计存在类似的问题?你如何改造图灵机,使得它不再存在这种问题?

(提示,注意图灵机的“读写头”。)

参考这个《Go 语言入门指南》,看看你是否能从中发现由于“片面追求短小”而产生的,别的语言里都没有的设计错误?

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 203,772评论 6 477
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 85,458评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 150,610评论 0 337
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,640评论 1 276
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,657评论 5 365
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,590评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,962评论 3 395
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,631评论 0 258
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,870评论 1 297
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,611评论 2 321
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,704评论 1 329
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,386评论 4 319
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,969评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,944评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,179评论 1 260
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 44,742评论 2 349
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,440评论 2 342

推荐阅读更多精彩内容

  • 在C语言中,五种基本数据类型存储空间长度的排列顺序是: A)char B)char=int<=float C)ch...
    夏天再来阅读 3,320评论 0 2
  • 第2章 基本语法 2.1 概述 基本句法和变量 语句 JavaScript程序的执行单位为行(line),也就是一...
    悟名先生阅读 4,114评论 0 13
  • 1. Java基础部分 基础部分的顺序:基本语法,类相关的语法,内部类的语法,继承相关的语法,异常的语法,线程的语...
    子非鱼_t_阅读 31,562评论 18 399
  • 「黑色」既神秘又有个性的颜色,在生活周遭不可或缺,在服装的世界里更是屹立不摇!有人用它来堆叠层次,还能巧妙的搭配显...
    时尚首阅读 573评论 0 1
  • 最近英语阅读视频已坚持八天了,虽然有时也有些应付的意味,但还是每天必发;《诗经》已经积累了九篇了,加上老师让积累的...
    岸笛飞声阅读 133评论 0 0