为什么选择Go语言?

最近我迷上了Go语言,这个编程语言中的新贵果然没让我失望,语法简练,达意深刻,而且提供的特性很是令人鼓舞,规避了很多之前语言(C/C++/Java)上出现的问题和使用上的不方便,我决定深入的学习一下。

那么为什么要选择Go语言,下面是我能想到的几个原因:

  1. 出身名门:我们知道Go语言出自Google公司,这个公司在业界的知名度和实力自然不用我多说。Google公司聚集了一批牛人,在各种编程语言称雄争霸的局面下推出新的编程语言,自然有她的战略考虑,而且从Go语言的发展态势来看,Google对她这个新的宠儿还是很看重的,自然有一个良好的发展前途。

  2. 血统纯正:让我们看看Go语言的主要创造者,你就可见端倪了。

    (1) 肯·汤普逊(Ken Thompson):设计了B语言和C语言,创建了Unix和Plan 9操作系统,1983年图灵奖得主。Go语言的共同作者。
    (2) 罗布·派克(Rob Pike):Unix小组的成员,参与Plan9和Inferno操作系统,参与 Limbo和Go语言的研发,《Unix编程环境》作者之一。
    (3) 罗伯特·格里泽默(Robert Griesemer):曾协助制作Java的HotSpot编译器和Chrome浏览器的JavaScript引擎V8。
    (4) 拉斯· 考克斯(Russ Cox):参与Plan 9操作系统的开发, Google Code Search项目负责人。

    上面列出的Go语言的设计者和实现者中的一部分人,就足以说明了Go语言的分量。这些业界的大佬们,之所以肯花费精力搞出这么个新的编程语言,而且不是在小范围的实验环境中使用,而是推向普遍的软件开发界,自然是卯足了劲,把平生的绝学和思考融入其中。

  3. 时代浪尖:Go语言被称为互联网时代的C语言。包含了两层意思:一是Go语言的执行效率很高,像目前流行的C/C++编译性语言一样运行高效;二是Go语言的特性很适应时代的潮流,像目前流行的Python/Ruby动态语言一样轻松自如。也正是这两点吸引了我,既有动态语言之形,又有静态语言之实,这得多么NB的语言才能做到啊?

  4. 自由高效:Go语言意味着更自由、更高效和一站式的编程体验,开发效率和运行效率之间的完美融合,以及天生的并发编程支持。Go语言支持当前所有的编程范式,包括过程式编程、面向对象编程、以及函数式编程。程序员们可以各取所需,自由组合,想怎么玩就怎么玩。

Go语言的特性

下面我就从几个方面大致的介绍一下Go语言的特性,也是对上一节为什么选择Go语言的补充吧。

  1. 自动垃圾回收机制
    内存管理一直是C/C++程序员的痛点,总是羡慕着java程序员们可以放心大胆的前行,而不用担心哪块内存没有释放。现在Go语言就实现了C/C++一脉程序员的梦想,在Go中,你再也看不到free和delete的身影,你可以放心大胆地使用任何变量而不用操心它什么时候被释放,Go帮你搞定了这一切。这就是Go语言强大的自动垃圾回收机制。

  2. 更丰富的内置类型
    Go语言中除了常规的基础类型(数字类型、布尔类型,字符串类型,复数类型)外,更是引入了强大的数组类型(array)、切片类型(slice)、字典类型(map),这些类型在其它的编译性语言中往往是通过引用库的方式来使用,而在Go语言中,直接作为内置类型来使用。

  3. 函数多返回值
    函数支持多返回值,也是Go语言的一大创举,在目前主流的编程语言中除了Python外,还没有其它编译性语言像Go语言一样支持函数多返回值。这个特性有效的解决了以前编程中遇到的通过繁琐的函数输出参数来返回更多的值的问题,让代码看起来更加的优雅和可读。这个特性在Go中典型的应用场景之一就是返回函数的执行状态,比如Go的标准库中很多的函数接口,就是用最后额外的一个返回值来标示函数是否执行成功。
    例如下面的代码片段所示:

    func main() {
        fmt.Println(order(2, 7))
        fmt.Println(order(7, 2))
    }
    
    func order(n1, n2 int) (int, int) {
        if n1 < n2 {
        return n1, n2          
        }
        return n2, n1
    }
    

    输出结果是:

    2 7
    2 7

    函数order接受两个int类型的数字,然后返回按照大小排序的两个数字,main函数使用fmt.Println()函数直接把这两个排好序的数字打印到控制台上。

  4. 错误处理机制
    Go语言采用了独创的新的错误处理机制,抛弃了之前主流语言中使用的try-catch错误处理机制,因为在Go语言的设计哲学中推崇简单精致,而传统的try-catch结构会破坏程序的可读性和维护性,让开发者无需仅仅为了程序安全性而添加大量一层套一层的try-catch语句。
    Go语言引入了3个关键字用于标准的错误处理流程,这3个关键字分别为defer、panic和recover。但是Go并不推崇大量使用该机制,而是推荐采用传统的判断接口返回状态的方法来保证执行的流程安全,而这种新的错误处理机制只有在万不得已的情况下才推荐使用。

  5. 匿名函数和闭包
    在Go语言中函数也是值类型,具有跟其它类型一样的地位,可以把函数赋值给一个变量,可以作为参数传递给函数,也可以从函数中返回一个函数。在Go中,匿名函数指没有指定名字的函数,而闭包是匿名函数中一种,它跟常规的匿名函数的区别就是闭包可以捕获到当前有效区域内的任何常量和变量,并且在自己的函数体中使用,而不用担心这些常量和变量超出有效范围而无效。
    例如下面的代码片段所示的就是一个闭包:

    func main() {
        fn := plusX(3)
        fmt.Println(fn(2))
    }
    
    func plusX(x int) func (int) int {
        return func (num int) int {
        return num + x
        }
    }
    

    输出结果是:

    5

    我们在plusX函数中我们定义了一个闭包(使用func定义,没有函数名),同时把这个闭包返回,赋值给了fn变量,然后我们使用fn变量来调用这个闭包。注意在闭包的函数体中我们使用了plusX函数的参数x,然后跟闭包本身的参数num相加,返回这个结果。正是因为在匿名函数中使用了外部的变量x,我们才称这个匿名函数为闭包。

  6. 非侵入式接口
    在Go语言中使用struct和interface两个关键字来构建复杂的类型系统,但是Go并不像之前主流的面向对象编程语言(C++/Java)那样支持继承和重载,这看起来似乎是语言上一个倒退,但恰恰相反,是Go语言的设计者认识到继承在编程实现中带来的诸多弊端,而从形式上抛弃了这种典型的面向对象的特征,而采用了更加高效和更加优雅的非侵入式接口来实现继承的概念,而且Go语言推荐采用最基本的类型组合的方式来构建更加复杂的类型。
    非侵入式接口的概念,通俗讲就是duck type(鸭子类型)——When I see a bird that walks like a duck and swims like a duck and quacks like a duck, I call that bird a duck.(当我看到一个鸟走起来像鸭子,游起来像鸭子,叫起来像鸭子,那么我就认为那个鸟就是一个鸭子)。在Go语言中,表现duck type的方式就是指当一个类型实现了一个接口的所有方法,那么这个类型就可以作为那个接口来使用,而不管这个类型和那个接口在形式上有没有任何关联。
    下面看一个简单的例子:

    package main
    
    import (
        "fmt"
    )
    
    type I interface {
        Get() int
        Set(int)
    }
    
    type P struct { i int }
    func (p *P)Get() int {
        return p.i
    }
    func (p *P)Set(num int) {
        p.i = num
    }
    
    func fn(t I) {
        fmt.Println(t.Get())
        t.Set(2)
    }
    
    func main() {
        p := P{1}
        fmt.Println(p)
        fn(&p)
        fmt.Println(p)
    }
    

    输出结果是:

    {1}
    1
    {2}

    我们定义了一个interface I,有两个方法:Get和Set;然后又定义了一个struct P,在P中我们实现了Get和Set方法。虽然P和I没有语法上的继承关系,但这时我们就可以说P实现了I,而且接受I的地方就可以传递P进入,接下来的fn函数就是为了演示这个目的。在main函数中,我们创建了一个P的实例,并且在创建时初始化成员变量i为1,然后传递给fn函数处理后,成员变量i变为了2。需要注意的是调用fn函数时,我们传递的是p的指针,之所以这样做,是因为在实现Get和Set方法时,我们是以*P作为函数前导符的,这样就可以修改对象的内部状态。

  7. 并发
    Go语言中引入了比线程更加轻量级的协程——goroutine,并且使用消息在不同的协程中传递数据而不是使用共享内存,这就使得并发编程变得非常的简单和有趣。
    请看下面一个简单的关于并发编程的例子:

    package main
    
    import (
        "fmt"
        "time"
    )
    
    var c chan int
    
    func main() {
        c = make(chan int)
        go job("study", 2)
        go job("work", 3)
        fmt.Println("I am waiting, but not tool long.")
        <-c
        <-c
    }
    
    func job(job string, sec int) {
        time.Sleep(time.Duration(sec) * time.Second)
        fmt.Println(job, "is ready!")
        c <- 1
    }
    

    输出结果是:

    I am waiting, but not tool long.
    study is ready!
    work is ready!

    在程序的main函数中,我们首先使用make创建了一个传递int类型的channel(类似于Unix下的管道),然后启动了两个goroutine,用来演示进行两个单独的工作流程,之后就打印一句话表示自己正在等待这两项工作的完成,等待完成的标记就是从channel里可以读取到两个数字(相当于goroutine完成的通知,具体什么数字不重要)。而在job函数里,也就是goroutine要执行的单独工作任务里,首先我们让程序Sleep几秒,以表示我们在工作,然后打印一句话表示我们工作完成了,最后通过向channel中写入一个数字1来结束这个流程。这个小例子完美地给我们演示了如何执行goroutine,并且如何让多个goroutine之间保持同步。

  8. 反射
    反射功能也一种高级的语言特性,通过反射,你可以获取对象类型的详细信息,并可动态操作对象。Go语言的反射实现了反射的大部分功能,但没有像Java语言那样内置类型工厂,故而无法做到像Java那样通过类型字符串创建对象实例。在Java中,你可以读取配置并根据类型名称创建对应的类型,这是一种常见的编程手法,但在Go语言中这并不被推荐。
    反射最常见的使用场景是做对象的序列化(serialization,有时候也叫Marshal & Unmarshal)。
    反射是把双刃剑,功能强大但代码可读性并不理想。若非必要,并不推荐使用反射。

  9. 语言交互性
    Go语言与C语言的天生联系,使得在Go语言中调用C的代码非常的简单,使用Cgo功能就可以轻松做到。
    比如下面的代码所示:

    package main
    /*
    #include <stdio.h>
    #include <stdlib.h>
    */
    import "C"
    import "unsafe"
    func main() {
        cstr := C.CString("Hello, world")
        C.puts(cstr)
        C.free(unsafe.Pointer(cstr))
    }
    

    输出结果是:

    Hello, world

    Go语言就是通过标准库中的“C”包来调用C的相关的函数的,需要注意的是上面注释中的C风格的包含头文件不能缺少哦,不然编译通不过的。

Go语言的设计哲学

  1. Go语言的设计规则非常的灵活,集众多编程范式于一身,这样程序员就可以仁者见仁、智者见智,选择自己喜欢的编程范式Coding,快乐无穷啊。

  2. 相对于设计规则上的灵活,Go语言有着明确且近乎苛刻的编码规范。这样设计的意义深刻而有必要,相当于从语言层面规范了程序员的编码习惯,而不用项目团队一遍又一遍地从行政手动上加以约束。全世界所有Go语言的程序员写出来的代码风格都一致,这对于代码共享和信息交流是多么深刻而令人欢欣鼓舞的一件事情啊。Perl语言的风格灵活诡异,让人想起来都不寒而栗,看别人的Perl项目代码可能就是噩梦的开始。而Go语言很好地从语言层面上规范了这一点。

  3. Go语言把软件工程的实践理念集成了进来,自带了非常丰富的标准命令,涵盖了软件生命周期(开发、测试、部署、维护等等)的各个环节。这对于程序员来说是一个极大的便利,不用再像之前那样需要额外的工具来组织管理代码、构建工程编译测试需要的各种步骤,省去了很多的不必要的琐碎事项,而只需要关注代码本身即可。

  4. Go语言强调简单、安全、有效的设计理念。这样的设计理念也是设计者们出于多年的软件开发经验提炼出来的思想精华,目的都是为了解决软件开发中实际存在的一些问题。比如Go语言提倡消息传递,而不是共享数据。共享数据的使用,虽然强大也很方便,但带来的问题也很巨大,让程序出错的几率上升,后续的维护变得艰难,程序规模变大后将处于不可控的危险境地。因为在多线程的场景下对共享数据的同步访问一直都是软件开发中一个难点,很多有经验的程序员在碰到这块雷区时都会小心翼翼、如履薄冰。再比如Go语言强调使用简单的组合的方式来构建更复杂的数据类型,而不是之前典型的面向对象编程中使用的继承的方法来构建。这同样也是实际的软件开发项目中遇到的问题。继承的理念不管有多么的合理和强大,但软件项目满天飞的继承关系,会把这个项目给彻底的断送,让项目的后续维护者们叫苦连天。Go语言正是抛弃了一些看似强大,实际上会增加软件开发复杂度的设计理念,返璞归真,回归到朴实。所谓大道至简,我想这正是Go语言的设计者们所追求的目标。

  5. Go语言是云计算时代的编程语言,是互联网时代的C语言,所以在设计上它关注高并发,关注开发效率和运行效率的平衡。使用Go语言,既可以享受到类似于Python等动态语言的强大灵活,而可以收益于类似于C/C++/Java等静态编译语言的运行效率。这就是时代的召唤。