标签 Java 下的文章

“能力越大,责任越大” – Go语言之父详解将于Go 1.18发布的Go泛型

img{512x368}

注:本文是首发于笔者微信公众号“iamtonybai”上的付费文章,这里免费分享给大家!

在2020.11.9~11.13举行的全球最具影响力的Go语言技术大会GopherCon 2020上,Go语言之父之一的Robert Griesemer为全世界Gopher们带来了本次大会最重量级的演讲“Typing [Generic] Go”

img{512x368}

图:Robert Griesemer带来的有关Go泛型演讲

在这个演讲中,Robert Griesemer向Gopher们介绍了自从今年中旬在Go官网发表文章“The Next Step for Generics”以来Go泛型(Go Generics)技术草案的最新变化,并详细介绍了类型参数(type parameter)是如何满足Go现有的类型系统的,以及Go编译器是如何对Go泛型代码进行类型检查的。

本文整理了此次演讲的重点内容,供广大Gopher参考,希望能为大家理解Go泛型带来帮助。

一. 预备知识

为了更好地理解Robert Griesemer的讲解,这里先带着大家回顾一下Go generics技术草案演化史。

img{512x368}

图:Go泛型技术草案演化时间线

  • 2017年7月,Go核心团队领军人物Russ Cox在Gophercon 2017大会上发表演讲“Toward Go 2”,正式吹响Go向下一个阶段演化的号角;
  • 2018年8月,在Gophercon 2018大会结束后不久,Go核心团队发布了Go2 draft proposal,这里面涵盖了由Ian Lance Taylor和Robert Griesemer操刀主写的Go泛型的第一版draft proposal。这版草案引入了contract关键字来定义泛型类型参数(type parameter)的约束、类型参数放在普通函数参数列表前面的小括号中,并用type关键字声明:
// 第一版泛型技术草案中的典型泛型语法

contract stringer(x T) {
    var s string = x.String()
}

func Stringify(type T stringer)(s []T) (ret []string) {

}
// 简化后的contract语法如下:

contract stringer(T) {
    T String() string
}
  • 2020年6月,《Featherweight Go》论文发表在arxiv.org上,该论文缘于Rob Pike向著名计算机科学家、函数语言专家、Haskell语言的设计者之一、Java泛型的设计者PHILIP WADLER发出的一次邀请,希望PHILIP WADLER帮助Go核心团队解决Go语言的泛型扩展问题:

img{512x368}

图:Rob Pike向PHILIP WADLER发出的邀请

而这篇论文则是对这次邀请的回应。这篇论文为Go语言的一个最小语法子集设计了泛型语法Featherweight Generic Go(FGG),并成功地给出了FGG到Feighterweight Go(FG)的可行性实现的形式化证明。

该篇论文采用monomorphisation(单态)的实现,而非Java使用的擦触法(Erasure),这样的好处之一是如果代码中没有使用任何泛型抽象,程序的运行时不会因支持泛型而承担额外的消耗。

该论文的形式化证明给Go团队带来了信心,也是的Go团队在一些语法问题上达成更广泛的一致。

img{512x368}

图:Robert Griesemer表达了对该论文团队的感谢

  • 2020.6月末,Ian Lance Taylor和Robert Griesemer在Go官方博客发表了文章《The Next Step for Generics》,介绍了Go泛型工作的最新进展。Go团队放弃了之前的技术草案,并重新编写了一个新草案。在这份新技术方案中,Go团队放弃了引入contract关键字作为泛型类型参数的约束,而采用扩展后的interface来替代contract。这样上面的Stringify函数就可以写成如下形式:
type Stringer interface {
    String() string
}

func Stringify(type T Stringer)(s []T) (ret []string) {
    ... ...
}

同时,Go团队还推出了可以在线试验Go泛型语法的playground:https://go2goplay.golang.org,这样gopher们可以直观体验新语法,并给出自己的意见反馈。

  • 2020年11月的GopherCon 2020大会,Griesemer与全世界Gopher同步了Go泛型的最新进展和roadmap,在最新的技术草案版本中,小括号被方括号取代,类型参数前面的type关键字也不再需要了:
func Stringify[T Stringer](s []T) (ret []string) {
    ... ...
}

go2goplay.golang.org也支持了方括号语法,gopher可以在线体验。

下面我们就来看看Griesemer对最新Go泛型技术草案的详细讲解

二. 类型参数(Type parameters)技术草案详解

这版草案与2019年中旬发布的草案的最大变动就是使用interface而不是contract来表达对类型参数的约束

该版设计的主要特性:

  • 类型参数(Type parameters) – 一种将类型或函数进行参数化的机制
  • 约束(Constraints) – 一种表达对类型参数的约束的机制
  • 类型推导(Type inference,可选)

普通函数参数列表 vs. 泛型函数的类型参数列表

我们知道,普通函数的参数列表是这样的:

(x, y aType, z anotherType)
  • x, y, z是形参(parameter)的名字,即变量;
  • aType,anotherType是形参的类型,即类型。

我们再来看一下类型参数(type parameter)列表:

[P, Q aConstraint, R anotherConstraint]
  • P,Q,R是类型形参的名字,即类型;
  • aConstraint,anotherConstraint代表类型参数的约束(constraint),可以理解为一种元类型(meta-type,即修饰类型的类型)。

注:按惯例,类型参数(type parameter)的名字都是头母大写的。

为什么需要类型参数(type parameter)

我们先来看一下当前Go语言标准库中提供的排序方案:

// $GOROOT/src/sort/sort.go
type Interface interface {
        Len() int
        Less(i, j int) bool
        Swap(i, j int)
}

func Sort(data Interface) {
    ... ...
}

为了应用这个排序函数Sort,我们需要让被排序的类型实现sort.Interface接口,就像下面例子中这样:

type IntSlice []int

func (p IntSlice) Len() int           { return len(p) }
func (p IntSlice) Less(i, j int) bool { return p[i] < p[j] }
func (p IntSlice) Swap(i, j int)      { p[i], p[j] = p[j], p[i] }

func main() {
        sl := IntSlice([]int{89, 14, 8, 9, 17, 56, 95, 3})
        fmt.Println(sl)
        sort.Sort(sl)
        fmt.Println(sl)
}

这真是我们想要的实现方式吗?我们真正需要的是这样的:

func Sort(list []Elem)

// 使用
var myList = []Elem{...}
Sort(myList)

解决办法:使用type parameter(类型参数或叫做参数化的类型,将类型作为参数传递):

img{512x368}

图:使用类型参数的Sort

约束(constraints)

约束(constraint)规定了一个类型实参(type argument)必须满足的条件要求。而在泛型Go中,我们使用interface来定义约束

如果某个类型实现了某个约束(规定的所有条件要求),那么它就是一个合法的类型实参。

下面是一个泛型版本的Sort函数:

func Sort[Elem interface{ Less(y Elem) bool }](list []Elem)

我们看到上面函数Sort的类型形参(type parameter)Elem的约束是一个interface,这样传入的类型实参(type argument)只要实现了该接口即可。

约束的定义中也可以引用类型形参,比如下面这个泛型函数:

img{512x368}

图:约束的定义中引用类型形参

类型形参的声明与作用域

img{512x368}

图:类型参数的声明与作用域

类型参数的作用域始于[,终于泛型函数的函数体结尾或泛型类型的声明结尾。

泛型的类型具化与类型检查

下面是一个使用泛型版本Sort函数的例子:

func Sort[Elem interface{ Less(y Elem) bool }](list []Elem)

type book struct{…}
func (x book) Less(y book) bool {…}
var bookshelf []book
…
Sort[book](bookshelf) // 泛型函数调用

上面的泛型函数调用Sort[book](bookshelf)将分成两个阶段:

  1. 具化(instantiation)

形象点说,具化(instantiation)就好比一家生产“排序机器”的工厂根据要排序的对象的类型将这样的机器生产出来的过程。以上面的例子来说,整个具化过程如下:

  • 工厂接单:Sort[book],发现要排序的对象类型为book;
  • 模具检查与匹配:检查book类型是否满足模具的约束要求(即是否实现了Less方法),如满足,则将其作为类型实参替换Sort函数中的类型形参,结果为Sort[book interface{ Less(y book) bool }]
  • 生产机器:将泛型函数Sort具化为一个新函数,这里将其起名为booksort,其函数原型为func([]book)。本质上booksort := Sort[book]
  1. 调用(invocation)

一旦“排序机器”被生产出来,那么它就可以对目标对象进行排序了,这和普通的函数调用没有区别。这里就相当于调用booksort(bookshelf),整个过程只需检查传入的函数实参(bookshelf)的类型与booksort函数原型中的形参类型([]book)是否匹配即可。

用伪代码来表述上面两个过程如下:

Sort[book](bookshelf)

<=>

具化:booksort := Sort[book]
调用:booksort(bookshelf)

泛型类型

除了函数可以携带类型参数变身为“泛型函数”外,类型也可以拥有类型参数而化身为“泛型类型”:

type Lesser[T any] interface{
   Less(y T) bool
}

上面代码中的any代表没有任何约束,等价于interface{}。

泛型类型的类型参数的声明与作用域范围

泛型类型的类型参数的声明方式如下,类型参数的作用域范围也同见下图:

img{512x368}

图:泛型类型的类型参数的声明与作用域

用泛型类型改造Sort

用泛型类型定义一个具名的约束条件- Lesser接口类型:

type Lesser[T any] interface{
   Less(y T) bool
}

使用Lesser[T]作为约束的Sort函数可以这样写:

func Sort[Elem Lesser[Elem]](list []Elem)

注意:任何泛型函数或泛型类型在使用前都必须先“具化(instantiation)”。

我们再来看看Sort函数的内部实现:

func Sort[Elem Lesser[Elem]](list []Elem) {
    ...
    var i, j int
    ...
    if list[i].Less(List[j]) {
        ...
    }
    ...
}
  • 这里的list[i]和list[j]的类型是Elem;
  • Elem不是一个接口类型,它是泛型函数(Sort)的类型参数,Lesser[Elem]是作为类型参数的约束而存在的,不要与函数常规参数列表混淆。

再次强调:类型参数是一个真实的类型,不是一个接口类型(变量),当然我们可以使用一个接口类型作为类型实参来具化一个泛型函数或泛型类型

实参类型自动推导(Argument type inference)

我们是想要:

Sort[book](bookshelf)

还是:

Sort(bookshelf)

显然是后者。我们希望Go编译器能够根据传入的变量自动推导出类型参数的实参类型。

img{512x368}

图:实参类型的自动推导

这样,在具化之前,如果泛型函数调用没有显式提供实参类型,那么Go编译器将进行自动实参类型推导。有了是实参类型的自动推导,大多数泛型调用的方式与常规函数调用一致

类型列表(type lists)

到这里,约束仅限于描述方法要求。下面的函数调用仍然无法工作:

Sort([]int{1, 2, 3})

因为原生的int类型不满足Elem的约束,没有实现Less方法。虽然我们可以用下面替代方法实现整型切片的排序:

type myInt int
func (x myInt) Less(y myInt) bool { return x < y }

但这还是太麻烦了。

Go泛型扩展了interface语法,除了让interface拥有自己的方法列表外,还支持在interface中定义类型列表(type list):

type Float interface {
   type float32, float64
}

// float32和float64都可以作为类型实参传递给Sin
func Sin[T Float](x T) T

现在,一个类型实参要想满足约束,要么它实现了约束中的所有方法,要么它或它的底层类型(underlying type)在约束的类型列表中。

下面是一个泛型函数min的声明与约束定义:

func min[T Ordered](x, y T) T ...

type Ordered interface {
    type int, int8, int16, ..., uint, uint8, uint16, ..., float32, float64, string
}

函数min的实现如下:

func min[T Ordered](x, y T) T {
    if x < y {
        return x
    }
    return y
}
  • x和y的类型都是T,T类型要满足约束Ordered;
  • x < y是合法的,因为在Ordered的类型列表中的每个类型都支持"<"比较。

但不同类型参数代表的却是不同类型:

func invalid[Tx, Ty Ordered](x Tx, y Ty) Tx {
    ...
    if x < y { // 不合法
        ...
    }
}
  • x的类型是Tx,y的类型是Ty;
  • Tx和Ty是不同类型;
  • "<"需要两个操作数拥有相同的类型。

类型列表应用的典型示例

  • 将[]byte和string的操作整合在一起

我们知道目前标准库中有一个bytes包和一个strings包,这两个包一个用于处理[]byte,一个则用于处理string。但使用过这两个包的gopher会发现,这两个包中大部分函数和方法是一样的,甚至处理逻辑都是一样的。有了泛型后,我们可以将对两种类型的大部分操作整合在一起,以Index函数为例:

type Bytes interface {
   type []byte, string
}

// Index returns the index of the first instance of sep
// in s, or -1 if sep is not present in s.
func Index[bytes Bytes](s, sep bytes) int
  • 类型参数(type parameter)之间的关系
type Pointer[T any] interface {
    type *T
}

func f[T any, PT Pointer[T]](x T)

或

func foo[T any, PT interface{type *T}](x T)

上面是基于类型列表表述“一个类型的指针类型”约束的方案。PT的实参的类型必须是T的实参类型的指针类型。

下面这几个函数和接口很大可能会加入到标准库:

func BasicSort[Elem Ordered](list []Elem)

func Sort[Elem Lesser[Elem]](list []Elem)

type Lesser[Elem any] interface {
    Less(Elem) Elem
}

小结

关于泛型声明:

  • 类型参数列表和普通参数列表相似,只是使用"[ ]"括起;
  • 函数和类型都可以拥有类型参数列表;
  • 使用interface表达对类型参数的约束。

关于泛型使用:

  • 泛型函数和类型在使用之前必须先“具化(instantiated)”;
  • 类型自动推导可实现函数隐式具化;
  • 如果类型实参满足约束,那么具化才会合法。

截至2020.10月份的泛型设计草案版本,我们对以下特性设计的满意度为:

img{512x368}

三. 结束语

“能力越大,责任越大”

  • 类型参数(泛型)是Go工具集中的新成员;
  • 它与语言的其他部分正交;
  • 其正交性也打开了编码风格的一个新维度。

泛型引入了抽象,无用的抽象带来复杂性。请三思而后行!

示例1

func ReadAll(r io.Reader) ([]byte, error)

对比:

func ReadAll[reader io.Reader](r reader) ([]byte, error)

=> 引入泛型的版本并未解决任何实际问题(还带来了复杂难以理解的抽象)

示例2

// Drain drains any elements remaining on the channel.
func Drain[T any](c <-chan T)

// Merge merges two channels of some element type into
// a single channel.
func Merge[T any](c1, c2 <-chan T) <-chan T

=> 类型参数让以往无法实现的逻辑成为现实。

何时使用泛型

  • 增强静态类型安全性
  • 更高效的内存使用
  • (显著的)更好的性能

泛型是带有类型检查的宏(macro)。使用宏之前请三思!

接下来的工作

Go核心团队正在着手做出一个完整的泛型实现,以便我们解决所有未解决的问题。我们继续欢迎大家的反馈!

如何抢先体验泛型:

  • playground: https://go2goplay.golang.org/
  • go2go命令工具:git checkout dev.go2go

注:2020.11.21日,Go开发团队技术负责人Russ Cox在golang-dev上的mail确认了Go泛型(type parameter)将在Go 1.18版本落地,即2022.2月份

img{512x368}

关注公众号“iamtonybai”,fgg获取论文“Featherweight Go”下载链接;发送gophercon2020获取GopherCon 2020大会技术ppt资料。

img{512x368}


“Gopher部落”知识星球开球了!高品质首发Go技术文章,“三天”首发阅读权,每年两期Go语言发展现状分析,每天提前1小时阅读到新鲜的Gopher日报,网课、技术专栏、图书内容前瞻,六小时内必答保证等满足你关于Go语言生态的所有需求!星球首开,福利自然是少不了的!2020年年底之前,8.8折(很吉利吧^_^)加入星球,下方图片扫起来吧!

Go技术专栏“改善Go语⾔编程质量的50个有效实践”正在慕课网火热热销中!本专栏主要满足广大gopher关于Go语言进阶的需求,围绕如何写出地道且高质量Go代码给出50条有效实践建议,上线后收到一致好评!欢迎大家订阅!

我的网课“Kubernetes实战:高可用集群搭建、配置、运维与应用”在慕课网热卖中,欢迎小伙伴们订阅学习!

img{512x368}

我爱发短信:企业级短信平台定制开发专家 https://51smspush.com/
smspush : 可部署在企业内部的定制化短信平台,三网覆盖,不惧大并发接入,可定制扩展; 短信内容你来定,不再受约束, 接口丰富,支持长短信,签名可选。2020年4月8日,中国三大电信运营商联合发布《5G消息白皮书》,51短信平台也会全新升级到“51商用消息平台”,全面支持5G RCS消息。

著名云主机服务厂商DigitalOcean发布最新的主机计划,入门级Droplet配置升级为:1 core CPU、1G内存、25G高速SSD,价格5$/月。有使用DigitalOcean需求的朋友,可以打开这个链接地址:https://m.do.co/c/bff6eed92687 开启你的DO主机之路。

Gopher Daily(Gopher每日新闻)归档仓库 - https://github.com/bigwhite/gopherdaily

我的联系方式:

  • 微博:https://weibo.com/bigwhite20xx
  • 微信公众号:iamtonybai
  • 博客:tonybai.com
  • github: https://github.com/bigwhite

微信赞赏:
img{512x368}

商务合作方式:撰稿、出书、培训、在线课程、合伙创业、咨询、广告合作。

Go语言很无聊…其实它妙不可言![译]

img{512x368}

无聊是一种很奇妙的状态,它可以稀释掉人类的一切情感。- 《古董局中局》马伯庸

GopherCon 2020技术大会上(线上虚拟大会),Jon Bodner为全球gopher们做了主题为“Go Is Boring”的精彩演讲(关注公众号iamtonybai,发送gophercon2020即可得到GopherCon 2020技术大会幻灯片资料)。

img{512x368}

其实早在2020年6月,Jon Bodner就发表过类似主题的文章《Go is Boring…And That’s Fantastic!》。其副标题为:深入探究世界为何依赖简单,可靠且易于理解的技术。本文将在这篇文章的基础上,结合演讲内容做综合翻译与整理,为大家呈现Jon Bodner这个资深程序员对Go语言哲学的理解。

1. 大多编程语言都在堆砌新功能特性

我从事专业软件工程师已有将近23年的时间,而我编写程序的时间也已有38年了。在这个过程中,我使用过很多编程语言。我喜欢编程语言,并且了解它们的新功能特性以及与之前的语言相比所进行的改动。

img{512x368}

如果看一下过去十年的编程语言,您会发现很多变化。C++,Java,Python和JavaScript增加了许多新功能,而一些新编程语言,诸如Rust和Swift等自诞生以来也发生了显著的变化。这一切都非常令人兴奋,但同时也会让你产生一种感觉:有时候,您永远无法跟上这些语言的所有想法。

img{512x368}

图:C到C++,再到更复杂的C++

img{512x368}

图:Java到Java2,再到更复杂的Java3?

JavaScript、Python、Rust、Swift、… …

2. Go没有这么多功能特性

接下来轮到Go了!考量Go的最好方法是思考它没有的功能特性:

img{512x368}

  • Go没有虚拟机或基于LLVM的编译器;
  • Go没有异常(exception);
  • Go没有用户定义的实现继承;
  • Go不支持重载函数、方法或运算符;
  • Go没有不变量;
  • Go没有枚举;
  • Go没有泛型
  • 自2012年Go 1.0发布以来,Go并未添加任何主要功能特性。

Go令人兴奋的一件事是通过goroutine,channel和select原生支持并发。但是,它基于CSP的思想,即Communicating Sequential Processes, 要知道,这可是一个早在1978年就被提出的思想。

这听起来不像是21世纪的编程语言,对吗?

然而,根据Stack Overflow的说法,Go是第三名程序员最想要学习的语言,而且(也许并非巧合)也是第三名最高薪的语言。硅谷的每个创业公司都在使用Go来构建其基础架构。Go语言编写了Docker,Kubernetes,etcd,Terraform,Vault,Consul,Traefik和许多其他前沿项目。那么问题来了?为什么每个人都对这种无聊的语言感兴趣呢?

3. 为什么每个人都对这种无聊的语言感兴趣呢?

在回答这个问题之前,让我们先退一步。

img{512x368}

这是希腊Argolis的Arkadiko桥,它是世界上最古老的桥梁,至今已有3000多年的历史。令人惊讶的是,它仍在使用中。

现在,我们为什么要关心一座古老的桥呢?这是因为软件开发有一个普遍的、但软件工程师们却不喜欢过多谈论的真理:

我们真的不擅长编写软件

我指的不仅仅是办公室里的那个人,你的经理在紧要关头派他去减少bug的数量。我指的是每个人–我,你,还有你能想到的所有著名的开发者。

但那些设计和建造桥梁的人,他们很擅长建桥。桥梁能按时、按预算建成,并能持续服务几十、几百、甚至几千年。造桥,如果你仔细想想,还真有点厉害。而桥梁是这样一种常见的现象,它们也是非常无聊的。当一座桥正常工作的时候,没有人惊奇,而当软件正常工作的时候,大家都有点惊奇。

不幸的是,这个世界非常依赖软件。它对软件的依赖甚至可能比对桥梁的依赖更甚。所以,我们必须以比造桥更快的速度更好地编写软件。

4. 这些年我们对编写软件的了解

在过去的60年中,我们在编写程序方面已经学到了一些东西,其中有很多普遍的共识:

img{512x368}

  • 早发现问题比晚发现问题要好。
  • 人们在管理程序的内存方面很糟糕。
  • 代码评审有助于发现bug。
  • 在任何一个超过一个人的项目中,沟通成本占主导地位。

5. 硬件也不能拯救我们

我们可以把这几件我们知道的事情和另一个已经确定下来的事实结合起来:电脑的速度不再快了。至少不像以前那样了。在20世纪80年代和90年代,CPU每1-2年就会快一倍。但现在情况变了。

img{512x368}

当你看单核性能时,2019年最快的酷睿i9的速度不到2011年最快的酷睿i7的两倍。我们没有变得更快,而是给CPU增加了更多的核心。当你看多核性能时,它更好一些,略微快了2倍多。

限制我们的不仅仅是CPU性能。Forrest Smith写了一篇关于RAM和RAM访问模式对性能影响的精彩博文。其要点如下:

  • RAM比CPU要慢得多,而且差距并没有得到改善,尽管CPU的速度并没有变快多少。

img{512x368}

  • RAM可能是随机访问,但如果你真的这样使用,它的速度很慢。在现代英特尔CPU上,如果数据是顺序的,你可以每秒从RAM中读取40千兆字节左右。如果你进行随机读取,每秒不到半GB。

img{512x368}

  • 有很多指针的代码特别慢。引用Forrest的话。“按顺序将指针后面的值相加的速度低于1GB/秒。随机访问,两次错过缓存,运行速度只有0.1 GB/s。指针追逐的速度要慢10到20倍”。

img{512x368}

6. 无聊带来新的惊喜,我们再来看看Go

鉴于我们知道的这些关于如何构建软件的几个宝贵的东西和我们现有的硬件状况,我们再来重新审视一下Go语言。

1) Go和软件

  • 尽早发现问题

Go语言可能缺乏功能特性,但它却有一套很棒的工具。Go的编译器速度很快,这种快速的编译速度被Go团队认为是一个特点。它可以让你快速查看你的代码是否能编译,如果不能,它可以让你看到问题所在。测试被内置在标准库中,以鼓励开发者测试他们的代码并发现问题。基准测试(benchmark)剖析(profiling)和竞态检查(-race)也是开箱即用的。很少有语言能提供这些工具,它们能让你更容易快速地发现问题。

  • 内存管理

众所周知,Go有一个垃圾收集器。你不用担心跟踪内存,这是一件很奇妙的事情。在编译语言中,垃圾回收是很罕见的。Rust的borrow checker是获得高性能和内存管理的一个迷人的方法,但它实际上把开发者变成了垃圾收集器,这可能很难正确使用;如果你犯了错误,忘记将一些引用声明为弱引用,Swift的ARC仍然会泄漏内存。现在,Go的GC的性能不如这些半自动系统,有些情况下,你需要额外的速度,但在大多数情况下,它肯定是足够的。

  • 代码评审

如果代码评审做得好,代码评审就很重要。为了进行有效的代码评审,你需要确保评审人员专注于正确的事情。低质量的代码评审会把时间花在格式化等事情上。Go在这里提供了很大帮助,因为在评审Go代码时没有有关代码格式的争论,因为所有的Go代码都是按照go fmt的标准代码格式进行格式化。

而代码评审是一个双向的过程。如果你想评审的效果好,你需要确保其他人能够理解你的代码。Go程序应该是简单的,使用一些很好理解的结构,这些结构自语言发布以来就没有改变过。因为没有异常(exception),没有面向方面的编程(AOP),没有继承和方法重写(override),也没有重载(overloading),所以很清楚什么代码在调用什么,在哪里返回值。如果你在Go中减少包级变量的使用,那就很容易看到数据到底是如何被修改的。由于Go的变化很小,你可以避免熔岩流反模式,你可以根据代码中使用的语法特性被引入到Go中的时间点来判断它到底有多老。

  • 沟通成本

Go是如何帮助解决这个问题的呢?我们已经讨论过Go的简单性、稳定性和标准格式化如何让你更容易地传达你的代码正在做什么。虽然这只是其中的一部分,但还有其他的东西。Go的隐式接口帮助团队编写解耦的代码。它们由调用代码定义,以准确描述需要什么功能,这就明确了你的代码在做什么。

2) Go和硬件

让Go成为编译语言的决定得到了回报。当CPU每天都在变快时,在虚拟机(译注:这里所谓的虚拟机是指动态语言的解释器或像jvm之类的字节码运行程序)中运行的解释语言似乎是个好主意。如果你的程序不够快,只要再等一年就可以了。但现在这已经行不通了。编译成原生代码比最新的虚拟机技巧少了很多乐趣,但它能带来很大的性能优势。

让我们用The Benchmark Game的微基准来比较Go与一些在虚拟机中运行的语言的性能。首先我们来看看Python和Ruby与Go的比较。任何小于100%的百分比都意味着比Go快,大于100%意味着慢:

img{512x368}

这里有很多红色(意味着比Go慢的测试)。有一个基准测试是Python更快 (奇怪的是,它不仅是Go的两倍,而且在这个测试中比其他所有语言都快),而Ruby则没有一个测试比Go快。除了那一个情况,这两种语言产生的代码都比Go慢了17%到60多倍。

现在让我们再看看Java和JavaScript与Go的比较:

img{512x368}

这两门语言与Go的性能更为接近。JavaScript在一个基准上比Go快,在其他基准上比Go慢,但JavaScript最坏的情况是比Go慢了三倍左右。

Java和Go的性能相当接近。Java在四种情况下比Go快,在两种情况下差不多,在四种情况下比Go慢。Go做的最差的情况是比Java慢三倍左右,Go做的最好的情况是比Java快50%左右。

我们看到的是,唯一能跟上Go的虚拟机是Java的。Hotspot是令人惊异的技术,但你需要一个世界上最好的工程软件,才能与一个优先考虑编译速度而非优化的编译器达到平衡,这说明了一些问题。而且你要为这种惊人的技术付出代价。Java应用程序的内存使用量要比Go应用程序大出很多很多倍。

还有第二个优势。垃圾收集器管理的垃圾都是不使用的指针。与隐藏指针的语言不同,Go给了你控制权。它让你避开指针,并以一种允许快速访问RAM的方式布局你的数据结构。这也让Go可以使用更简单的垃圾收集器,因为Go程序只是简单地制造更少的垃圾。枯燥无味的工作就是少了。

而我们都知道,CPU正在用更多的内核来弥补速度提升的不足。所以,使用一种能够利用这一点的语言是很好的。这就是Go内置并发支持的目的。有了对并发的语言级支持和一个在多个线程中调度goroutine的运行时库,意味着当你有多个CPU核时,这些线程可以被自然地映射到这些核上。

7. 我不想要我没有得到的那些功能特性

我们已经看到,Go专注于我们所知道的使创建软件更容易、更适合现代计算机的内存和CPU架构的功能和工具。但是其他语言有而Go没有的功能特性呢?也许Go的开发者错过了,那些Go没有的特性能帮助开发者写出了更少错误、更容易维护的代码?好吧,研究人员的研究结果告诉我们,事实并非如此。

2017年一篇名为《Github中编程语言与代码质量的大规模研究》的论文,该论文研究了17种语言的729个项目、8000万行代码、2.9万名作者、150万次提交,并试图回答这个问题:编程语言对软件质量的影响是什么?他们的答案是,差别不大。

“值得注意的是,这些因语言设计而产生的微弱影响,绝大多数是由项目规模、团队规模和提交规模等过程因素主导的。”

另一组研究人员对这些数据进行了第二次研究,并在2019年做了一项名为“关于编程语言对代码质量的影响”的论文。他们的发现更令人惊讶:

“根据手头的数据,不仅无法建立编程语言和代码质量之间的因果关系,甚至它们之间的相关性也被证明是值得怀疑的。”

如果编程语言的选择并不重要,那为什么要选择Go?这些研究表明的是,流程很重要。工具、测试、性能和长期维护的便利性比时髦的功能特性更重要。如果使用得当,Go内置的工具支持更好的流程,同时提供久经考验的功能特性。

这并不是说新功能不好。在过去的几个世纪和几千年里,桥梁建设技术当然在不断进步。但是,你想成为第一个走过一座用全新的理念和未经测试的技术建造的桥梁吗?你会想等一下,让人们测试一下再采用。

软件也是如此。如果我们要建立像桥梁一样可靠的软件基础架构,我们就需要使用像物理基础架构一样经过充分测试和理解的软件技术。这就是为什么Go主要使用20世纪70年代设计的功能特性,我们知道它们是有效的。

Go很无聊….其实它妙不可言。让我们都来用它来构建明天的精彩应用吧。

img{512x368}


“Gopher部落”知识星球开球了!高品质首发Go技术文章,“三天”首发阅读权,每年两期Go语言发展现状分析,每天提前1小时阅读到新鲜的Gopher日报,网课、技术专栏、图书内容前瞻,六小时内必答保证等满足你关于Go语言生态的所有需求!星球首开,福利自然是少不了的!2020年年底之前,8.8折(很吉利吧^_^)加入星球,下方图片扫起来吧!

Go技术专栏“改善Go语⾔编程质量的50个有效实践”正在慕课网火热热销中!本专栏主要满足广大gopher关于Go语言进阶的需求,围绕如何写出地道且高质量Go代码给出50条有效实践建议,上线后收到一致好评!欢迎大家订阅!

我的网课“Kubernetes实战:高可用集群搭建、配置、运维与应用”在慕课网热卖中,欢迎小伙伴们订阅学习!

img{512x368}

我爱发短信:企业级短信平台定制开发专家 https://51smspush.com/。smspush : 可部署在企业内部的定制化短信平台,三网覆盖,不惧大并发接入,可定制扩展; 短信内容你来定,不再受约束, 接口丰富,支持长短信,签名可选。2020年4月8日,中国三大电信运营商联合发布《5G消息白皮书》,51短信平台也会全新升级到“51商用消息平台”,全面支持5G RCS消息。

著名云主机服务厂商DigitalOcean发布最新的主机计划,入门级Droplet配置升级为:1 core CPU、1G内存、25G高速SSD,价格5$/月。有使用DigitalOcean需求的朋友,可以打开这个链接地址:https://m.do.co/c/bff6eed92687 开启你的DO主机之路。

Gopher Daily(Gopher每日新闻)归档仓库 – https://github.com/bigwhite/gopherdaily

我的联系方式:

  • 微博:https://weibo.com/bigwhite20xx
  • 微信公众号:iamtonybai
  • 博客:tonybai.com
  • github: https://github.com/bigwhite
  • “Gopher部落”知识星球:https://public.zsxq.com/groups/51284458844544

微信赞赏:
img{512x368}

商务合作方式:撰稿、出书、培训、在线课程、合伙创业、咨询、广告合作。

如发现本站页面被黑,比如:挂载广告、挖矿等恶意代码,请朋友们及时联系我。十分感谢! 图片广告位1 图片广告位2 图片广告位3 商务合作请联系bigwhite.cn AT aliyun.com

欢迎使用邮件订阅我的博客

输入邮箱订阅本站,只要有新文章发布,就会第一时间发送邮件通知你哦!

这里是 Tony Bai的个人Blog,欢迎访问、订阅和留言! 订阅Feed请点击上面图片

如果您觉得这里的文章对您有帮助,请扫描上方二维码进行捐赠 ,加油后的Tony Bai将会为您呈现更多精彩的文章,谢谢!

如果您希望通过微信捐赠,请用微信客户端扫描下方赞赏码:

如果您希望通过比特币或以太币捐赠,可以扫描下方二维码:

比特币:

以太币:

如果您喜欢通过微信浏览本站内容,可以扫描下方二维码,订阅本站官方微信订阅号“iamtonybai”;点击二维码,可直达本人官方微博主页^_^:
本站Powered by Digital Ocean VPS。
选择Digital Ocean VPS主机,即可获得10美元现金充值,可 免费使用两个月哟! 著名主机提供商Linode 10$优惠码:linode10,在 这里注册即可免费获 得。阿里云推荐码: 1WFZ0V立享9折!


View Tony Bai's profile on LinkedIn

文章

评论

  • 正在加载...

分类

标签

归档



View My Stats