这几天看了《Go语言编程》这本书,在微博上吐了不少槽,得到了不少反馈,于是现在趁着新鲜记录一些简单的感受吧。

简单地说,这本书并不算好,至少相较于Go这门语言以及老许的技术水平来说,这本书着实写得一般。Go的确是一门有亮点的语言,尤其是相对C语言来说,例如其中的接口特性、“基于对象”的抽象方式、自然还有goroutine等一些被人广为了解的特色等等。不过问题关键在于,书中有些地方描述地太过火了,白白多出各种槽点。

以书中出现的顺序为准,随便摘录几个我还有印象的地方:

1、前言中关于defer关键字的描述:

在Java中,你可能这样写代码来保证资源正确释放:

Connection conn = ...;
try {
    Statement stmt = ...;
    try {
        ResultSet rset = ...;
        try {
            ... // 正常代码
        }
        finally {
            rset.close();
        }
    }
    finally {
        stmt.close();
    }
}
finally {
    conn.close();
}

完成同样的功能,相应的Go代码只需要写成这样:

conn := ...
defer conn.Close()

stmt := ...
defer stmt.Close()

rset := ...
defer rset.Close()

事实上,在如今的Java语言中,我们基本都是用try-with-resources特性来写这样的代码的:

try (Connection conn = ...;
     Statement stmt = ...;
     ResultSet rset = ...;) {

}

同时,对于defer关键字可以跟匿名函数的特点,其实对于支持匿名函数和闭包的语言来说,几乎都不成问题,也不失其优雅性。后面的章节还提到了recoverpanic,给人的感觉就是个普通的异常抛出和捕获机制,但是在那个地方却没有对这显而易见会出现的对比进行解释了。

2、第1章中关于多返回值的描述:

Go语言革命性地在静态开发语言阵营中率先提供了多返回值功能。

这种说法让ML系(SML、OCaml、F#)或Scala等语言情何以堪?更何况这个特性跟动态静态语言基本毫无关系,事实上都有人总结过这个问题,在搜索引擎上排名数一数二。

3、第2章中关于变量初始化的描述:

对于声明变量时需要进行初始化的场景,var关键字可以保留,但不再是必要的元素,如下所示:

var v1 int = 10 // 正确的使用方式1
var v2 = 10 // 正确的使用方式2,编译器可以自动推导出v2的类型
v3 := 10 // 正确的使用方式3,编译器可以自动推导出v3的类型

以上三种用法的效果是完全一样的(除了第三种声明方式不能用于声明全局变量)。与第一种用法相比,第三种用法需要输入的字符数大大减少,是懒程序员和聪明程序员的最佳选择。这里Go语言也引入了另一个C和C++中没有的符号(冒号和等号的组合:=),用于明确表达同时进行变量声明和初始化的工作。

说实话,这段描述,尤其是“懒程序员和聪明程序员的最佳选择”这种说法让我十分不满,在我看来,这不是硬把臭的说成香的么?多这么第三种方式有什么意义?难道就是因为省了三个字符?当然,事实上:=是有一定意义的,例如重新为之前声明过的变量赋值,或是直接在表达式内赋值并使用等等。尽管我觉得这个设计多余且有些自找麻烦,但这可以作为taste来讨论,远比书中“懒程序员和聪明程序员”要有意义地多。

此外,书中到处使用:=来创建局部变量,是否是一种误用?当然这是题外话。

4、第3章中关于“面向对象编程”的描述:

在Go语言中,面向对象的神秘面纱被剥得一干二净。对比下面的两段代码:

func (a Integer) Less(b Integer) bool { // 面向对象
    return a < b
}

func Integer_Less(a Integer, b Integer) bool { // 面向过程
    return a < b
}

a.Less(2) // 面向对象的用法
Integer_Less(a, 2) // 面向过程的用法

可以看出,面向对象只是换了一种语法形式来表达。C++语言的面向对象之所以让有些人迷惑的一大原因就在于其隐藏的this指针。一旦把隐藏的this指针显露出来,大家看到的就是一个面向过程编程。感兴趣的读者可以去查阅《深度探索C++对象模型》这本书,看看C++语言是如何对应到C语言的。而Java和C#其实都是遵循着C++语言的惯例而设计的,它们的成员方法中都带有一个隐藏的this指针。如果读者了解Python语法,就会知道Python的成员方法中会有一个self参数,它和this指针的作用是完全一样的。

我们对于一些事物的不理解或者畏惧,原因都在于这些事情所有意无意带有的绚丽外衣和神秘面纱。只要揭开这一层直达本质,就会发现一切其实都很简单。

上面这段话从“可以看出”开始就显得无厘头了一些,将编程范式和内部实现混在了一起。为什么Java的使用者要关注this是怎么传递的?这对于面向对象编程这种编程范式没有任何意义。这章最后还提到:

本章我们详细讲解了Go语言面向对象编程的相关特性。众多读者可能会有一个疑问,那就是与C++、Java和C#这些经典的面向对象语言相比,为什么只用了比第2章还短的篇幅就介绍完本章?是Go语言在面向对象编程上的支持力度不够,还是本书没有介绍完整呢?

在回答这个问题之前,读者可以先考虑一个问题:基于本章介绍的Go语言的面向对象编程特性,有C++、Java和C#可以实现而Go语言无法表达和实现的场景吗?事实上我们很难想出这种场景,也就是Go语言看似简陋的面向对象编程特性已经可以满足需求,这反而映射了其他语言在这方面可能做了过多的工作。用简单的语法、更少的代码就可以做完的事情,为什么我还要自 寻烦恼去学习繁复的语法,然后为众多实现细节而烦恼呢?

Go语言给我们开辟了一个新的视野,原来问题可以这么简单地解决。

其实我可以举出很多Java和C#中可以实现而Go语言无法表达的内容,但是我也可以自己按照“已经可以满足需求”来进行回应,但是这种应对方式是语言讨论中最没有意义的方式之一。Go语言的确提供了一种相对于Java和C#更为简单的数据抽象方式,也可能的确够用(这点我不会简单粗暴地反对),但因此说它实现了面向对象,能用来应对面向对象能实现的各种场景,这我表示看不下去。事实上,我不认为Go语言做到了面向对象(当然我也没说面向对象是必须的),它只不过是一种基于对象组合的方式来提供数据抽象能力(当然还有接口等等)。

印象中大部分的槽点都出现在本书的前半部分,描述Go语言基础能力的那些,而后半主要是围绕着类库和周边环境,所以没有太多可谈的地方。总体而言,这本书给我的印象,就好像在看一个Go语言粉丝,再竭力推销这门语言,但对于其中讲述内容的严肃性、正确性便有所放低了,这不是老许这一级别的人物应该给人的印象。这本书里很多地方对于Java和C#的批评并不是那么有道理(我又想起了在“并发编程”里的一段描述,但懒得举了)。

其实Go语言还是有所特色的,例如goroutine方面,尽管我认为它只是把其他一些语言/平台中用类库实现的功能做进了语言(有机会我会详述这一点),不过在语言级别强调一种编程方法/范式也是这种语言的文化,Erlang便是前例之一。可惜的是,书中只用了非常简短的篇幅描述了这个功能,但是对于它应该如何使用,如何以此来设计一个应用,如何进行架构规划,几乎可以说是只字未提。事实上,这方面通过一个稍微复杂点的案例也能较好地说明问题了,这在我之前看过的一本ManningProgmatic的Erlang书(具体哪本忘了)中就有体会。

假如要我给这本书打分(满分10分),我会打6分。只是再考虑到这本书在宣传时给人的印象,以及老许给人的期待,我只能给它打5分。假如有人问我学习Go语言是否该看这本书,我会建议他们去看Go语言的官方文档。其实老许和七牛在Go语言方面的积累绝对可以出一本好书,这本实在令人有些失望。

最后再说些有点关联又有些无关的话,好东西不需要太多修饰,多多描述就行了。不光是面向技术,还有其他方面。例如,不给牛人带高帽子他们就不是牛人了吗