Go语言第一课FAQ
发布于 2021-11-04 14:22 ,所属分类:软件编程学习资料
《Go语言第一课》[1]专栏正式上线后收到了很多读者的留言反馈,很多留言中的问题显然都是大家认真思考过提出的,在专栏后台我也尽可能地做出认真细致的回答。这些问题以及我的回答也算是我和专栏学习者基于专栏的二次创作,于是我有了将这些问题作为FAQ集中记录起来的想法,这就是这篇文章的由来。
本页面内容将持续更新!请本FAQ永久链接[2]- https://tonybai.com/go-course-faq。
一. 本人相关
关于音频中带有地方特色的口音^_^
这也是我第一次录带有音频的专栏,虽然音频老师给与了多次耐心的讲解,但毕竟不是专业的,在音频技巧方面还有提高空间。
有些童鞋听出了我的地方特色的口音,这个我得承认,而且这个不是短期能修正的^_^。我是辽宁人,辽宁一些地区的地方特色口音就是平翘舌界限不清晰,这里还希望大家海涵。
二. Go专栏
为什么要出这个Go专栏?
为此,我特意写了一篇简短的文章,叙述了这门Go专栏诞生幕后的那些事[3],感兴趣的朋友可以去看看。
专栏的更新节奏
根据极客时间要求,专栏每周更新三篇。希望我能保持生产力,争取不断更,压力山大啊!
是否有针对该专栏的交流群
目前暂没有。作者精力有限,能力有限,不适合维护这样的一个群,希望大家体谅。欢迎大家在专栏积极留言,我会认真解答大家问题的。
阅读完该专栏,我是否可以得到地道的Go代码编写风格、优雅的Go编程姿势呢?
虽然这门课的定位是入门课,而并非进阶课,但我在课程讲解以及Go示例代码中都会尽力以native的Go代码去呈现。并且课程讲解穿插着一些关于Go编码的最佳实践建议,希望大家在阅读后能有收获。
btw,要写出native 的Go代码,一定要多读高质量Go代码,Go标准库是一个最好的选择。俗话说:"熟读唐诗三百首,不会作诗也会吟",多读高质量代码,与此有异曲同工之妙。
专栏讲解使用的是Go最新稳定版本吗?
专栏写作开始于Go 1.17正式发布之前[4],因此早期的一些篇章使用的可能是Go 1.16.x版本,后期使用的几乎都是最新的Go 1.17.x。即便有一些引用的标准库或运行时的代码是Go 1.17之前的某个版本的,我这里也可以保证这些代码的引用仅是为了说明某个具体知识点,不会影响到大家的理解。
一个可能影响大家实践的问题就是从Go 1.17版本开始,go get不再用于安装某个Go版本或工具,我们要用Go install。Go install命令在之前的版本中几乎被弱化到“基本不用”的尴尬境地,其角色都被go get的光环所掩盖。
但从Go 1.17开始,Go install又恢复了其本职工作。
三. 入坑Go与Go前景
什么样的人适合学Go语言?
Go设计之初,其目标是成为一门通用的系统编程语言。这一目标基本上就将Go划分到后端编程语言行列。虽然Go社区在前端、移动端编程的支持上面都做了很多尝试,比如:Gopherjs项目以及Go支持编译为webassembly来应对前端开发,再比如Gomobile项目(https://pkg.go.dev/Golang.org/x/mobile)让Go也可以在移动端编程占有一席之地,但这么多年下来,Go的主力战场还是云原生基础设施、中间件、web/api服务、微服务、命令行应用等等。因此如果你的目标与这些领域重合,那么Go是一个很有竞争力的选择。
请问中小公司中的Go语言技术栈的岗位多吗?
Go是生产力与执行效率两方面都有突出表现的语言。这两方面都能给中小公司省下不少money。一线城市接纳新语言的开发者较多,招聘也不再是问题了。因此我觉得一线城市应该不少,这方面具体数据还得看招聘网站。二三线城市这些年Go也在拓展地盘。在我地处的东北地区,越来越多小公司选用了Go,趋势是好的。
希望老师能说一下Java和Go的区别?
这是很大的话题,也是一个极容易“引战”的话题。
看待这个问题有多种维度,比如从语言语法、生产力、性能、社区活跃度,生态成熟度、发展前景等等。
语言语法见仁见智,java是不折不扣的面向对象编程语言,就像“java编程思想”一书中说:“一切都是对象”。而Go是传统的命令式编程语言,按照Go语言family图谱,它的先祖来自C、Pascal、Newsqueak等。语法简单,但谈不上“领先”,就像很多人说的在最近10年出品的编程语言中,Go的语法显得有些“土气”,我更喜欢称之为朴实无华。很多人就像我,就是喜欢这种朴实。虽然朴实,但Go的表达力并不差哦。
在生产力方面,目前来看Go是要高于java的。
性能方面,同资源消耗下,Go也是要高于java的。另外一点就是即便是新手写Go,性能也不会很差。
社区活跃度方面,两者都是主流语言,java诞生年头多,且是目前企业应用领域的第一语言,其社区自然更好一些。生态成熟度也是如此,现在很难找到一个领域没有java的开源实现。实话说,Go在这方面规模还不及java,但是增长速度要更快。
至于,发展前景,两者都是自己擅长领域的佼佼者,都有不错的前景。Go由于处于成长期,蓝海属性更强一些。
Go在机器学习算法包括工程这一块前景如何?
这个要实话实说。在机器学习领域,python是当之无愧的老大。但python也有自己的瓶颈,主要是性能相较于静态语言有数量级差距。各个编程语言也都试图争抢python在机器学习领域的份额,包括julia、c++、rust,Go也不例外。但与在云原生领域的投入相比,Go社区在机器学习算法库方向上的投入还不够,但也有一些成果,比较知名的项目包括Gonum[5]、GorGonia[6]、onnx-Go[7]等。在帮助构建机器学习/深度学习平台层面上,Go也发挥了很大的作用,比如:kubeflow的部分实现。
机器学习算法上,python已经形成一家独大之势,其他语言,包括Go都会在自己擅长的领域一起助力机器学习的发展了。
Go在区块链方向应用广吗?
Go在区块链领域应用应该很多啊,至少区块链刚刚起步时,很多都是用Go开发的。比如联盟链fabric。以太坊已经足够Gopher学习好长时间了。另外像ipfs、filecoin等项目虽然不是典型区块链项目,但很多技术点都很相似,也可以了解一下。
四. Go的历史与哲学
有人吐槽 Go 核心人员不想做的东西,就是 Less is more,自己想做就是各种哲学,这个问题,老师怎么看?
Go语言的简单或者说功能特性少,的确来自于less is more的理念。保持一门小语言,让语言更容易学习与理解。同时每个特性都是经过精心打磨与实现,不能再少了。上周我看了Rob Pike最新一期的talk[8],他还在说 “Go语言中变量声明的方式有些多了”,这也是我在实际编码过程中的体会。如果重新来过,我想Rob Pike会更彻底的执行less is more,将变量声明方式再减少一种。所以说,特性少不是不想做,而是经过深思熟虑,那个特性的确没必要加入到语言中。
Go的异常处理,使用起来简单,但是不方便,请问老师这是在践行Go的简单设计哲学吗?
从Go设计者的初衷来看(https://golang.google.cn/doc/faq#exceptions),Go没有采用像java那样的结构化异常处理的确是出于对“简单”原则的考虑。
在java中错误处理与真正的“异常”是混杂在Try-catch机制中的,并没有明显的界限,无论是错误还是异常,一旦throw,方法的调用者就得负责处理它。
但在Go中,错误处理与真正的异常处理是严格分开的,也就是说不要将panic掺和到错误处理中。
错误处理是常态,Go中只有错误是返回给上层的。一旦出现panic,这意味着整个程序处于即将崩溃的状态,返回给上层几乎也是“无济于事”,所以在Go中,一个常见的api设计思路是:不要向外部抛出panic(don't panic!)。如果api中存在panic的可能性,那么api自己要负责处理panic,并通过error将状态返回给上层。如果api无法处理panic,那程序就很大可能是要崩溃了,这种panic多是因为程序bug导致的。
Go的统一代码有利的地方是:保证了开发者的编码风格是一致的,增加了代码的可读性。但这会不会对一些高手来说是一个限制呢?
Go面向工程的设计哲学鼓励复杂软件开发的大协作,Go不鼓励“奇技淫巧”,在Go中做一件事一般只有一种方法。所以我们看到的高手编写的开源项目或是标准库,代码绝大多数都是很容易读懂的。
什么是Go的自举?
和很多主流语言一样,Go语言编译器最初都是由C语言和汇编语言实现的。C语言和汇编实现的Go编译器(记作A)用来编译Go源文件。那么问题来了?是否可以用Go语言自身实现一个Go编译器B,用编译器A来编译Go编译器B工程的源码并链接成最终的Go编译器B呢?这就是Go核心团队在Go 1.5版本时做的事情。
他们将绝大多数原来用C和汇编编写的Go编译器以及运行时实现改为使用Go语言编写,并用Go 1.4.x编译器(C与汇编实现的,相当于A)编译出Go 1.5编译器。这样自Go 1.5版本[9]开始,Go编译器就是用Go语言实现的了,这就是所谓的自举。即用要编译的目标编程语言(Go语言)编写其(Go)编译器。
这之后,Go核心团队基本就告别C代码了,可以专心写Go代码了。这可以让Go核心团队积累更为丰富的Go语言编码经验,也算是一种“吃狗粮”。同时Go语言自身就是站在C语言等的肩膀上,修正了C语言等的缺陷并加入创新机制而形成的,用Go编码效率高,还可避面C语言的很多坑。
在这个基础上,使用Go语言实现编译器和runtime还利于Go编译器以及运行时的优化,Go 1.5及后续版本GC延迟大幅降低以及性能的大幅提升都说明了这一点。这就是自举的重要之处。
五. Go开发环境安装
关于gotip版本
很多初学者不知道gotip版本的存在,gotip指代就是目前Go核心团队正在积极开发的项目最新提交版本,因此gotip时刻在变化。当我们通过go get/Go install(Go 1.17及以后版本)方式安装go-tip版本时,go get其实也是下载Go项目最新源码,然后编译这份源码。如果某个Go核心开发者提交一次代码恰好导致Go tip源码编译不过去,而你下载的恰恰是这个时刻的Go tip源码,那你的Go tip安装自然就会因build失败而失败。这也是我提到的gotip版本不是每次都能安装成功的原因。
Go env里面的配置项究竟是存储在哪儿的?网上有说是生成Go 命令(Go语言的的编译工具)时,直接包含在其中了,也有说是在一个和用户相关的配置文件夹里面,还有的说是来自系统环境变量,那这三种来源的优先级是怎么样的?
Go env的确会综合多个数据源。优先级最高的是用户级环境变量。以linux为例,你的用户下的.profile文件中的环境变量优先级最高。然后是系统级环境变量(但我们很少在linux下用系统级环境变量),最后是Go自带的默认值。
六. Go程序构建
如何import自己在本地创建的module,在这个module还没有发布到GitHub的情况下?
go module机制在您提到的工作场景下目前的体验做的还不够好。在Go 1.17版本及之前版本的解决方法是使用go mod的replace指示符(directive)。假如你的module a要import的module b将发布到github.com/user/b中,那么你可以手动在module的go.mod中的require块中手工加上一条:
requiregithub.com/user/bv1.0.0
注意v1.0.0这个版本号是一个临时的版本号。
然后在module a的go.mod中使用replace将上面对module b的require替换为本地的module b:
replacegithub.com/user/bv1.0.0=>moduleb本地路径
这样Go命令就会使用你本地正在开发、尚未提交github的module b了。
Go应用项目源码还需要放在gopath的src下么?
go module与gopath的一个重要区别就是可以将项目放在任意路径下,而无需局限在 $GOPATH/src下面。我之所以将一个module放在一个任意路径下,就是故意要与GOPATH模式区分开的。
go.mod中的module path必须是github.com/user/repo这样的形式么?
专栏例子中使用github.com/user/repo这个样式作为module path是因为多数实用级module多是要上传到github上的。用这种示例便于后续与真实生产接驳。但对于本地开发使用的简单示例程序而言,module path可以任意选用,比如:
//go.mod
moduledemo1
Go1.17
也是ok的。
go get或go mod tidy下载的go module缓存在哪里了?
go mod tidy下载的第三方module一般在 $GOPATH[0]/pkg/mod下面。这里的GOPATH[0]指的是GOPATH环境变量设置的多个路径中的第一个路径,比如说:如果GOPATH的设置如下:
exportGOPATH=path1:path2:path3
那么下载的第三方module就会被缓存在path1/pkg/mod下面。
如果你没有设置GOPATH环境变量也没关系,而且这不是必须的步骤。gopath的默认值为你的home路径下的Go文件夹。这样第三方包就在 $HOME/Go文件夹的pkg/mod下面。
Go 1.15版本[10]开始,Go提供了GOMODCACHE环境变量用于自定义module cache的存放位置。如果没有显式设置GOMODCACHE,那么module cache的默认存储路径依然是 $GOPATH[0]/pkg/mod。
是否需要深入了解gopath
Go官方有移除gopath的打算。目前这个时间点,学习Go基本不需要了解太多gopath了。
有没有推荐的免费好用的国内module proxy服务?
我个人最常用的是下面这个proxy服务:
exportGOPROXY=https://goproxy.cn,direct
其他的几个proxy服务也应该都很好用:
exportGOPROXY=https://goproxy.io,direct
exportGOPROXY=https://mirrors.aliyun.com/goproxy,direct
exportGOPROXY=https://goproxy.baidu.com,direct
以上代理除了通过环境变量配置外,还可以用go env命令写入,以阿里的module proxy为例:
$Goenv-wGOPROXY=https://mirrors.aliyun.com/goproxy/
如何拉取私有go module?
这个属于稍高级一些话题,这门课尚不会涉及。之前写过一篇有关拉取私有module[11]的文章可以参考。
什么是可重现构建(Reproducible Build)?
可重现构建,顾名思义,就是针对同一份go module的源码进行构建,不同人,在不同机器(同一架构,比如都是x86-64),相同os上,在不同时间点都能得到相同的二进制文件。
go.mod文件中能表述依赖的module信息吗?go.mod文件中的内容一般不都是依赖的第三方包和版本吗?
在go module机制进入Go之前,也就是gopath构建模式时代,我们谈到的所有依赖都是包与包的版本;但go module引入后,所有的版本信息都绑定在module上,所以你在go.mod中看到的require块中的依赖都是module与module的版本,不再是包。
Go团队认为“最小版本选择”为Go程序实现持久的和可重现的构建提供了最佳的方案” 这句话能展开讲讲吗?
对开发者而言,更易于理解和预测,就像课程中例子那样,我们根据依赖图可以很容易确定程序构建最终使用的依赖版本。
对Go核心团队来说,更容易实现,据说实现最小选择的代码也就几十行。
更重要的是最小版本选择更容易实现可重现构建。试想一下,如果选择的是最大最新版本,那么针对同一份代码,其依赖包的最新最大版本在不同时刻可能是不同的,那么在不同时刻的构建,产生的最终文件就是不同的。
当然这一切的前提都是基于语义版本规范,对于不符合规范的module,相当于没有遵守契约,这套规则也就失效。这对任何语言来说都是一样的。
在包依赖引用那一节,您说的是A和B依赖C的v1.1.0和v1.3.0版本,这种版本依赖很好理解。但是按照上述的V1和V2不同的原则,如果现在B依赖的不是v1.3.0而是v2.3.0,那我现在项目里引用的C到底是哪个版本?
如果B依赖的是C v2.3.0,那么B导入C的语句就是 import c/v2,而A依赖的是v1.1.0,那么A导入c的语句就是import c,这两个是可以共存的啊。于是你会在go.mod中既看到c v1.1.0,也有c/v2 v2.3.0,它们可以理解为两个不同的module。
七. Go语法
什么是Go运行时?
Go 运行时,也称为Go runtime。
它在那里?其本身就是每个Go程序的一部分,它会跟你的源码一起编译并连接到目标程序中。即便你只是写了一个hello world程序,这个程序中也包含了runtime的实现。
它在我的程序中具体负责什么?runtime负责实现Go的垃圾收集、并发、内存堆栈管理以及Go语言的其他关键功能。
它的代码在哪里?它大部分以标准库的形式存放在每个Go发布版的源码中。
包的空导入有什么作用?
像下面代码这样的包导入方式被称为“空导入”:
import_"foo"
空导入也是导入,意味着我们将依赖foo这个路径下的包。但由于是空导入,我们并没有显式使用这个包中的任何语法元素。那么空导入的意义是什么呢?由于依赖foo包,程序初始化的时候会沿着包的依赖链初始化foo包,我们在08里会讲到包的初始化会按照常量->变量->init函数的次序进行。通常实践中空导入意味着期望依赖包的init函数得到执行,这个init函数中有我们需要的逻辑。
八. Go程序设计
专栏中提到的“一动一静共同构成了Go 应用程序的骨架”中的一动一静指的是什么?该如何理解
关于“一动一静”,“动”主要指程序的并发设计层面,如何设计去管理和控制Goroutine。当程序运行起来后,真正“动”的是一个一个Goroutine。而“静”,则是Go源码中的实体以及它们之间的耦合关系。
九. Go标准库
printf 能格式化字符串,换行就要手动添加 "\n",println 又不能格式化字符串。我想知道为什么要这样的设计?在看我来这就是特别反人类的设定,Rust 的 println!("{}", a); 才是符合直觉的。
这个问题我是这么看的,printf是go提供的标准格式化io的函数,它能实现你所期望的所有功能。与c语言的printf是对等的。但println这个函数你可以看成是一种“语法糖”,它本身就是一个特例,你可以用go doc看看println的manual,println原语义就是使用一种默认的格式输出数据到stdout上。你认同这种默认格式,你就使用println,简化你的代码。否则,你就用printf就好了
十. Go工具链与工程实践
谈谈支持Go的VS Code的Copilot插件
copilot插件我还没体验过,如果真的如你所言那么强大,那也是Go语言和Go开发者的一大幸事
十一. 其他
暂无。
参考资料
《Go语言第一课》:http://gk.link/a/10AVZ
[2]本FAQ永久链接:https://tonybai.com/go-course-faq
[3]这门Go专栏诞生幕后的那些事:https://tonybai.com/2021/10/25/the-things-behind-the-first-lesson-of-go-language
[4]专栏写作开始于Go 1.17正式发布之前:https://tonybai.com/2021/10/25/the-things-behind-the-first-lesson-of-go-language
[5]Gonum:https://github.com/Gonum/Gonum
[6]GorGonia:https://github.com/GorGonia/GorGonia
[7]onnx-Go:https://github.com/owulveryck/onnx-Go
[8]Rob Pike最新一期的talk:https://tonybai.com/2021/10/06/the-go-programming-language-and-environment
[9]Go 1.5版本:https://tonybai.com/2015/07/10/some-changes-in-go-1-5/
[10]Go 1.15版本:https://tonybai.com/2020/10/11/some-changes-in-go-1-15/
[11]拉取私有module:https://tonybai.com/2021/09/03/the-approach-to-go-get-private-go-module-in-house
相关资源