标签 编程语言 下的文章

Go语言的遗产

本文是gohugo作者Steve Francia在意大利佛罗伦萨举办的GoLab上分享的闭幕演讲讲稿的文字版,该演讲的主题为”Go的遗产”。该演讲讨论了Go语言继承的遗产,以及它是如何尊重这些遗产的,并在最后总结了Go希望留给后来者的遗产。

img{512x368}

演讲胶片

我们有责任保留好留给我们的遗产,并留下值得我们子孙后代继承的遗产 – 克里斯汀·格雷格(Christine Gregoire)

1. Go语言之前

img{512x368}

1950年

在1950年代后期,人们对每台新计算机如何产生自己独特的语言而感到不安。当时,编程语言是由硬件制造商提供的,并且因型号而异。跨计算机且保持一致的第一门编程语言是Fortran,但这仍然仅适用于其制造商IBM(生产的计算机)。而后,人们成立了一个委员会,该委员会的使命就是设计第一种真正通用的、独立于机器的编程语言。

img{512x368}
图:编程语言历史Babel塔,CACM封面,1961年1月

1960年

1960年1月,有13位计算机科学家在巴黎举行了一次空前的会议,旨在(设计)开发出这样一种语言。美国派出了6位代表,欧洲派出了7位代表。

会议上无休止、令人振奋的讨论也让科学家们筋疲力尽。当一个人的好主意与他人的坏主意一起被抛弃时,一个人就会变得更加恼火。然而,在整个会议期间内,大家都没有懈怠,持续地努力投入着。

最终这13名科学家的思想碰撞产生了良好的化学反应 – Alan Perlis

Algol

img{512x368}

这是一种远远超越其时代的语言,它不仅是对其前辈的一种改进,而且对其所有后继者也产生了重大影响。- Tony Hoare关于编程语言设计的提示– 1973年

img{512x368}

之后,原本一脉相承的语言出现了分裂:

  • Pascal这一分支在欧洲蓬勃发展,有许多继任者,包括ModulaOberon

img{512x368}

  • C语言在美国激增,激发和促进了C++、C#、Java以及JavaScript、Python、Perl、PHP和许多其他语言的诞生和发展。

img{512x368}

  • 到2007年,存在的数十种编程语言都可以追溯到其共同祖先:Algol。

img{512x368}

1964年

我们的并发故事始于Doug McIlroy,他在1964年提出了一些新的想法,这些想法最终演化为Unix Pipes。

当有必要以另一种方式处理数据时,我们应该有一些耦合程序的方法,例如像将花园软管拧入另一部分那样。这也是IO的方式。- Doug McIlroy

背后故事

在1970年至1972年的一段时间内,我不时说:“如何做这样的事情?”,然后我提出了另一个建议,另一个建议,另一个建议。有一天,我想出了一种shell语法用于支持管道使用,Ken说:“我要去实现它!”他厌倦了听到所有这些内容……[并且]他说,“要去实现它”。他没有完全按照我为管道系统调用所建议的去做。他发明了一种更好一点的东西,终于又改变了今天的样子。他在一夜之间将管道符放入了Unix(并且他做到了)……。

麦克罗伊(McIlroy)引述:布赖恩(Brian)的墙上还挂着一张纸,在那张纸上我谈到了像花园软管那样将流(stream)拧在一起。所以这个想法在我脑海中徘徊了很长时间。

同时,在Thompson和Ritchie在黑板上,草拟了一个文件系统,我正在草拟如何在黑板上进行数据处理,方法是将一系列过程串联在一起,并寻找一种将过程连接在一起的前缀表示法语言。之所以失败,是因为很容易说出“cat into grep into……”或“who into cat into grep”等等。这么说很容易,而且从一开始就很清楚,这就是您想说的。但是这些命令具有所有这些附带的参数。它们不仅具有输入和输出参数,还具有选项,并且在语法上还不清楚如何将这些选项插入以前缀表示法编写的链中,比如:cat(grep(who …))。在句法上很多人不知道如何做。所以我把这些非常漂亮的程序写在黑板上,用的语言不够强大,无法应付现实。因此,我们实际上并未这样做。

1978年

到1978年,在对多处理器进行编程的背景下,有许多提议的方法被用于通信和同步。共享内存是最常见的通信机制。

托尼·霍尔(Tony Hoare)发表了一篇论文,该论文改变了一切。它比时代提前了几十年。他称他的论文为:”通讯顺序进程,communicating sequential processes”,就是大家熟知的CSP。

img{512x368}

  • 进程(Processes):执行单元
  • 顺序(Sequential):每个进程都作为一个普通的单线程程序运行
  • 通讯(Communicating):进程如何协调
  • 没有内存共享
  • 没有线程,没有互斥体

Hoare的论文提出了一种语言,每个进程(或者作为一个普通的单线程程序)按顺序执行,通过无缓冲通道(unbuffered channel)相互通信。Hoare的通信进程比典型的Unix Shell管道更通用,因为它们可以以任意模式连接。

img{512x368}

三个语言分支因Hoare的CSP论文而诞生:ErlangOccamNewsqueak

  • 1983年诞生的Occam最接近CSP论文(由Hoare推荐)
  • Erlang在80年代后期专注于CSP的功能方面,并使用mailbox在进程之间进行通信
  • Rob Pike(Newsqueak之父)追逐了并发白鲸(the concurrency white whale)长达20年

img{512x368}

Go是第一种可以同时拥有欧洲和美国语言设计分支传统的语言。实际上,它已经统一了这三个分支

2016年,黑客新闻评论(Hacker News)上的一则帖子称Go语言时停留在70年代的一种语言,这引起了一些对Go的批评……

2. 对过去伟大思想的复兴

img{512x368}

编程语言发展的四波浪潮

img{512x368}

  • 第一波浪潮:语言扩张 – 巴别塔

特征:多样化。很久以前,语言是多种多样的,并在在思想、方法和意见等方面体现出多样性。

  • 第二波浪潮:语言的标准化

特征:快速、复杂且对开发不友好。语言的标准化发生了数十年。到2000年代,事情开始停滞。他们融合为两个阵营:Java/JVM和C/CLR。C++、Java、C#都非常相似。

  • 第三波浪潮:脚本语言

特征:慢、不安全但对开发友好。脚本语言作为对上述语言的复杂性和痛苦的回应而应运而生。它们开发快速而松散,对开发人员友好,但缺乏性能和安全性。

  • 第四波浪潮:恢复

特征:快速、安全、对开发人员友好

Go是对这些语言的复杂性和痛苦的一种反应,也是对脚本语言快速开发和松散本质的反应。

Go恢复了早期语言的简单性和灵活性,增加了现代语言的安全性和开发友好性。Go以一种非常真实的方式复兴了许多伟大的想法,这些想法终于准备就绪。

Go给人的感觉就像是来自60年代,70年代,80年代,90年代,00s,10年代的语言……Steve Francia 2019

Go感觉像这样是因为它由过去60年来的许多伟大构想组成。

img{512x368}

现在,我想谈谈Go中的3种特定功能(简单、并发和Go的OO)以及这些思想起源的4种语言(Oberon、Newsqueak、Simula和Smalltalk)。在Go恢复它们之前,许多思想被遗忘了。

1988年

简单易读的结构和语法: Oberon&C

Niklaus Wirth负责Algol-W,Pascal,Modula。现在是1988年,他的最新语言是Oberon

Oberon的程序结构,以“hello, world”例子为例:

MODULE hello;

IMPORT Out;

BEGIN
    Out.String("Hello, World"); Out.Ln
END hello.

Oberon围绕着爱因斯坦(Albert Einstein)的座右铭设计:“使事情尽可能简单,但不要过于简单。”

程序结构非常简单。

下面是Go的”hello world”程序结构:

package main

import "fmt"

func main(){
    fmt.Println"hello world")
}

这个例子看起来应该很熟悉,它直接采用Oberon的结构。

我们再来看看Oberon的声明结构

CONST n = 42;
TYPE mystring = ARRAY 32 OF CHAR;
VAR s: mystring;

PROCEDURE squared(x:INTEGER):INTEGER;
BEGIN
    RETURN x * x
END squared;
VAR b,c: INTEGER = 1,2;

再来看看Go的声明结构:

const n = 42

type mystring string

var s mystring

func squared(x int) int {
    return x*x
}

var b, c int = 1, 2

在Go和Oberon中,声明都是从左到右(名称,类型,可选值),这恰与C相反,在C语言中,类型放在前面。

很多人看到Go后会问为什么我们要翻转C语法,他们错误地认为Go的声明结构来自C语言。它不是,它来自Oberon。

Go使用了Oberon形态,但却用C的token:

  • {}代替BEGIN END
  • ++,– 代替(内置)的INC和DEC
  • != 代替#
  • %代替MOD
  • || 代替OR
  • []代替ARRAY
  • 结构体代替RECORD
  • *代替POINTER TO

虽然结构来自Oberon,但Go使用的token却来自C。

  • 这里没有太多,这就是重点。语法和结构都很简单。
  • 没有继承,没有层次。没有复杂的作用域(scope)系统。
  • 它们尽可能简单,但并不过于简单。

您可以看到Go如何采用Oberon的简单结构,但是删除了笨拙的语法,并采用C语言的更加优雅和熟悉的语法替换了它们。

这样做的结果是一种非常易读的语言诞生了。

1989年

并发与Newsqueak

罗伯·派克(Rob Pike),他于1989年在贝尔实验室工作。他在这里设计了Newsqueak

  • Newsqueak是一门用于研究和探索的编程语言
  • 它致力于在Sequeak基础上添加实用的、切实可行的并发(concurrency)支持
  • Newsqueak语法上类似C
  • 像CSP一样,Newsqueak使用Channel作为Process的集合点

Rob Pike的Newsqueak在语法上看起来像C,但对并发支持的更好。Squeak用于设计菜单和滚动条之类的设备,Newsqueak解决了同样的问题,但涉及范围更广:Newsqueak用于编写整个应用程序,尤其是窗口系统。

Newsqueak-Prime Sieve pt.1

译注:Rob Pike拿手的素数筛例子

counter := prog(end: int, c: chan of int) {
    i: int;
    for(i = 2; i<end; i++) c<-=i;
};

filter := prog(prime: int, listen, send: chan of int) {
    i: int;
    for(;;)
        if((i=<-listen)%prime)
            send<-=i;
};

Newsqueak-Prime Sieve pt.2

sieve := prog(c: chan of int) {
    for(;;) {
        prime := <-c;
        print(prime, “ “);
        newc := mk(chan of int);
        begin filter(prime, c, newc);
        c = newc;
    }
};

count := mk(chan of int);

begin counter(10000, count);
sieve(count);

与CSP和Squeak不同,Newsqueak将channel视为一等公民:channel可以存储在变量中,可以作为参数传递给函数,甚至channel自身也可以通过channel发送。

另外”<-c(receive)”表达式也是第一次在这里介绍。

channel和routine

Go:

c := make(chan int)
c <- 1
x = <-c
go f(x)

vs.

Newsqueak:

c := mk(chan of int);
c <- = 1;
x = <-c;
begin f(x);

我们看到:Go的并发方法几乎与Newsqueak完全相同,channel和 goroutines的使用方式也是相同的。

select

Newsqueak还使用了看起来与Go的select语句非常相似的select。

select {
    case msg1 = <-c1:
        print(“received”, msg1, “\n”);
    case msg2 = <-c2:
        print(“received”, msg2, “\n”);
}

您可以清楚地看到Go并发的基础在25年前是如何在Newqueak中被建立起来的。Go采纳了这些”老想法”,并对其进行了改进,使其可以投入生产。

Ryan Dahl: Node.js的创建者的访谈(2017)

我喜欢Go的编程模型。使用goroutine是如此简单和有趣……如果您要构建服务器,那么我无法想象使用Go以外的任何工具。Goroutine使Go的并发变得简单。

1965年

面向对象基础(Smalltalk)

OO在C++/Java之前就已存在,在C++和Java重新定义面向对象之前。

什么是面向对象?

  • 附加到数据对象的过程(Procedure)
  • Procedure的可重用性

Procedure+数据

img{512x368}

Simula继承了Algol,并在其中添加了对象,类,继承和子类。 Simula被认为是第一种面向对象的编程语言,并且在Smalltalk和所有随后的OO语言的开发中具有重要的影响力。

Simula改变了一直以来的从Procedure的角度来看的思维方式,…他将其翻转为…面向对象的视角,即在每种类型的对象中,您都有处理它的所有方法。- Small Talk的实现者Dan Ingalls

1980

接下来是Smalltalk,其中一切都是对象,并且仅通过发送消息与对象进行通信。

img{512x368}

我确实发明了“面向对象”这个术语,但这是一个错误的选择,因为它没有强调消息发送这个更重要的思想。 – Alan Kay: 从A到Z的编程语言:Smalltalk-80 – 2010

1989年

Procedure重用

我们将讨论两个出版物:

如果系统的任何部分取决于另一部分的内部结构,那么复杂度会随着系统大小的平方而增加 – Dan Ingalls面向对象编程— 1989年

继承。 我们看到了继承带来的这种指数级复杂性

对于使用半新的OO语言进行编程的任何人,这应该看起来都很熟悉。关系线无处不在。 – SPAGHETTI CODE的诞生

论文《强类型面向对象编程的接口》中所提到的系统提供了Ada和Modula-2之类的语言中的模块接口的优点,同时保留了可表达性,使无类型的面向对象的语言(如Smalltalk)具有灵活性。

Go interfaces

type Point interface {
    X() int
    Y() int
    Move(int,int)
    Point Equal(Point) bool
}

Go团队在实现interface时并不知道到该论文的存在。由于这两种方法的明显相似性,后来与他们share了该论文。

Go采取了非常相似的方法,但是对上面论文中想法进行了改进,因为Go接口是隐式的,这使Go应用程序解耦并提供了极大的灵活性。

当您尝试分解一个复杂的问题时,您想要尝试将其分解为尽可能少的部分,并且希望它们尽可能独立。 – Dan Ingalls 面向对象编程— 1989

Go的interface和method采用尽可能独立的方式。只要添加正确的方法,任何类型都可以满足任何接口。可以在满足该接口的类型之前或之后定义一个接口。事实证明,这种方式是有效的,而且效果很好。

Go的OO

  • method提供任何类型的消息发送机制
  • 接口通过动态调度多态性提供可重用性

Go提供了像Smalltalk定义的那种面向对象编程,只是更加贴近实际,即使它不包含类,对象或继承。

  • Smalltalk: OO是关于消息发送
  • Go的interface允许方法像Smalltalk的消息一样自由使用,但是是在一种有类型的语言中使用

3. Go的设计哲学

img{512x368}

2007年

在一次耗时45分钟的C++构建过程中……

罗勃·派克:把时钟拨回到2007年9月,当时我正在对一个巨大的谷歌C++程序做一些微小但重要的优化工作,你们都与这个庞大的程序做过交互。我得这个编译过程在我们的巨大的分布式编译集群上跑了约45分钟。我收到一条消息:为C++标准委员会服务的几位Google员工将进行一个演讲,他们将告诉我们C++ 11的新功能。

在一个小时的演讲中,我们听到了有关计划中的35个新功能的消息。……这时我问自己一个问题:”C++委员会真的相信C++的不足之处在于它没有足够的功能吗?” 当然……,简化语言而不是为其添加更多功能将是一个更大的成就。Rob Pike和他的办公室同事(Robert Griesemer、Ken Thompson)回到了办公桌前。这真的让他们开始思考…

现代实用的编程语言应该是什么样?到45分钟构建完成时,他们已经有了一个充满想法的白板。

语言设计的进化过程

我们从头开始构建,仅从C中借鉴了一些小东西,例如运算符和大括号,以及一些通用关键字。当然,我们还借鉴了我们所知道的其他语言的想法。- 罗伯·派克(Rob Pike)

少即是(指数级的)多 – 2012年,在谈到Go的灵感时 Rob Pike

Go的众多祖先和对Go有影响的语言:

img{512x368}

我要说的是,没有哪位语言设计师比这三位语言设计师(Rob Pike, Robert Griesemer, Ken Thompson) 具有更广泛或更深的语言设计专业知识。他们对以前发生的事情有很丰富的了解,他们知道该采摘什么。他们还具有事后观察的优势(后发优势)。这是修复他们认为可以做得更好的事情的机会。

进化不是革命

  • 原则1:大多数思想都来自先前的思想

大多数思想根本不是新事物

进化不是革命:新语言应该巩固而不是发明新特性

等待良好的设计

  • 原则2:No是暂时的,Yes是永远的。

在Go的整个历史中,有很多这样的实例。通常的想法是,在设计语言时,不会出现“撤消(undo)”的情况。如果您今天说“No”,那么您明天总是可以说“Yes”,但是如果今天您说“Yes”,那么您将在很长一段时间或永远被它“困”住…。

如有疑问,请将其排除在外。- Joshua Bloch:关于设计的对话– 2002

共识驱动的设计

  • 原则3: 应该使一切都尽可能简单,但不要过于简单。-爱因斯坦

当我们三个人开始时,这纯粹是研究。…我们从一个想法开始,即我们三个人都必须针对该语言的每个特性进行讨论,因此,无论出于何种原因,都不会在该语言中放入多余的垃圾。 – 肯·汤普森(Ken Thompson)访谈– 2011年,肯从Bell Labs学习了这种做法

有两种构建软件设计的方法。一种方法是使其变得如此简单,以至于显然没有缺陷。另一种方法是使其变得如此复杂,以至于没有明显的缺陷。 – 托尼·霍尔(Tony Hoare)皇帝的旧衣服-1981年,Go采取了第一种方法,而大多数其他语言都采用第二种方法。

快速迭代期待并实现大规模改变

  • 最后一个原则是快速迭代的原则。

当您处于语言的设计阶段时,您将需要进行频繁且有时是巨大的更改。朝着这个期望前进,并围绕它建立您的流程。

4. 今天的Go

img{512x368}

我们来看Go如今是如何演变的。

2019年

Go今天是如何继续进行演化的。

上面的4条原则在该语言的初期,在发行稳定版之前和被采用之前都非常有效。

但我们现在的处境非常不同。我们不再能够将所有贡献者都放在白板上,甚至不能放在如此大的房间中(译注:Go目前的contributor数量庞大)。

现在,我想与大家分享Go项目今天如何进行更改的。

我们的原则是“等待良好的设计”,这似乎意味着这是一种消极的活动,但这与事实相去甚远。真正的意思是,除非我们非常有信心采用正确的方法,否则我们不会接受更改。

这意味着所有问题的默认答案是“否”。“是”的成本非常高,因此需要一个压倒性的理由。

对一件事说“Yes”意味着对其他一切都说“No”。

软件复杂性的主要原因是供应商不加批判地采用了用户想要的几乎所有功能。人们似乎将复杂误解为先进。

不可理解的应该引起怀疑而不是钦佩。- Niklaus Wirth, 1995年

我们对Go进行了长期展望。为下一个十年或两个或更多个而设计。大多数项目的运行时间要短得多,因此通常会接受第一个可通过的解决方案。

随着时间的流逝,经过长时间这种训练的人们已经意识到:如果一个好主意会被接受,或者反之,不好的主意会被拒绝。

由于我们的长期观点,在为Go项目做出贡献时人们挣扎并不罕见。当他们的想法不能被接受时,许多人感到被亲自拒绝。

或更糟糕的是,人们会感到自己不合格或不称职。我记得有这种感觉。

几年前,我创建了一个网站引擎Hugo,随着时间的推移,它成为Go模板的第一用户,并在此过程中发现了几个问题。尽管如此,我感到非常没有资格报告这些问题,因为我认为创建这些库的“专家”显然比我了解更多,并且我无能为力。在第一次或第二次Gophercon上,我碰巧在午餐台上站在Russ Cox旁边,我们开始交谈。他强烈鼓励我报告这些问题,并让我知道他们多么地需要反馈。

几年后,我加入了Go团队,并从这个经验中学到了很多。我观察到的一件事是,Go团队那些加入较久的核心成员有一件事比大多数其他成员都做得更好,这可能不是您的想法。Go团队的老成员已经非常习惯于听到“不”的声音。我们团队成员的提议被拒绝的比率很高,甚至高于Go团队之外的提议。我们已经了解到,每个“No”都与拥有正确的“Yes”仅一步之遥。

因为我们经常听到“No”的声音,所以我们同情别人被拒绝的感觉。

今天我要传达给您的信息是您受到重视和需要。请继续尝试。在接下来的十年或二十年或更长的时间内,您是Go演化的关键部分。

Go开发流程

Go开发流程

实验流程简化始于今年早些时候,Russ Cox谈论了我们用来对Go进行更改的流程以及它的演变方式。在演讲中,他讨论了实验的两个步骤,并简化了我们的迭代过程。

我们的过程不是为了速度而建立的,而是为了正确。我们花费大量时间进行实验和简化,然后完善自己的想法,直到它们正确为止。

你们都是Go伟大实验的一部分,并且是继续构建Go的过程的关键部分

我想与大家分享3种方法,每个人都可以为Go做出贡献。

  • 使用Go -> 识别问题 -> 您遇到的事情/体验并写下来。
  • 您有想法-> 编写建议 -> 纳入反馈
  • 您阅读提案 -> 阅读评论 > 添加您的声音

img{512x368}

Go开发过程:实验 -> 简化 -> 最终交付。通过此提炼过程,想法将准备就绪,我们将进行交付。我想对过程的这一部分及其工作原理提供更多见解。

共识驱动的设计

  • 误解:谷歌有一小群“决策者”
  • 真相:评论者之间达成共识

关于提案过程的事实

  • 事实上,大多提案提案都很小
  • 几乎所有提案的讨论最终都在参与者(评论员)之间达成了共识。
  • 提案审核委员会主要进行一些“园艺劳动”(译者:社区行为培养)

您看到这不是一件非常迷人的工作。我们评论的大多数问题都要求您澄清问题或什么也不做,让对话继续进行。我们还会考虑谁在对话中丢失,并邀请他们加入对话。

当讨论似乎已经解决(赞成或反对)时,我们将关闭其中的一小部分。

让我们看一下最近的一个建议。这只是从最近提案池中随机选取的一个。

它具有一些有趣的属性:大量参与,来自9个参与者的25条评论引用用户问题(体验)。早期该issue尚无共识(由点赞决定)。

在对该想法进行讨论和完善之后,很明显已经达成了普遍共识。

它被标记为“可能接受”,并且留下足够的时间窗口允许任何人提供我们不接受的理由。

这是一组最近审核的提案。您会注意到,他们每个人都引用了之前的评论,并根据这些评论提出了建议。

在提案审核委员会中,通常会有一个人留下评论,但代表所有出席者。Russ Cox通常志愿承担了这个角色,这就是为什么所有这些issue上面都加上他的名字的原因。在大多数情况下,此窗口不会附加注释。我们觉得这个窗口虽然很少使用,但对于建立共识的过程至关重要。

变化是缓慢发生的

这是设计使然。这是缓慢的、谨慎和有条不紊的,以确保我们最终达到想要的目标。

过去十年的主要里程碑

尽管Go的变化缓慢,但增长迅速。我想了解一下过去十年中的一些主要里程碑。

img{512x368}

  • 2009年, Go语言开源,Gopher诞生,Go脱离了Google的实验场;
  • 2010年,获得年度TIOBE语言,Bossie奖,引入append和go tour;
  • 2011年,gccgo合并到GCC中,引入gofix,YouTube在生产中采用了Go;
  • 2012年,Go 1.0发布!发布Go1兼容性承诺;在Google内部发布第一项Go生产服务

img{512x368}

  • 2013年,Packer,Docker,Hugo用Go编写;6个月发布周期;第一个Go大会举行(日本东京)
  • 2014年,Kubernetes使用Go开发;代码仓库由Mercurial→Git;第一次美国和欧洲会议;Go项目贡献者达到500名;
  • 2015年,Go编译器使用Go重写,实现自举;GC精化; Women Who Go&GoBridge born; 印度、中国第一次go大会举行;
  • 2016年,支持HTTP/2和Context;第一次拉丁&中东Go大会举行;最受喜欢的5门编程语言;第一次Go用户调查;贡献者达1000名;

img{512x368}

  • 2017年,GC小于ms级的暂停; 引入type alias;开发人员想要使用编程语言第一名第一次); 13次会议; 第一届贡献者峰会
  • 2018年,引入Go模块;来自Go团队之外的贡献者人数首次超过Go团队;19次Go会议;Go新品牌和logo发布;PR数在github排名第四; 开发人员打算学习的语言中排名第一

5. Go的遗产

img{512x368}

没有时间机器可以达到未来。未来的到来缓慢而又出乎意料。我们不知道Go或世界将会发生什么。但是我们确实知道我们想留下什么标记。

  • 我们希望Go能够留下创新的遗产

Go向主流受众带来了创新的想法,例如goroutine,channel,简单的interface。这些想法现在正在其他语言中出现,我们为这一趋势继续感到高兴。

Go fmt于2009年推出时颇有争议。现在,大多数语言都采用了类似的方法。

也许我们最有影响力的遗产将是,我们像Go一样激励人们挑战既定的规范,并在各处寻找灵感。

  • 我们希望Go留下增强信心和能力的遗产

Go使开发人员能够编写生产服务器软件而无需C和C++所需的额外专业知识,而无需现代Java的复杂性,也无需解释语言的性能成本。

Go比其他任何语言都更能使人们把他们的想法变成现实。当我第一次开始撰写Hugo时,我个人感觉到了这一点,这是Go最吸引我的地方。

其他那些也被赋予了类似的能力和信心的人,其中许多人在本次会议上谈到了Go的创造性用途,包括Florin的家庭自动化研讨会,Ron的机器人,Elias的GUI等。

  • Go改变生活。

我有幸环游世界,在任何地方遇到的男人和女人,他们通常没有CS背景或学位,但能够学习Go并用它来创办公司,获得更好的工作并改善他们和他们家人的生活。

遗产不会为人们留下任何东西。它在人们身上留下了一些东西。- 彼得·斯特普尔

我们每个人都受到过往历史的影响。我们是遗产。我们被影响,我们影响别人。


我的网课“Kubernetes实战:高可用集群搭建、配置、运维与应用”在慕课网上线了,感谢小伙伴们学习支持!

我爱发短信:企业级短信平台定制开发专家 https://tonybai.com/
smspush : 可部署在企业内部的定制化短信平台,三网覆盖,不惧大并发接入,可定制扩展; 短信内容你来定,不再受约束, 接口丰富,支持长短信,签名可选。

著名云主机服务厂商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 module

自2007年“三巨头(Robert Griesemer, Rob Pike, Ken Thompson)”提出设计和实现Go语言以来,Go语言已经发展和演化了十余年了。这十余年来,Go取得了巨大的成就,先后在2009年和2016年当选TIOBE年度最佳编程语言,并在全世界范围内拥有数量庞大的拥趸。不过和其他主流编程语言一样,Go语言也不是完美的,不能满足所有开发者的“口味”。这些年来Go在“包依赖管理”和“缺少泛型”两个方面饱受诟病,它们也是Go粉们最希望Go核心Team重点完善的两个方面。

今年(2018)年初,Go核心Team的技术leader,也是Go Team最早期成员之一的Russ Cox个人博客上连续发表了七篇文章,系统阐述了Go team解决“包依赖管理”的技术方案: vgo。vgo的主要思路包括:Semantic Import VersioningMinimal Version Selection引入Go module等。这七篇文章的发布引发了Go社区激烈地争论,尤其是MVS(最小版本选择)与目前主流的依赖版本选择方法的相悖让很多传统Go包管理工具的维护者“不满”,尤其是“准官方工具”:dep。vgo方案的提出也意味着dep项目的生命周期即将进入尾声。

5月份,Russ Cox的Proposal “cmd/go: add package version support to Go toolchain”被accepted,这周五早些时候Russ Cox将vgo的代码merge到Go主干,并将这套机制正式命名为“go module”。由于vgo项目本身就是一个实验原型,merge到主干后,vgo这个术语以及vgo项目的使命也就就此结束了。后续Go modules机制将直接在Go主干上继续演化。

Go modules是go team在解决包依赖管理方面的一次勇敢尝试,无论如何,对Go语言来说都是一个好事。在本篇文章中,我们就一起来看看这个新引入的go modules机制。

一. 建立试验环境

由于加入go modules experiment机制的Go 1.11版本尚未正式发布,且go 1.11 beta1版本发布在go modules merge到主干之前,因此我们要进行go module试验只能使用Go tip版本,即主干上的最新版本。我们需要通过编译Go源码包的方式获得支持go module的go编译器:

编译Go项目源码的前提是你已经安装了一个发布版,比如Go 1.10.3。然后按照下面步骤执行即可:

$ git clone https://github.com/golang/go.git
$ mv go go-tip
$ cd go-tip
$ ./all.bash
Building Go cmd/dist using /root/.bin/go1.10.2.
Building Go toolchain1 using /root/.bin/go1.10.2.
Building Go bootstrap cmd/go (go_bootstrap) using Go toolchain1.
Building Go toolchain2 using go_bootstrap and Go toolchain1.
Building Go toolchain3 using go_bootstrap and Go toolchain2.
Building packages and commands for linux/amd64.
##### Testing packages.
ok      archive/tar    0.026s
... ...
##### API check

ALL TESTS PASSED
---
Installed Go for linux/amd64 in /root/.bin/go-tip
Installed commands in /root/.bin/go-tip/bin
*** You need to add /root/.bin/go-tip/bin to your PATH.

验证源码编译方式的安装结果:

# ./go version
go version devel +a241922 Fri Jul 13 00:03:31 2018 +0000 linux/amd64

查看有关go module的手册:

$  ./go help mod
usage: go mod [-v] [maintenance flags]

Mod performs module maintenance operations as specified by the
following flags, which may be combined.

The -v flag enables additional output about operations performed.

The first group of operations provide low-level editing operations
for manipulating go.mod from the command line or in scripts or
other tools. They read only go.mod itself; they do not look up any
information about the modules involved.

The -init flag initializes and writes a new go.mod to the current directory,
in effect creating a new module rooted at the current directory.
The file go.mod must not already exist.
If possible, mod will guess the module path from import comments
(see 'go help importpath') or from version control configuration.
To override this guess, use the -module flag.
(Without -init, mod applies to the current module.)

The -module flag changes (or, with -init, sets) the module's path
(the go.mod file's module line).
... ...

无法通过编译源码的方式获取go tip版的小伙伴们也不用着急,在后续即将发布的go 1.11 beta2版本中将会包含对go modules的支持,到时候按常规方式安装beta2即可体验go modules。

二. 传统Go构建以及包依赖管理的回顾

Go在构建设计方面深受Google内部开发实践的影响,比如go get的设计就深受Google内部单一代码仓库(single monorepo)和基于主干(trunk/mainline based)的开发模型的影响:只获取Trunk/mainline代码和版本无感知。

img{512x368}

Google内部基于主干的开发模型:
– 所有开发人员基于主干trunk/mainline开发:提交到trunk或从trunk获取最新的代码(同步到本地workspace)
– 版本发布时,建立Release branch,release branch实质上就是某一个时刻主干代码的快照;
– 必须同步到release branch上的bug fix和增强改进代码也通常是先在主干上提交(commit),然后再cherry-pick到release branch上

我们知道go get获取的代码会放在$GOPATH/src下面,而go build会在$GOROOT/src和$GOPATH/src下面按照import path去搜索package,由于go get 获取的都是各个package repo的trunk/mainline的代码,因此,Go 1.5之前的Go compiler都是基于目标Go程序依赖包的trunk/mainline代码去编译的。这样的机制带来的问题是显而易见的,至少包括:

  • 因依赖包的trunk的变化,导致不同人获取和编译你的包/程序时得到的结果实质是不同的,即不能实现reproduceable build
  • 因依赖包的trunk的变化,引入不兼容的实现,导致你的包/程序无法通过编译
  • 因依赖包演进而无法通过编译,导致你的包/程序无法通过编译

为了实现reporduceable build,Go 1.5引入了Vendor机制,Go编译器会优先在vendor下搜索依赖的第三方包,这样如果开发者将特定版本的依赖包存放在vendor下面并提交到code repo,那么所有人理论上都会得到同样的编译结果,从而实现reporduceable build。

在Go 1.5发布后的若干年,gopher们把注意力都集中在如何利用vendor解决包依赖问题,从手工添加依赖到vendor、手工更新依赖,到一众包依赖管理工具的诞生:比如: govendorglide以及号称准官方工具的dep,努力地尝试着按照当今主流思路解决着诸如:“钻石型依赖”等难题。

正当gopher认为dep将“顺理成章”地升级为go toolchain一部分的时候,vgo横空出世,并通过对“Semantic Import Versioning”和”Minimal Version Selected”的设定,在原Go tools上简单快速地实现了Go原生的包依赖管理方案 。vgo就是go module的前身。

三. go modules定义、experiment开关以及“依赖管理”的工作模式

通常我们会在一个repo(仓库)中创建一组Go package,repo的路径比如:github.com/bigwhite/gocmpp会作为go package的导入路径(import path),Go 1.11给这样的一组在同一repo下面的packages赋予了一个新的抽象概念: module,并启用一个新的文件go.mod记录module的元信息。

不过一个repo对应一个module这种说法其实并不精确也并不正确,一个repo当然可以拥有多个module,很多公司或组织是喜欢用monorepo的,这样势必有在单一的monorepo建立多个module的需求,显然go modules也是支持这种情况的。

img{512x368}
图:single repo,single module

img{512x368}
图:single monorepo,multiple modules

是时候上代码了!

我们在~/test下建立hello目录(注意:$GOPATH=~/go,显然hello目录并不在GOPATH下面)。hello.go的代码如下:

// hello.go
package main

import "bitbucket.org/bigwhite/c"

func main() {
    c.CallC()
}

我们构建一下hello.go这个源码文件:

# go build hello.go
hello.go:3:8: cannot find package "bitbucket.org/bigwhite/c" in any of:
    /root/.bin/go-tip/src/bitbucket.org/bigwhite/c (from $GOROOT)
    /root/go/src/bitbucket.org/bigwhite/c (from $GOPATH)

构建错误!错误原因很明了:在本地的GOPATH下并没有找到bitbucket.org/bigwhite/c路径的package c。传统fix这个问题的方法是手工将package c通过go get下载到本地(并且go get会自动下载package c所依赖的package d):

# go get bitbucket.org/bigwhite/c
# go run hello.go
call C: master branch
   --> call D:
    call D: master branch
   --> call D end

这种我们最熟悉的Go compiler从$GOPATH下(以及vendor目录下)搜索目标程序的依赖包的模式称为:“GOPATH mode”

GOPATH是Go最初设计的产物,在Go语言快速发展的今天,人们日益发现GOPATH似乎不那么重要了,尤其是在引入vendor以及诸多包管理工具后。并且GOPATH的设置还会让Go语言新手感到些许困惑,提高了入门的门槛。Go core team也一直在寻求“去GOPATH”的方案,当然这一过程是循序渐进的。Go 1.8版本中,如果开发者没有显式设置GOPATH,Go会赋予GOPATH一个默认值(在linux上为$HOME/go)。虽说不用再设置GOPATH,但GOPATH还是事实存在的,它在go toolchain中依旧发挥着至关重要的作用。

Go module的引入在Go 1.8版本上更进了一步,它引入了一种新的依赖管理mode:“module-aware mode”。在该mode下,某源码树(通常是一个repo)的顶层目录下会放置一个go.mod文件,每个go.mod文件定义了一个module,而放置go.mod文件的目录被称为module root目录(通常对应一个repo的root目录,但不是必须的)。module root目录以及其子目录下的所有Go package均归属于该module,除了那些自身包含go.mod文件的子目录。

在“module-aware mode”下,go编译器将不再在GOPATH下面以及vendor下面搜索目标程序依赖的第三方Go packages。我们来看一下在“module-aware mode”下hello.go的构建过程:

我们首先在~/test/hello下创建go.mod:

// go.mod
module hello

然后构建hello.go

# go build hello.go
go: finding bitbucket.org/bigwhite/d v0.0.0-20180714005150-3e3f9af80a02
go: finding bitbucket.org/bigwhite/c v0.0.0-20180714063616-861b08fcd24b
go: downloading bitbucket.org/bigwhite/c v0.0.0-20180714063616-861b08fcd24b
go: downloading bitbucket.org/bigwhite/d v0.0.0-20180714005150-3e3f9af80a02

# ./hello
call C: master branch
   --> call D:
    call D: master branch
   --> call D end

我们看到go compiler并没有去使用之前已经下载到GOPATH下的bitbucket.org/bigwhite/c和bitbucket.org/bigwhite/d,而是主动下载了这两个包并成功编译。我们看看执行go build后go.mod文件的内容:

# cat go.mod
module hello

require (
    bitbucket.org/bigwhite/c v0.0.0-20180714063616-861b08fcd24b
    bitbucket.org/bigwhite/d v0.0.0-20180714005150-3e3f9af80a02 // indirect
)

我们看到go compiler分析出了hello module的依赖,将其放入go.mod的require区域。由于c、d两个package均没有版本发布(打tag),因此go compiler使用了c、d的当前最新版,并以Pseudo-versions的形式记录之。并且我们看到:hello module并没有直接依赖d package,因此在d的记录后面通过注释形式标记了indirect,即非直接依赖,也就是传递依赖。

在“module-aware mode”下,go compiler将下载的依赖包缓存在$GOPATH/pkg/mod下面:

// $GOPATH/pkg/mod
# tree -L 3
.
├── bitbucket.org
│   └── bigwhite
│       ├── c@v0.0.0-20180714063616-861b08fcd24b
│       └── d@v0.0.0-20180714005150-3e3f9af80a02
├── cache
│   ├── download
│   │   ├── bitbucket.org
│   │   ├── golang.org
│   │   └── rsc.io
│   └── vcs
│       ├── 064503657de46d4574a6ab937a7a3b88fee03aec15729f7493a3dc8e35cc6d80
│       ├── 064503657de46d4574a6ab937a7a3b88fee03aec15729f7493a3dc8e35cc6d80.info
│       ├── 0c8659d2f971b567bc9bd6644073413a1534735b75ea8a6f1d4ee4121f78fa5b
... ...

我们看到c、d两个package也是按照“版本”进行缓存的,便于后续在“module-aware mode”下进行包构建使用。

Go modules机制在go 1.11中是experiment feature,按照Go的惯例,在新的experiment feature首次加入时,都会有一个特性开关,go modules也不例外,GO111MODULE这个临时的环境变量就是go module特性的experiment开关。GO111MODULE有三个值:auto、on和off,默认值为auto。GO111MODULE的值会直接影响Go compiler的“依赖管理”模式的选择(是GOPATH mode还是module-aware mode),我们详细来看一下:

  • 当GO111MODULE的值为off时,go modules experiment feature关闭,go compiler显然会始终使用GOPATH mode,即无论要构建的源码目录是否在GOPATH路径下,go compiler都会在传统的GOPATH和vendor目录(仅支持在gopath目录下的package)下搜索目标程序依赖的go package;

  • 当GO111MODULE的值为on时(export GO111MODULE=on),go modules experiment feature始终开启,与off相反,go compiler会始终使用module-aware mode,即无论要构建的源码目录是否在GOPATH路径下,go compiler都不会在传统的GOPATH和vendor目录下搜索目标程序依赖的go package,而是在go mod命令的缓存目录($GOPATH/pkg/mod)下搜索对应版本的依赖package;

  • 当GO111MODULE的值为auto时(不显式设置即为auto),也就是我们在上面的例子中所展现的那样:使用GOPATH mode还是module-aware mode,取决于要构建的源码目录所在位置以及是否包含go.mod文件。如果要构建的源码目录不在以GOPATH/src为根的目录体系下,且包含go.mod文件(两个条件缺一不可),那么使用module-aware mode;否则使用传统的GOPATH mode。

四. go modules的依赖版本选择

1. build list和main module

go.mod文件一旦创建后,它的内容将会被go toolchain全面掌控。go toolchain会在各类命令执行时,比如go get、go build、go mod等修改和维护go.mod文件。

之前的例子中,hello module依赖的c、d(indirect)两个包均没有显式的版本信息(比如: v1.x.x),因此go mod使用Pseudo-versions机制来生成和记录c, d的“版本”,我们可以通过下面命令查看到这些信息:

# go list -m -json all
{
    "Path": "hello",
    "Main": true,
    "Dir": "/root/test/hello"
}
{
    "Path": "bitbucket.org/bigwhite/c",
    "Version": "v0.0.0-20180714063616-861b08fcd24b",
    "Time": "2018-07-14T06:36:16Z",
    "Dir": "/root/go/pkg/mod/bitbucket.org/bigwhite/c@v0.0.0-20180714063616-861b08fcd24b"
}
{
    "Path": "bitbucket.org/bigwhite/d",
    "Version": "v0.0.0-20180714005150-3e3f9af80a02",
    "Time": "2018-07-14T00:51:50Z",
    "Indirect": true,
    "Dir": "/root/go/pkg/mod/bitbucket.org/bigwhite/d@v0.0.0-20180714005150-3e3f9af80a02"
}

go list -m输出的信息被称为build list,也就是构建当前module所要构建的所有相关package(及版本)的列表。在输出信息中我们看到 “Main”: true这一信息,标识当前的module为“main module”。所谓main module,即是go build命令执行时所在当前目录所归属的那个module,go命令会在当前目录、当前目录的父目录、父目录的父目录…等下面寻找go.mod文件,所找到的第一个go.mod文件对应的module即为main module。如果没有找到go.mod,go命令会提示下面错误信息:

# go build test/hello/hello.go
go: cannot find main module root; see 'go help modules'

当然我们也可以使用下面命令简略输出build list:

# go list -m all
hello
bitbucket.org/bigwhite/c v0.0.0-20180714063616-861b08fcd24b
bitbucket.org/bigwhite/d v0.0.0-20180714005150-3e3f9af80a02

2. module requirement

现在我们给c、d两个package打上版本信息:

package c:
v1.0.0
v1.1.0
v1.2.0

package d:
v1.0.0
v1.1.0
v1.2.0
v1.3.0

然后清除掉$GOPATH/pkg/mod目录,并将hello.mod重新置为初始状态(只包含module字段)。接下来,我们再来构建一次hello.go:

// ~/test/hello目录下

# go build hello.go
go: finding bitbucket.org/bigwhite/c v1.2.0
go: downloading bitbucket.org/bigwhite/c v1.2.0
go: finding bitbucket.org/bigwhite/d v1.3.0
go: downloading bitbucket.org/bigwhite/d v1.3.0

# ./hello
call C: v1.2.0
   --> call D:
    call D: v1.3.0
   --> call D end

# cat go.mod
module hello

require (
    bitbucket.org/bigwhite/c v1.2.0 // indirect (c package被标记为indirect,这似乎是当前版本的一个bug)
    bitbucket.org/bigwhite/d v1.3.0 // indirect
)

我们看到,再一次初始构建hello module时,Go compiler不再用最新的commit revision所对应的Pseudo-version,而是使用了c、d两个package的最新发布版(c:v1.2.0,d: v1.3.0)。

如果我们对使用的c、d版本有特殊约束,比如:我们使用package c的v1.0.0,package d的v1.1.0版本,我们可以通过go mod -require来操作go.mod文件,更新go.mod文件中的require段的信息:

# go mod -require=bitbucket.org/bigwhite/c@v1.0.0
# go mod -require=bitbucket.org/bigwhite/d@v1.1.0

# cat go.mod
module hello

require (
    bitbucket.org/bigwhite/c v1.0.0 // indirect
    bitbucket.org/bigwhite/d v1.1.0 // indirect
)

# go build hello.go
go: finding bitbucket.org/bigwhite/d v1.1.0
go: finding bitbucket.org/bigwhite/c v1.0.0
go: downloading bitbucket.org/bigwhite/c v1.0.0
go: downloading bitbucket.org/bigwhite/d v1.1.0

# ./hello
call C: v1.0.0
   --> call D:
    call D: v1.1.0
   --> call D end

我们看到由于我们显式地修改了对package c、d两个包的版本依赖约束,go build构建时会去下载package c的v1.0.0和package d的v1.1.0版本并完成构建。

3. module query

除了通过传入package@version给go mod -requirement来精确“指示”module依赖之外,go mod还支持query表达式,比如:

# go mod -require='bitbucket.org/bigwhite/c@>=v1.1.0'

go mod会对query表达式做求值,得出build list使用的package c的版本:

# cat go.mod
module hello

require (
    bitbucket.org/bigwhite/c v1.1.0
    bitbucket.org/bigwhite/d v1.1.0 // indirect
)

# go build hello.go
go: downloading bitbucket.org/bigwhite/c v1.1.0
# ./hello
call C: v1.1.0
   --> call D:
    call D: v1.1.0
   --> call D end

go mod对module query进行求值的算法是“选择最接近于比较目标的版本(tagged version)”。以上面例子为例:

query text: >=v1.1.0
比较的目标版本为v1.1.0
比较形式:>=

因此,满足这一query的最接近于比较目标的版本(tagged version)就是v1.1.0。

如果我们给package d增加一个约束“小于v1.3.0”,我们再来看看go mod的选择:

# go mod -require='bitbucket.org/bigwhite/d@<v1.3.0'
# cat go.mod
module hello

require (
    bitbucket.org/bigwhite/c v1.1.0 // indirect
    bitbucket.org/bigwhite/d <v1.3.0
)

# go build hello.go
go: finding bitbucket.org/bigwhite/d v1.2.0
go: downloading bitbucket.org/bigwhite/d v1.2.0

# ./hello
call C: v1.1.0
   --> call D:
    call D: v1.2.0
   --> call D end

我们看到go mod选择了package d的v1.2.0版本,根据module query的求值算法,v1.2.0恰是最接近于“小于v1.3.0”的tagged version。

用下面这幅示意图来呈现这一算法更为直观一些:

img{512x368}

4. minimal version selection(mvs)

到目前为止,我们所使用的example都是最最简单的,hello module所依赖的package c和package d并没有自己的go.mod,也没有定义自己的requirements。对于复杂的包依赖场景,Russ Cox在“Minimal Version Selection”一文中给过形象的算法解释(注意:这个算法仅是便于人类理解,但是性能低下,真正的实现并非按照这个算法实现):

img{512x368}
例子情景

img{512x368}
算法的形象解释

MVS以build list为中心,从一个空的build list集合开始,先加入main module(A1),然后递归计算main module的build list,我们看到在这个过程中,先得到C 1.2的build list,然后是B 1.2的build list,去重合并后形成A1的rough build list,选择集合中每个module的最新version,最终形成A1的build list。

我们改造一下我们的例子,让它变得复杂些!

首先,我们为package c添加go.mod文件,并为其打一个新版本:v1.3.0:

//bitbucket.org/bigwhite/c/go.mod
module bitbucket.org/bigwhite/c

require (
        bitbucket.org/bigwhite/d v1.2.0
)

在module bitbucket.org/bigwhite/c的module文件中,我们为其添加一个requirment: bitbucket.org/bigwhite/d@v1.2.0。

接下来,我们将hello module重置为初始状态,并删除$GOPATH/pkg/mod目录。我们修改一下hello module的hello.go如下:

package main

import "bitbucket.org/bigwhite/c"
import "bitbucket.org/bigwhite/d"

func main() {
    c.CallC()
    d.CallD()
}

我们让hello module也直接调用package d,并且我们在初始情况下,给hello module添加一个requirement:

module hello

require (
    bitbucket.org/bigwhite/d v1.3.0
)

好了,这次我们再来构建一下hello module:

# go build hello.go
go: finding bitbucket.org/bigwhite/d v1.3.0
go: downloading bitbucket.org/bigwhite/d v1.3.0
go: finding bitbucket.org/bigwhite/c v1.3.0
go: downloading bitbucket.org/bigwhite/c v1.3.0
go: finding bitbucket.org/bigwhite/d v1.2.0
# cat go.mod
module hello

require (
    bitbucket.org/bigwhite/c v1.3.0 // indirect
    bitbucket.org/bigwhite/d v1.3.0 // indirect
)

# ./hello
call C: v1.3.0
   --> call D:
    call D: v1.3.0
   --> call D end
call D: v1.3.0

我们看到经过mvs算法后,go compiler最终选择了d v1.3.0版本。这里也模仿Russ Cox的图解给出hello module的mvs解析示意图(不过我这个例子还是比较simple):

img{512x368}

5. 使用package d的v2版本

按照语义化版本规范,当出现不兼容性的变化时,需要升级版本中的major值,而go modules允许在import path中出现v2这样的带有major版本号的路径,表示所用的package为v2版本下的实现。我们甚至可以同时使用一个package的v0/v1和v2两个版本的实现。我们依旧使用上面的例子来实操一下如何在hello module中使用package d的两个版本的代码。

我们首先需要为package d建立module文件:go.mod,并标识出当前的module为:bitbucket.org/bigwhite/d/v2(为了保持与v0/v1各自独立演进,可通过branch的方式来实现),然后基于该版本打v2.0.0 tag。

// bitbucket.org/bigwhite/d
#cat go.mod
module bitbucket.org/bigwhite/d/v2

改造一下hello module,import d的v2版本:

// hello.go
package main

import "bitbucket.org/bigwhite/c"
import "bitbucket.org/bigwhite/d/v2"

func main() {
    c.CallC()
    d.CallD()
}

清理hello module的go.mod,仅保留对package c的requirement:

module hello

require (
    bitbucket.org/bigwhite/c v1.3.0
)

清理$GOPATH/pkg/mod目录,然后重新构建hello module:

# go build hello.go
go: finding bitbucket.org/bigwhite/c v1.3.0
go: finding bitbucket.org/bigwhite/d v1.2.0
go: downloading bitbucket.org/bigwhite/c v1.3.0
go: downloading bitbucket.org/bigwhite/d v1.2.0
go: finding bitbucket.org/bigwhite/d/v2 v2.0.0
go: downloading bitbucket.org/bigwhite/d/v2 v2.0.0

# cat go.mod
module hello

require (
    bitbucket.org/bigwhite/c v1.3.0 // indirect
    bitbucket.org/bigwhite/d/v2 v2.0.0 // indirect
)

# ./hello
call C: v1.3.0
   --> call D:
    call D: v1.2.0
   --> call D end
call D: v2.0.0

我们看到c package依然使用的是d的v1.2.0版本,而main中使用的package d已经是v2.0.0版本了。

五. go modules与vendor

在最初的设计中,Russ Cox是想彻底废除掉vendor的,但在社区的反馈下,vendor得以保留,这也是为了兼容Go 1.11之前的版本。

Go modules支持通过下面命令将某个module的所有依赖保存一份copy到root module dir的vendor下:

# go mod -vendor
# ls
go.mod    go.sum  hello.go  vendor/
# cd vendor
# ls
bitbucket.org/    modules.txt
# cat modules.txt
# bitbucket.org/bigwhite/c v1.3.0
bitbucket.org/bigwhite/c
# bitbucket.org/bigwhite/d v1.2.0
bitbucket.org/bigwhite/d
# bitbucket.org/bigwhite/d/v2 v2.0.0
bitbucket.org/bigwhite/d/v2

# tree .
.
├── bitbucket.org
│   └── bigwhite
│       ├── c
│       │   ├── c.go
│       │   ├── go.mod
│       │   └── README.md
│       └── d
│           ├── d.go
│           ├── README.md
│           └── v2
│               ├── d.go
│               ├── go.mod
│               └── README.md
└── modules.txt

5 directories, 9 files

这样即便在go modules的module-aware mode模式下,我们依然可以只用vendor下的package来构建hello module。比如:我们先删除掉$GOPATH/pkg/mod目录,然后执行:

# go build -getmode=vendor hello.go
# ./hello
call C: v1.3.0
   --> call D:
    call D: v1.2.0
   --> call D end
call D: v2.0.0

当然生成的vendor目录还可以兼容go 1.11之前的go compiler。不过由于go 1.11之前的go compiler不支持在GOPATH之外使用vendor机制,因此我们需要将hello目录copy到$GOPATH/src下面,再用go 1.10.2版本的compiler编译它:

# go version
go version go1.10.2 linux/amd64
~/test/hello# go build hello.go
hello.go:3:8: cannot find package "bitbucket.org/bigwhite/c" in any of:
    /root/.bin/go1.10.2/src/bitbucket.org/bigwhite/c (from $GOROOT)
    /root/go/src/bitbucket.org/bigwhite/c (from $GOPATH)
hello.go:4:8: cannot find package "bitbucket.org/bigwhite/d/v2" in any of:
    /root/.bin/go1.10.2/src/bitbucket.org/bigwhite/d/v2 (from $GOROOT)
    /root/go/src/bitbucket.org/bigwhite/d/v2 (from $GOPATH)

# cp -r hello ~/go/src
# cd ~/go/src/hello
# go build hello.go
# ./hello
call C: v1.3.0
   --> call D:
    call D: v1.2.0
   --> call D end
call D: v2.0.0

编译输出和程序的执行结果均符合预期。

六. 小结

go modules刚刚merge到go trunk中,问题还会有很多。merge后很多gopher也提出了诸多问题,可以在这里查到。当然哪位朋友如果也遇到了go modules的问题,也可以在go官方issue上提出来,帮助go team尽快更好地完善go 1.11的go modules机制。

go module的加入应该算是go 1.11版本最大的变化,go module的内容很多,短时间内我的理解也可能存在偏差和错误,欢迎广大gopher们交流指正。

参考资料:


51短信平台:企业级短信平台定制开发专家 https://tonybai.com/
smspush : 可部署在企业内部的定制化短信平台,三网覆盖,不惧大并发接入,可定制扩展; 短信内容你来定,不再受约束, 接口丰富,支持长短信,签名可选。

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

我的联系方式:

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

微信赞赏:
img{512x368}

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

如发现本站页面被黑,比如:挂载广告、挖矿等恶意代码,请朋友们及时联系我。十分感谢! Go语言第一课 Go语言精进之路1 Go语言精进之路2 商务合作请联系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
DigitalOcean Referral Badge

文章

评论

  • 正在加载...

分类

标签

归档



View My Stats