购买
下载掌阅APP,畅读海量书库
立即打开
畅读海量书库
扫码下载掌阅APP

第4条
使用Go语言原生编程思维来写Go代码

经过十几年的演进和发展,Go语言在全世界范围内已经拥有了百万级别的拥趸,在这些开发者当中,有一部分新入行的编程语言初学者,而更多的是从其他编程语言阵营转过来的开发者。由于Go语言上手容易,在转Go的初期大家很快就掌握了Go的语法。但在编写一些Go代码之后,很多人感觉自己写的Go代码很别扭,并且总是尝试在Go语言中寻找自己熟悉的上一门语言中的语法元素。自己的Go代码风格似乎与Go标准库、主流Go开源项目的代码在思考角度和使用方式上存在不小差异,并且每每看到Go核心开发团队的代码时总有一种醍醐灌顶的感觉。出现这种情况的主要原因是大脑中上一门编程语言的思维方式在“作祟”。那么思维与语言之间究竟有什么联系呢?

4.1 语言与思维——来自大师的观点

在人类自然语言学界有一个很著名的假说——“萨丕尔-沃夫假说”,这个假说的内容是这样的:“语言影响或决定人类的思维方式。”

说到这个假说,我们不能不提及在2017年年初国内上映的一部口碑不错的美国科幻大片《降临》,这部片子改编自雨果奖获得者、华裔科幻小说家Ted姜的《你一生的故事》。片中主线剧情的理论基础就是“萨丕尔—沃夫假说”。更夸张的是,片中直接将该假说应用到外星人语言上,将其扩展到宇宙范畴。片中的女主作为人类代表与外星人沟通,并学会了外星语言,从此思维大变,拥有了预知未来的超能力,这也算是语言影响思维的极致表现了。

奇妙的是,在编程语言界,有位大师级人物也有着与“萨丕尔-沃夫假说”异曲同工的观点和认知,他就是首届图灵奖得主、著名计算机科学家艾伦·佩利(Alan J. Perlis),他从另外一个角度提出:“不能影响到你的编程思维方式的编程语言不值得学习和使用。”

4.2 现实中的“投影”

从上述大师们的理论和观点中我们看到了语言与思维之间存在着某种联系。那么两者间的这种联系在真实编程世界中的投影又是什么样子的呢?我们来看一个简单的编程问题——素数筛。

问题描述:素数是一个自然数,它具有两个截然不同的自然数除数:1和它本身。这里的问题是如何找到小于或等于给定整数n的素数。针对这个问题,我们可以采用埃拉托斯特尼素数筛算法。

算法描述:先用最小的素数2去筛,把2的倍数筛除;下一个未筛除的数就是素数(这里是3)。再用这个素数3去筛,筛除3的倍数……这样不断重复下去,直到筛完为止(算法图示见图4-1)。

图4-1 素数筛算法图示

下面是该素数筛算法的不同编程语言的实现版本。

(1)C语言版本


// chapter1/sources/sieve.c

#include <stdio.h>

#define LIMIT  50
#define PRIMES 10

void sieve() {
int c, i,j,numbers[LIMIT], primes[PRIMES];

for (i=0;i<LIMIT;i++){
    numbers[i]=i+2; /*fill the array with natural numbers*/
}
for (i=0;i<LIMIT;i++){
    if (numbers[i]!=-1){
        for (j=2*numbers[i]-2;j<LIMIT;j+=numbers[i])
            numbers[j]=-1; /* 筛除非素数 */
    }
}
c = j = 0;
for (i=0;i<LIMIT&&j<PRIMES;i++) {
    if (numbers[i]!=-1) {
        primes[j++] = numbers[i]; /*transfer the primes to their own array*/
        c++;
    }
}
for (i=0;i<c;i++) printf("%d\n",primes[i]);
}

(2)Haskell版本


// chapter1/sources/sieve.hs

sieve [] = []
sieve (x:xs) = x : sieve (filter (\a -> not $ a `mod` x == 0) xs)

n = 100
main = print $ sieve [2..n]

(3)Go语言版本


// chapter1/sources/sieve.go

func Generate(ch chan<- int) {
for i := 2; ; i++ {
    ch <- i
}
}

func Filter(in <-chan int, out chan<- int, prime int) {
for {
    i := <-in
    if i%prime != 0 {
        out <- i
    }
}
}

func main() {
ch := make(chan int)
go Generate(ch)
for i := 0; i < 10; i++ {
    prime := <-ch
    print(prime, "\n")
    ch1 := make(chan int)
    go Filter(ch, ch1, prime)
    ch = ch1
}
}

对比上述三个语言版本的素数筛算法的实现,我们看到:

■C版本的素数筛程序是一个常规实现。它定义了两个数组numbers和primes,“筛”的过程在numbers这个数组中进行(基于纯内存修改),非素数的数组元素被设置为-1,便于后续提取。

■Haskell版本采用了函数递归的思路,通过“filter操作集合”,用谓词(过滤条件)\a -> not $ a `mod` x == 0筛除素数的倍数,将未筛除的数的集合作为参数传递归递给下去。

■Go版本程序实现了一个并发素数筛,它采用的是goroutine的并发组合。程序从素数2开始,依次为每个素数建立一个goroutine,用于作为筛除该素数的倍数。ch指向当前最新输出素数所位于的筛子goroutine的源channel。这段代码来自Rob Pike的一次关于并发的分享 [1] 。Go版本程序的执行过程可以用图4-2立体地展现出来。

图4-2 Go版本素数筛运作的示意图

[1] http://golang.org/s/prime-sieve

4.3 Go语言原生编程思维

由上述现实中的问题可以看到,面对同一个问题,来自不同编程语言的程序员给出了思维方式截然不同的解决方法:C的命令式思维、Haskell的函数式思维和Go的并发思维。结合“萨丕尔—沃夫假说”,我们可以得到一个未经理论证实但又确实对现实有影响的推论: 编程语言影响编程思维,或者说每种编程语言都有属于自己的原生编程思维。

Go语言诞生较晚,大多数Gopher(包括笔者在内)的第一语言并不是Go,而是“半路出家”从其他语言(如C、C++、Java、Python等)转过来的。每种语言都有自己的原生编程思维。比如:C语言相信程序员,提供了指针和指针运算,让C程序员天马行空地发挥,接近底层的直接内存操作让C程序拥有很高的性能;C++支持多范式(命令式、OO和泛型),虽不强迫程序员使用某个特定的范式,但推荐使用最新代表现代语言发展特色的泛型等高级范式;Python语言更是形成了Pythonic规则来指导Python程序员写出符合Python思维或惯用法的代码。经验告诉我们,但凡属于某个编程语言的高质量范畴的代码,其必定是在这种编程语言原生思维下编写的代码。如果用A语言的思维去编写B语言的代码(比如用OO思维写C代码,用命令式的思维写Haskell代码等),那么你写出的代码多半无法被B语言社区所认可,更难以成为高质量代码的典范。并且,如果沿着这样的方向去学习和实践B语言,那么结果只能是南辕北辙,与编写出高质量代码的目标渐行渐远。

那么Go原生编程思维究竟是什么呢?一门编程语言的编程思维是由语言设计者、语言实现团队、语言社区、语言使用者在长期的演进和实践中形成的一种统一的思维习惯、行为方式、代码惯用法和风格。Go语言从诞生到现在已经有十年多了。经过Go设计哲学熏陶、Go开发团队的引导和教育、Go社区的实践,Go语言渐渐形成属于自己的原生编程思维,或者说形成符合Go语言哲学的Go语言惯用法(idiomatic go)。它们是Go语言的精华,也是构建本书内容的骨架,并值得我们用一本书的篇幅去详细呈现。因此可以说,阅读本书的过程也是学习和建立Go语言原生编程思维的过程。

我们的目标是编写出高质量的Go代码,这就需要我们在学习语言的同时,不断学习Go语言原生的编程思维,时刻用Go编程思维考虑Go代码的设计和实现,这是通往高质量Go代码的必经之路。

小结

人类在通过自然语言交流和表达观点的漫长过程中,逐渐形成了固定的语言表述方法。除此之外,人类还利用肢体动作、眼神、表情、纸笔等辅助行为或工具来帮助语言的精确表达,并且在使用这些辅助行为和工具时形成了固定的使用方法,这些以这门语言为中心的固定的表述方法、辅助行为和工具用法总称为这门语言的惯用法,它们反映的就是该语言的思维方式。

编程语言也类似,以一门编程语言为中心的,以解决工程问题为目标的编程语言用法、辅助库、工具的固定使用方法称为该门编程语言的原生编程思维。图4-3是自然语言思维与编程语言思维的对比。

图4-3 自然语言思维与编程语言思维

我们学习和使用一门编程语言,目标是用这门语言的原生思维方式编写高质量代码。学习Go,就要用Go的原生编程思维而不是用其他语言的思维方式写Go代码。掌握Go原生编程思维就是我们通往高质量Go编程的学习方向和必经之路,因此本书后面将从语言、标准库、工具链、工程实践等方面来全面介绍Go语言的原生编程思维,帮助大家打好编写高质量Go代码的基础。 t1unnEZ7r3X/pGX91+04f1KpLfOWWQpMCI7sxwcQY442xjNzBzCuL8sDZXUHOji9

点击中间区域
呼出菜单
上一章
目录
下一章
×