8

Go语言第一课FAQ

 2 years ago
source link: https://tonybai.com/go-course-faq/
Go to the source link to view the article. You can view the picture content, updated content and better typesetting reading experience. If the link is broken, please click the button below to view the snapshot at that time.
neoserver,ios ssh client

本文永久链接 – https://tonybai.com/go-course-faq

《Go语言第一课》专栏正式上线后收到了很多读者的留言反馈,很多留言中的问题显然都是大家认真思考过提出的,在专栏后台我也尽可能地做出认真细致的回答。这些问题以及我的回答也算是我和专栏学习者基于专栏的二次创作,于是我有了将这些问题作为FAQ集中记录起来的想法,这就是这篇文章的由来。

本页面内容将持续更新!请持续关注本FAQ永久链接 – https://tonybai.com/go-course-faq。

一. 本人相关

  • 关于音频中带有地方特色的口音^_^

这也是我第一次录带有音频的专栏,虽然音频老师给与了多次耐心的讲解,但毕竟不是专业的,在音频技巧方面还有提高空间。

有些童鞋听出了我的地方特色的口音,这个我得承认,而且这个不是短期能修正的^_^。我是辽宁人,辽宁一些地区的地方特色口音就是平翘舌界限不清晰,这里还希望大家海涵。

二. Go专栏

  • 为什么要出这个Go专栏?

为此,我特意写了一篇简短的文章,叙述了这门Go专栏诞生幕后的那些事,感兴趣的朋友可以去看看。

  • 专栏的更新节奏

根据极客时间要求,专栏每周更新三篇。希望我能保持生产力,争取不断更,压力山大啊!

  • 是否有针对该专栏的交流群

目前暂没有。作者精力有限,能力有限,不适合维护这样的一个群,希望大家体谅。欢迎大家在专栏积极留言,我会认真解答大家问题的。

  • 阅读完该专栏,我是否可以得到地道的Go代码编写风格、优雅的Go编程姿势呢?

虽然这门课的定位是入门课,而并非进阶课,但我在课程讲解以及Go示例代码中都会尽力以native的Go代码去呈现。并且课程讲解穿插着一些关于Go编码的最佳实践建议,希望大家在阅读后能有收获。

btw,要写出native 的Go代码,一定要多读高质量Go代码,Go标准库是一个最好的选择。俗话说:”熟读唐诗三百首,不会作诗也会吟”,多读高质量代码,与此有异曲同工之妙。

  • 专栏讲解使用的是Go最新稳定版本吗?

专栏写作开始于Go 1.17正式发布之前,因此早期的一些篇章使用的可能是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设计之初,其目标是成为一门通用的系统编程语言。这一目标基本上就将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社区在机器学习算法库方向上的投入还不够,但也有一些成果,比较知名的项目包括GonumGorGoniaonnx-Go等。在帮助构建机器学习/深度学习平台层面上,Go也发挥了很大的作用,比如:kubeflow的部分实现。

机器学习算法上,python已经形成一家独大之势,其他语言,包括Go都会在自己擅长的领域一起助力机器学习的发展了。

  • Go在区块链方向应用广吗?

Go在区块链领域应用应该很多啊,至少区块链刚刚起步时,很多都是用Go开发的。比如联盟链fabric。以太坊已经足够Gopher学习好长时间了。另外像ipfs、filecoin等项目虽然不是典型区块链项目,但很多技术点都很相似,也可以了解一下。

四. Go的历史与哲学

  • 有人吐槽 Go 核心人员不想做的东西,就是 Less is more,自己想做就是各种哲学,这个问题,老师怎么看?

Go语言的简单或者说功能特性少,的确来自于less is more的理念。保持一门小语言,让语言更容易学习与理解。同时每个特性都是经过精心打磨与实现,不能再少了。上周我看了Rob Pike最新一期的talk,他还在说 “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版本开始,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块中手工加上一条:

require github.com/user/b v1.0.0

注意v1.0.0这个版本号是一个临时的版本号。

然后在module a的go.mod中使用replace将上面对module b的require替换为本地的module b:

replace github.com/user/b v1.0.0 => module b本地路径

这样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
module demo1

Go 1.17

也是ok的。

  • go get或go mod tidy下载的go module缓存在哪里了?

go mod tidy下载的第三方module一般在$GOPATH[0]/pkg/mod下面。这里的GOPATH[0]指的是GOPATH环境变量设置的多个路径中的第一个路径,比如说:如果GOPATH的设置如下:

export GOPATH=path1:path2:path3

那么下载的第三方module就会被缓存在path1/pkg/mod下面。

如果你没有设置GOPATH环境变量也没关系,而且这不是必须的步骤。gopath的默认值为你的home路径下的Go文件夹。这样第三方包就在$HOME/Go文件夹的pkg/mod下面。

Go 1.15版本开始,Go提供了GOMODCACHE环境变量用于自定义module cache的存放位置。如果没有显式设置GOMODCACHE,那么module cache的默认存储路径依然是$GOPATH[0]/pkg/mod。

  • 是否需要深入了解gopath

Go官方有移除gopath的打算。目前这个时间点,学习Go基本不需要了解太多gopath了。

  • 有没有推荐的免费好用的国内module proxy服务?

我个人最常用的是下面这个proxy服务:

export GOPROXY=https://goproxy.cn,direct

其他的几个proxy服务也应该都很好用:

export GOPROXY=https://goproxy.io,direct
export GOPROXY=https://mirrors.aliyun.com/goproxy,direct
export GOPROXY=https://goproxy.baidu.com,direct

以上代理除了通过环境变量配置外,还可以用go env命令写入,以阿里的module proxy为例:

$Go env -w GOPROXY=https://mirrors.aliyun.com/goproxy/
  • 如何拉取私有go module?

这个属于稍高级一些话题,这门课尚不会涉及。之前写过一篇有关拉取私有module的文章可以参考。

  • 什么是可重现构建(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开发者的一大幸事

十一. 其他


About Joyk


Aggregate valuable and interesting links.
Joyk means Joy of geeK