Go 1.14更新:

Module support in the go command is now ready for production use.
We encourage all users to migrate to go modules for dependency management.

Go moduleGo module

但是泛型功能什么时候才能来呢😂。。

Go语言已经被很多大型企业使用在其基础设施的开发了。作为一个新生语言,为什么它有这么大的魅力呢?下面我整理了网上各位大佬的观点,配合我自己对Go的理解,来总体介绍一下这门语言的优缺点(主要以C/C++、Java、Python作为比较对象)。

如果你是C/C++、Java、Python的开发者,可以完全看懂这篇文章。快来和我一切速览Go语言吧!

我这里是以go 1.13作为基准的。

本文不是Go语言教程,不介绍语言细节,只是粗略地展示一下Go语言。

本文将会持续更新~

下面的目录供你先预览:

Go优点

开发效率

auto
Python

虽然Go的写法很有动态类型语言的感觉,但是它实际上是一个静态类型语言,这样就不会有动态语言的缺点,能在编译时检查出很多问题(动态语言只能在运行时检查出来)。

下面我们简单对比一下Go和Java声明变量的不同:

java:

// 简单变量
Integer a = 23;
// 对象
Person person = new Person();
// 通过方法获取
Something sth = createSomething();

go:

// 简单变量
a := 23
// 对象
person := Person{}
// 通过方法获取
sth := CreateSomething()

Go是不是很有Python的感觉呢?

运行效率

Go语言的运行效率是很高的。目前Go的运行效率和Java差不多,但是Go比优化了多年的Java年轻的多,因此潜力也更多。

在高并发的情况下,Go的表现会更好。因此很多企业将Go作为服务器语言,用于替换原先C++的位置。

虽然Go比C++还是要慢的,但是它的开发效率比C++实在是高上太多了,在硬件越来越便宜的今天,Go未来在服务器基础设施领域必定会占据更多市场。

少即是多(缺点?)

Go语言遵循“少即是多”的设计理念,提供更少的语言特性。这会让Go语言显得不那么“臃肿”。特别是OOP,Go语言在OOP上更像C语言,将OOP神秘的面纱揭示得一干二净。在Go中,仅提供了结构体、组合等少数几个功能,没有直接提供继承等功能,OOP只是一个语法糖。

甚至对于封装,Go语言也只是一个命名的事而已(首字母大写即为public,小写为private)。

下面我们用Java和Go进行一个简单的对比:

java:

public class Student {
    private String name;
    private int age;

    public Student(String name, int age) {
        this.name = name;
        this.age = age;
    }

    public void show() {
        System.out.println(this.name, this.age)
    }
}

go:

type Student struct {
    name string
    age  int
}

func NewStudent(name string, age int) Student {
    return Student{name: name, age: age}
}

func (student Student) Show() {
    fmt.Println(student.name, student.age)
}

这种一切从简的设计思路很受很多C语言程序员的喜爱,但是也有一些从高级语言转过来的人认为,Go提供的语言特性太少了;当然也有人觉得这种特性少的语言更能剖析编程的本质,写出来的代码更加有美感。

当然,给小白的好处就是,Go学习起来比其他语言简单,特别是对于C/C++程序员来说,转型做Go是很简单的。

当然,关于“少即是多”是好是坏,就仁者见仁智者见智了。

gofmt格式统一

go语言提供了很多小工具,其中最受欢迎的是gofmt。gofmt可以把任意格式的Go语言源代码统一格式化为统一的格式。

这样,有了官方指定的格式,我们终于不用为了代码格式的统一吵得焦头烂额了。直接gofmt一下即可。

并发

这是Go的一大卖点,Go是原生支持并发的,并且Go的并发单位是协程(在Go中被叫做goroutine)。

关于协程的介绍,请见:Go协程。

go

下面再来对比Java和Go:

java:

package com.test.main;

import java.lang.Runnable;
import java.lang.Thread;

class Worker implements Runnable {
    @Overried
    public void run() {
        System.out.println("run concurrent");
    }
}

public class Main {
    public static void main(String[] args) {
        Worker worker = new Worker();
        new Thread(worker).start()
    }
}

go:

package main

import "fmt"

func main() {
    go func() {
        fmt.Println("run concurrent")
    }()
}

怎么样,是不是写起来比Java简单多了。

ExecutorLockCountDownLatchCyclicBarrierSemaphoreExchanger
LockWaitGroupCountDownLatch

而且,channel也是Go内置支持的呢。

部署简单

Go的build输出的直接是可以运行的二进制文件,这就比Java简单多了。这意味我们的部署只需要简单地把一个二进制文件丢到服务器上运行即可(甚至服务器不需要安装Go环境),而如果是Java,还需要在服务器上安装一个jre。这对运维部署人员来说是个好事。

Go的部署也比C/C++简单,Go不需要什么繁琐的静态链接、动态链接的过程。所有代码仓库都会被编译到一个可执行文件中(当然,这也有可能导致Go的可执行文件比较大)

在大多数时候,只需要下面一行命令即可编译Go项目为可执行文件:

$ go build -o runnable main.go
maven

httpjsonnettextruntime

自带map

mapstlboost

下面我们还是拿Java和Go做个对比:

java:

Map<String, Object> man = new HashMap<>();

man.put("name", "Wang");
man.put("age", 18);
man.put("birthday", new Date());

System.out.println("name =", man.get("name"));
System.out.println("age =", man.get("age"));
System.out.println("birthday =", man.get("birthday"));

go:

man := make(map[string]interface{})

man["name"] = "Wang"
man["age"] = 18
man["birthday"] = time.Now()

fmt.Println("name =", man["name"])
fmt.Println("age =", man["age"])
fmt.Println("birthday =", man["birthday"])

这样的写法更像Python。

生态圈

Go有一个杀手级别的项目:Docker。以及Kubernetes。这两个东西的火热程度不用说大家心理已经知道。

以容器技术在未来的趋势,只要Docker不倒,Go在容器领域就会一直火热下去。

测试

assert

在测试失败后,Go也不会马上终止测试程序,而是会把测试程序坚持运行完毕。

Go的测试远不如此,Go支持并行测试、基准测试等。对于服务器-客户端程序的测试也有支持。

关于Go的测试,这里有一个很好的教程:Go testing教程。

Go缺点

吹完了Go,作为一个新生语言,Go还是有很多缺点的。许多地方甚至为人诟病。作为一个转型做Go的,我当然希望Go越来越好,所以我们需要直面这些问题。

包管理(go module大法好)

vendor
  • 项目之间不能复用依赖
  • 依赖没有版本控制,涉及到依赖的版本更新、回退、多版本共存等问题时,你会感到绝望
  • 如果一个依赖引用了其它依赖,你也会绝望的
vgo
go modulego.mod
go module

错误处理

try...catch
result, err := fun()
if err != nil {
    // handle error
}
wrapper

所以Go并不非常适合写业务代码,目前行业内写业务还是以Java这种语言为主。

try...catch

java:

public class TestTryCatch {

    public static void main(String[] args) {
        try {
            throw new Exception();
        } catch (Exception e) {
            System.out.Println("发生了错误!");
        }
    }
}

go:

package main

import "fmt"

func main() {

	defer func() {
		if err := recover(); err != nil {
			fmt.Println("产生了错误!")
		}
	}()

	panicFun()

}

func panicFun() {
	panic("我是错误")
}
deferrecoverpanictry...catch

缺乏泛型

这是我个人非常讨厌的一点。作为一个静态语言,Go居然没有泛型。这就像回到了几十年前的Java一样。

JDK1.5

然而在2020年,Go仍然在使用大量的类型向下转型。

interface{}Object

希望在Go未来的版本看到泛型。

缺少框架(优点?)

不像Java Spring,Go没有一个大一统的框架。光是在web框架领域,就有很多选择:

  • gin
  • beego
  • iris
  • echo

当然,这也和Go自带的库很强大有关系,很多人不喜欢框架,觉得框架限制了他们发挥的空间;也有人觉得框架能够快速开发,符合现代开发的要求。

这点就因人而异了。

缺少更多的数据结构

Go语言只提供了数组、Slice和map。对于栈、堆、队列等其它数据结构,以及并发安全的数据结构,并没有直接的支持。

interface{}

所以如果我们要实现某个数据结构,只能针对自己的struct手撸了。

GC

在Go GC经过了以下的发展阶段:

  • Go 1.3之前:STW(Stop The World) 非常简陋的GC算法,在内存超过阈值或定时的条件下,暂停所有任务,执行mark+sweep(标记清除)。在高内存场景下,这意味着任务的长时间停顿,是一种灾难。
  • Go 1.3:Mark STW + Sweep。将mark和sweep分开。但是也需要暂停所有任务,但是暂停过程只进行mark,mark之后恢复其它任务,sweep通过协程异步进行。这在一定程度上减少了GC的开销,减少STW的时间。
  • Go 1.5:三色标记法。对mark进行改进,使mark可以和用户任务并发执行。这种方法的mark操作是可以渐进执行的而不需每次都扫描整个内存空间,可以进一步减少STW的时间。
  • Go 1.8:混合写屏障(hybrid write barrier)允许堆栈扫描永久地使堆栈变黑(没有STW并且没有写入堆栈的障碍),这完全消除了堆栈重新扫描的需要,从而消除了对堆栈屏障的需求。使用这种方法可以将STW的时间降低到1毫秒以下

如果你的Go程序突然出现卡顿,就可能是GC的原因,就需要花时间去优化,减少Go内存的压力。

在1.8版本以后,GC的延迟性下降了很多,但是因为需要并行处理GC,线程间同步和多余的数据生成复制都会占用实际逻辑业务代码运行的时间。因此程序的吞吐量会下降,STW可以提高程序的吞吐量。

Go GC和Java JVM相比,差距还是很大的。但是Go毕竟是一个比较年轻的语言,给它时间进行发展,未来GC一定会越来越好。

字段标记灾难

看下面一段代码,这是来自Go官方文档的一个例子:

type Test struct {
    Label         *string             `protobuf:"bytes,1,req,name=label" json:"label,omitempty"`
    Type          *int32              `protobuf:"varint,2,opt,name=type,def=77" json:"type,omitempty"`
    Reps          []int64             `protobuf:"varint,3,rep,name=reps" json:"reps,omitempty"`
    Optionalgroup *Test_OptionalGroup `protobuf:"group,4,opt,name=OptionalGroup" json:"optionalgroup,omitempty"`
}

字段标签是Go提供的一个功能,用于给Go字段进行注释。在使用json的过程中,字段标签非常重要。

json

如果涉及到更多协议相关的内容,则标签会更长。冗长的标签会让结构体代码的可读性下降。

不是总结的总结

总体来说,Go还是一门不错的语言的。它写起来很像Python,却有着Python远不及的性能。Go也有着很低的入门门槛。

当然,Go最吸引人的地方就是它的协程了。协程的概念让我们在编程中可以几乎随心所欲地创建并发任务而不用太多考虑开销。也让我们的并发编程变得更加简单。

在高并发领域,Go可以说是越来越受欢迎,很多公司都使用Go来构建他们的基础服务器设施。Go让C/C++程序员远离构建的痛苦,专注于开发。

Go当然还有很多缺点,在业务编写上面,为很多人诟病。和Java还有很大的差别,生态圈也不如Java丰富。Go还有很多不为人知的陷阱(可以详见我的“Go基础笔记”系列)。

希望Go在未来能够越来越好。