第一部分 熟知Go语言的一切
第1条 了解Go语言的诞生与演进
在C语言的基础上,修正一些明显的缺陷,删除一些被诟病较多的特性,增加一些缺失的功能。具体功能和特性如下。
- 使用import替代include。
- 去掉宏(macro)。
- 理想情况是用一个源文件替代.h和.c文件,模块的接口应该被自动提取出来(而无须手动在.h文件中声明)。
- 语句像C语言一样,但需要修正switch语句的缺陷。
- 表达式像C语言一样,但有一些注意事项(比如是否需要逗号表达式)。
- 基本上是强类型的,但可能需要支持运行时类型。
- 数组应该总是有边界检查。
- 具备垃圾回收的机制。
- 支持接口(interface)。
- 支持嵌套和匿名函数/闭包。
- 一个简单的编译器。
- 各种语言机制应该能产生可预测的代码。
第2条 选择适当的Go语言版本
和绝大多数编程语言相似,Go语言也是“站在巨人的肩膀上的”,正如图2-1所示,Go继承了诸多编程语言的特性。
Go的基本语法参考了C语言,Go是“C家族语言”的一个分支;而Go的声明语法、包概念则受到了Pascal、Modula、Oberon的启发;一些并发的思想则来自受到Tony Hoare教授CSP理论影响的编程语言,比如Newsqueak和Limbo。
第3条 理解Go语言的设计哲学
3.1 追求简单,少即是多
简单是一种伟大的美德,但我们需要更艰苦地努力才能实现它,并需要经过一个教育的过程才能去欣赏和领会它。但糟糕的是:复杂的东西似乎更有市场。——Edsger Dijkstra,图灵奖得主
当我们问Gopher“你为什么喜欢Go语言”时,我们通常会得到很多答案,如图3-1所示。
不同于那些通过相互借鉴而不断增加新特性的主流编程语言(如C++、Java等),Go的设计者们在语言设计之初就拒绝走语言特性融合的道路,而选择了“做减法”,选择了“简单”,他们把复杂性留给了语言自身的设计和实现,留给了Go核心开发组自己,而将简单、易用和清晰留给了广大Gopher。因此,今天呈现在我们眼前的是这样的Go语言:
- 简洁、常规的语法(不需要解析符号表),它仅有25个关键字;
- 内置垃圾收集,降低开发人员内存管理的心智负担;
- 没有头文件;
- 显式依赖(package);
- 没有循环依赖(package);
- 常量只是数字;
- 首字母大小写决定可见性;
- 任何类型都可以拥有方法(没有类);
- 没有子类型继承(没有子类);
- 没有算术转换;
- 接口是隐式的(无须implements声明);
- 方法就是函数;
- 接口只是方法集合(没有数据);
- 方法仅按名称匹配(不是按类型);
- 没有构造函数或析构函数;
- n++和n--是语句,而不是表达式;
- 没有++n和--n;
- 赋值不是表达式;
- 在赋值和函数调用中定义的求值顺序(无“序列点”概念);
- 没有指针算术;
- 内存总是初始化为零值;
- 没有类型注解语法(如C++中的const、static等);
- 没有模板/泛型;
- 没有异常(exception);
- 内置字符串、切片(slice)、map类型;
- 内置数组边界检查;
- 内置并发支持;
……
任何设计都存在权衡与折中。Go设计者选择的“简单”体现在,站在巨人肩膀上去除或优化在以往语言中已被证明体验不好或难于驾驭的语法元素和语言机制,并提出自己的一些创新性的设计,比如首字母大小写决定可见性,内存分配初始零值,内置以go关键字实现的并发支持等)。Go设计者推崇“最小方式”思维,即一件事情仅有一种方式或数量尽可能少的方式去完成,这大大减少了开发人员在选择路径方式及理解他人所选路径方式上的心智负担。
3.2 偏好组合,正交解耦
当我们有必要采用另一种方式处理数据时,我们应该有一些耦合程序的方式,就像花园里将浇水的软管通过预置的螺丝扣拧入另一段那样,这也是Unix IO采用的方式。——Douglas McIlroy,Unix管道的发明者(1964)
C++、Java等主流面向对象(以下简称OO)语言通过庞大的自上而下的类型体系、继承、显式接口实现等机制将程序的各个部分耦合起来,但在Go语言中我们找不到经典OO的语法元素、类型体系和继承机制,或者说Go语言本质上就不属于经典OO语言范畴。针对这种情况,很多人会问:那Go语言是如何将程序的各个部分有机地耦合在一起的呢?就像上面引述的Douglas McIlroy那句话中的浇水软管那样,Go语言遵从的设计哲学也是组合。
在诠释组合之前,我们可以先来了解一下Go在语法元素设计时是如何为组合哲学的应用奠定基础的。
在语言设计层面,Go提供了正交的语法元素供后续组合使用,包括:
- Go语言无类型体系(type hierarchy),类型之间是独立的,没有子类型的概念;
- 每个类型都可以有自己的方法集合,类型定义与方法实现是正交独立的;
- 接口(interface)与其实现之间隐式关联;
- 包(package)之间是相对独立的,没有子包的概念。
我们看到无论是包、接口还是一个个具体的类型定义(包括类型的方法集合),Go语言为我们呈现了这样一幅图景:一座座没有关联的“孤岛”,但每个岛内又都很精彩。现在摆在面前的工作就是以最适当的方式在这些孤岛之间建立关联(耦合),形成一个整体。Go采用了组合的方式,也是唯一的方式。
Go语言提供的最为直观的组合的语法元素是类型嵌入(type embedding)。通过类型嵌入,我们可以将已经实现的功能嵌入新类型中,以快速满足新类型的功能需求。这种方式有些类似经典OO语言中的继承机制,但在原理上与其完全不同,这是一种Go设计者们精心设计的语法糖。被嵌入的类型和新类型之间没有任何关系,甚至相互完全不知道对方的存在,更没有经典OO语言中的那种父类、子类的关系以及向上、向下转型(type casting)。在通过新类型实例调用方法时,方法的匹配取决于方法名字,而不是类型。这种组合方式,笔者称之为“垂直组合”,即通过类型嵌入,快速让一个新类型复用其他类型已经实现的能力,实现功能的垂直扩展。
下面是一个类型嵌入的例子:
// $GOROOT/src/sync/pool.go
type poolLocal struct {
private interface{}
shared []interface{}
Mutex
pad [128]byte
}
我们在poolLocal这个结构体类型中嵌入了类型Mutex,被嵌入的Mutex类型的方法集合会被提升到外面的类型(poolLocal)中。比如,这里的poolLocal将拥有Mutex类型的Lock和Unlock方法。但在实际调用时,方法调用会被传给poolLocal中的Mutex实例。
我们在标准库中还经常看到如下的interface类型嵌入的代码:
// $GOROOT/src/io/io.go
type ReadWriter interface {
Reader
Writer
}
通过在interface的定义中嵌入interface类型来实现接口行为的聚合,组成大接口,这种方式在标准库中尤为常用,并且已经成为Go语言的一种惯用法。
interface是Go语言中真正的“魔法”,是Go语言的一个创新设计,它只是方法集合,且与实现者之间的关系是隐式的,它让程序各个部分之间的耦合降至最低,同时是连接程序各个部分的“纽带”。隐式的interface实现会不经意间满足依赖抽象、里氏替换、接口隔离等设计原则,这在其他语言中是需要很刻意的设计谋划才能实现的,但在Go interface看来,一切却是自然而然的。
通过interface将程序各个部分组合在一起的方法,笔者称之为“水平组合”。水平组合的模式有很多,一种常见的方法是通过接受interface类型参数的普通函数进行组合,例如下面的代码。
// $GOROOT/src/io/ioutil/ioutil.go
func ReadAll(r io.Reader)([]byte, error)
// $GOROOT/src/io/io.go
func Copy(dst Writer, src Reader)(written int64, err error)
函数ReadAll通过io.Reader这个接口将io.Reader的实现与ReadAll所在的包以低耦合的方式水平组合在一起了。类似的水平组合模式还有wrapper、middleware等,这里就不展开了,在后面讲到interface时再详细叙述。
此外,Go语言内置的并发能力也可以通过组合的方式实现对计算能力的串联,比如通过goroutine+channel的组合实现类似Unix Pipe的能力。
综上,组合原则的应用塑造了Go程序的骨架结构
- 类型嵌入为类型提供垂直扩展能力
- interface是水平组合的关键,它好比程序肌体上的“关节”,给予连接“关节”的两个部分各自“自由活动”的能力,而整体上又实现了某种功能。
组合也让遵循简单原则的Go语言在表现力上丝毫不逊色于复杂的主流编程语言。
3.3 原生并发,轻量高效
并发是有关结构的,而并行是有关执行的。——Rob Pike(2012)
Go的设计者敏锐地把握了CPU向多核方向发展的这一趋势,在决定不再使用C++而去创建一门新语言的时候,果断将面向多核、原生内置并发支持作为新语言的设计原则之一。
Go语言原生支持并发的设计哲学体现在以下几点。
(1)Go语言采用轻量级协程并发模型,使得Go应用在面向多核硬件时更具可扩展性
Go果断放弃了传统的基于操作系统线程的并发模型,而采用了用户层轻量级线程或者说是类协程(coroutine),Go将之称为goroutine。goroutine占用的资源非常少,Go运行时默认为每个goroutine分配的栈空间仅2KB。goroutine调度的切换也不用陷入(trap)操作系统内核层完成,代价很低。因此,在一个Go程序中可以创建成千上万个并发的goroutine。所有的Go代码都在goroutine中执行,哪怕是Go的运行时代码也不例外。
不过,一个Go程序对于操作系统来说只是一个用户层程序。操作系统的眼中只有线程,它甚至不知道goroutine的存在。goroutine的调度全靠Go自己完成,实现Go程序内goroutine之间公平地竞争CPU资源的任务就落到了Go运行时头上。而将这些goroutine按照一定算法放到CPU上执行的程序就称为goroutine调度器(goroutine scheduler)。
(2)Go语言为开发者提供的支持并发的语法元素和机制
我们先来看看那些设计并诞生于单核年代的编程语言(如C、C++、Java)在语法元素和机制层面是如何支持并发的。
- 执行单元:线程。
- 创建和销毁的方式:调用库函数或调用对象方法。
- 并发线程间的通信:多基于操作系统提供的IPC机制,比如共享内存、Socket、Pipe等,当然也会使用有并发保护的全局变量。
与上述传统语言相比,Go提供了语言层面内置的并发语法元素和机制。
- 执行单元:goroutine。
- 创建和销毁方式:go+函数调用;函数退出即goroutine退出。
- 并发goroutine的通信:通过语言内置的channel传递消息或实现同步,并通过select实现多路channel的并发控制。
对比来看,Go对并发的原生支持将大大降低开发人员在开发并发程序时的心智负担。
(3)并发原则对Go开发者在程序结构设计层面的影响
由于goroutine的开销很小(相对线程),Go官方鼓励大家使用goroutine来充分利用多核资源。但并不是有了goroutine就一定能充分利用多核资源,或者说即便使用Go也不一定能写出好的并发程序。为此Rob Pike曾做过一次关于“并发不是并行”的主题分享,图文并茂地讲解了并发(Concurrency)和并行(Parallelism)的区别。RobPike认为:
- 并发是有关结构的,它是一种将一个程序分解成多个小片段并且每个小片段都可以独立执行的程序设计方法;并发程序的小片段之间一般存在通信联系并且通过通信相互协作。
- 并行是有关执行的,它表示同时进行一些计算任务。
以上观点的重点是,并发是一种程序结构设计的方法,它使并行成为可能。
采用并发方案设计的程序在单核处理器上也是可以正常运行的(在单核上的处理性能可能不如非并发方案),并且随着处理器核数的增多,并发方案可以自然地提高处理性能,提升吞吐量。而非并发方案在处理器核数提升后,也仅能使用其中的一个核,无法自然扩展,这一切都是程序的结构所决定的。这告诉我们:并发程序的结构设计不要局限于在单核情况下处理能力的高低,而要以在多核情况下充分提升多核利用率、获得性能的自然提升为最终目的。
除此之外,并发与组合的哲学是一脉相承的,并发是一个更大的组合的概念,它在程序设计层面对程序进行拆解组合,再映射到程序执行层面:goroutine各自执行特定的工作,通过channel+select将goroutine组合连接起来。并发的存在鼓励程序员在程序设计时进行独立计算的分解,而对并发的原生支持让Go语言更适应现代计算环境。
3.4 面向工程,“自带电池”
软件工程指引着Go语言的设计。——Rob Pike(2012)
要想理解这条设计哲学,我们依然需要回到三位Go语言之父在设计Go语言时的初衷:面向真实世界中Google内部大规模软件开发存在的各种问题,为这些问题提供答案。主要的问题包括:
- 程序构建慢;
- 失控的依赖管理;
- 开发人员使用编程语言的不同子集(比如C++支持多范式,这样有些人用OO,有些人用泛型);
- 代码可理解性差(代码可读性差、文档差等);
- 功能重复实现;
- 升级更新消耗大;
- 实现自动化工具难度高;
- 版本问题;
- 跨语言构建问题。
很多编程语言的设计者或拥趸认为这些问题并不是编程语言应该解决的,但Go语言的设计者并不这么看,他们以更高、更广阔的视角审视软件开发领域尤其是大规模软件开发过程中遇到的各种问题,并在Go语言最初设计阶段就将解决工程问题作为Go的设计原则之一去考虑Go语法、工具链与标准库的设计,这也是Go与那些偏学院派、偏研究性编程语言在设计思路上的一个重大差异。
Go设计者将所有工程问题浓缩为一个词:scale(笔者总觉得将scale这个词翻译为任何中文词都无法传神地表达其含义,暂译为“规模”吧)。从Go1开始,Go的设计目标就是帮助开发者更容易、更高效地管理两类规模。
- 生产规模:用Go构建的软件系统的并发规模,比如这类系统并发关注点的数量、处理数据的量级、同时并发与之交互的服务的数量等。
- 开发规模:包括开发团队的代码库的大小,参与开发、相互协作的工程师的人数等。
Go设计者期望Go可以游刃有余地应对生产规模和开发规模变大带来的各种复杂问题。Go语言的演进方向是优化甚至消除Go语言自身面对规模化问题时应对不好的地方,比如:Go 1.9引入类型别名(type alias)以应对大型代码仓库代码重构,Go 1.11引入go module机制以解决不完善的包依赖问题等。这种设计哲学的落地让Go语言具有广泛的规模适应性:既可以被仅有5人的初创团队用于开发终端工具,也能够满足像Google这样的巨型公司大规模团队开发大规模网络服务程序的需要。
那么Go是如何解决工程领域规模化所带来的问题的呢?我们从语言、标准库和工具链三个方面来看一下。
(1)语言
语法是编程语言的用户接口,它直接影响开发人员对于一门语言的使用体验。Go语言是一门简单的语言,简单意味着可读性好,容易理解,容易上手,容易修复错误,节省开发者时间,提升开发者间的沟通效率。但作为面向工程的编程语言,光有简单的设计哲学还不够,每个语言设计
细节还都要经过“工程规模化”的考验和打磨,需要在细节上进行充分的思考和讨论。
比如Rob Pike就曾谈到,Go当初之所以没有使用Python那样的代码缩进而是选择了与C语言相同的大括号来表示程序结构,是因为他们经过调查发现,虽然Python的缩进结构在构建小规模程序时的确很方便,但是当代码库变得更大的时候,缩进式的结构非常容易出错。从工程的安全性和可靠性角度考虑,Go团队最终选择了大括号代码块结构。类似的面向工程的语言设计细节考量还有以下这些。
- 重新设计编译单元和目标文件格式,实现Go源码快速构建,将大工程的构建时间缩短到接近于动态语言的交互式解释的编译时间。
- 如果源文件导入了它不使用的包,则程序将无法编译。这既可以充分保证Go程序的依赖树是精确的,也可以保证在构建程序时不会编译额外的代码,从而最大限度地缩短编译时间。
- 去除包的循环依赖。循环依赖会在大规模的代码中引发问题,因为它们要求编译器同时处理更大的源文件集,这会减慢增量构建速度。
- 在处理依赖关系时,有时会通过允许一部分重复代码来避免引入较多依赖关系。比如:net包具有其自己的整数到十进制转换实现,以避免依赖于较大且依赖性较强的格式化io包。
- 包路径是唯一的,而包名不必是唯一的。导入路径必须唯一标识要导入的包,而名称只是包的使用者对如何引用其内容的约定。包名不必是唯一的约定大大降低了开发人员给包起唯一名字的心智负担。
- 故意不支持默认函数参数。因为在规模工程中,很多开发者利用默认函数参数机制向函数添加过多的参数以弥补函数API的设计缺陷,这会导致函数拥有太多的参数,降低清晰度和可读性。
- 首字母大小写定义标识符可见性,这是Go的一个创新。它让开发人员通过名称即可知晓其可见性,而无须回到标识符定义的位置查找并确定其可见性,这提升了开发人员阅读代码的效率。
- 在语义层面,相对于C,Go做了很多改动,提升了语言的健壮性,比如去除指针算术,去除隐式类型转换等。
- 内置垃圾收集。这对于大型工程项目来说,大大降低了程序员在内存管理方面的负担,程序员使用GC感受到的好处超过了付出的成本,并且这些成本主要由语言实现者来承担。
- 内置并发支持,为网络软件带来了简单性,而简单又带来了健壮,这是大型工程软件开发所需要的。
- 增加类型别名,支持大规模代码库的重构。
(2)标准库
Go被称为“自带电池”(battery-included)的编程语言。“自带电池”原指购买了电子设备后,在包装盒中包含了电池,电子设备可以开箱即用,无须再单独购买电池。如果说一门编程语言“自带电池”,则说明这门语言标准库功能丰富,多数功能无须依赖第三方包或库,Go语言恰是这类编程语言。由于诞生年代较晚,且目标较为明确,Go在标准库中提供了各类高质量且性能优良的功能包,其中的net/http、crypto/xx、encoding/xx等包充分迎合了云原生时代关于API/RPC Web服务的构建需求。Go开发者可以直接基于这些包实现满足生产要求的API服务,从而减轻对第三方包或库的依赖,降低工程代码依赖管理的复杂性,也降低开发人员学习第三方库的心智负担。
仅使用标准库来构建系统,这对于开发人员是很有吸引力的。在很多关于选用何种Go Web开发框架的调查中,选择标准库的依然占大多数,这也是Go社区显著区别于其他编程语言社区的一点。Go团队还在golang.org/x路径下提供了暂未放入标准库的扩展库/补充库供广大Gopher使用,包括text、net、crypto等。这些库的质量也是非常高的,标准库中部分包也将golang.org/x下的text、net和crypto包作为依赖包放在标准库的vendor目录中。
(3)工具链
开发人员在做工程的过程中需要使用工具。而Go语言提供了十分全面、贴心的编程语言官方工具链,涵盖了编译、编辑、依赖获取、调试、测试、文档、性能剖析等的方方面面。
- 构建和运行:go build/go run
- 依赖包查看与获取:go list/go get/go mod xx
- 编辑辅助格式化:go fmt/gofmt
- 文档查看:go doc/godoc
- 单元测试/基准测试/测试覆盖率:go test
- 代码静态分析:go vet
- 性能剖析与跟踪结果查看:go tool pprof/go tool trace
- 升级到新Go版本API的辅助工具:go tool fix
- 报告Go语言bug:go bug
值得重点提及的是gofmt统一了Go语言的编码风格,在其他语言开发者还在为代码风格争论不休的时候,Go开发者可以更加专注于领域业务。同时,相同的代码风格让以往困扰开发者的代码阅读、理解和评审工作变得容易了很多,至少Go开发者再也不会有那种因代码风格的不同而产生的陌生感。
在提供丰富的工具链的同时,Go语言的语法、包依赖系统以及命名惯例的设计也让针对Go的工具更容易编写,并且Go在标准库中提供了官方的词法分析器、语法解析器和类型检查器相关包,开发者可以基于这些包快速构建并扩展Go工具链。
可以说Go构建了一个开放的工具链生态系统,它鼓励社区和开发人员为Go添加更多、更实用的工具,而更多、更实用的工具反过来又帮助Go更好地解决工程上的“规模化”问题,这是一个良性的生态循环。
小结
简单是Go语言贯穿语言设计和应用的主旨设计哲学。德国建筑大师路德维希·密斯·凡德罗将“少即是多”这一哲学理念应用到建筑设计当中后取得了非凡的成功,而Go语言则是这一哲学在编程语言领域为数不多的践行者。“少”绝不是目的,“多”才是其内涵。Go在语言层面的简单让Go收获了不逊于C++/Java等的表现力的同时,还获得了更好的可读性、更高的开发效率等在软件工程领域更为重要的元素。
“高内聚、低耦合”是软件开发领域亘古不变的管理复杂性的准则。Go在语言设计层面也将这一准则发挥到极致。Go崇尚通过组合的方式将正交的语法元素组织在一起来形成应用程序骨架,接口就是在这一哲学下诞生的语言精华。不同于C、C++、Java等诞生于20世纪后段的面向单机的编程语言,Go语言是面向未来的。Go设计者对硬件发展趋势做出了敏锐且准确的判断——多核时代是未来主流趋势,于是将并发作为语言的“一等公民”,提供了内置于语言中的简单并发原语——go(goroutine)、channel和select,大幅降低了开发人员在云计算多核时代编写大规模并发网络服务程序时的心智负担。
Go生来就肩负着解决面向软件工程领域问题的使命,我们看到的开箱即用的标准库、语言自带原生工具链以及开放的工具链生态的建立都是这一使命落地的结果,Go在面向工程领域的探索也引领着编程语言未来发展的潮流。
第4条 使用Go语言原生编程思维来写Go代码
4.1 语言与思维——来自大师的观点
在人类自然语言学界有一个很著名的假说——“萨丕尔-沃夫假说”,这个假说的内容是这样的:
“语言影响或决定人类的思维方式。”
在编程语言界,有位大师级人物也有着与“萨丕尔-沃夫假说”异曲同工的观点和认知,他就是首届图灵奖得主、著名计算机科学家艾伦·佩利(Alan J. Perlis),他从另外一个角度提出:
“不能影响到你的编程思维方式的编程语言不值得学习和使用。”
4.3 Go语言原生编程思维
编程语言影响编程思维,或者说每种编程语言都有属于自己的原生编程思维。
Go语言诞生较晚,大多数Gopher(包括笔者在内)的第一语言并不是Go,而是“半路出家”从其他语言(如C、C++、Java、Python等)转过来的。每种语言都有自己的原生编程思维。比如:
- C语言相信程序员,提供了指针和指针运算,让C程序员天马行空地发挥,接近底层的直接内存操作让C程序拥有很高的性能;
- C++支持多范式(命令式、OO和泛型),虽不强迫程序员使用某个特定的范式,但推荐使用最新代表现代语言发展特色的泛型等高级范式;
- Python语言更是形成了Pythonic规则来指导Python程序员写出符合Python思维或惯用法的代码。
经验告诉我们,但凡属于某个编程语言的高质量范畴的代码,其必定是在这种编程语言原生思维下编写的代码。如果用A语言的思维去编写B语言的代码(比如用OO思维写C代码,用命令式的思维写Haskell代码等),那么你写出的代码多半无法被B语言社区所认可,更难以成为高质量代码的典范。并且,如果沿着这样的方向去学习和实践B语言,那么结果只能是南辕北辙,与编写出高质量代码的目标渐行渐远。
那么Go原生编程思维究竟是什么呢?一门编程语言的编程思维是由语言设计者、语言实现团队、语言社区、语言使用者在长期的演进和实践中形成的一种统一的思维习惯、行为方式、代码惯用法和风格。Go语言从诞生到现在已经有十年多了。经过Go设计哲学熏陶、Go开发团队的引导和教育、Go社区的实践,Go语言渐渐形成属于自己的原生编程思维,或者说形成符合Go语言哲学的Go语言惯用法(idiomatic go)。它们是Go语言的精华,也是构建本书内容的骨架,并值得我们用一本书的篇幅去详细呈现。因此可以说,阅读本书的过程也是学习和建立Go语言原生编程思维的过程。
我们的目标是编写出高质量的Go代码,这就需要我们在学习语言的同时,不断学习Go语言原生的编程思维,时刻用Go编程思维考虑Go代码的设计和实现,这是通往高质量Go代码的必经之路。
小结
人类在通过自然语言交流和表达观点的漫长过程中,逐渐形成了固定的语言表述方法。除此之外,人类还利用肢体动作、眼神、表情、纸笔等辅助行为或工具来帮助语言的精确表达,并且在使用这些辅助行为和工具时形成了固定的使用方法,这些以这门语言为中心的固定的表述方法、辅助行为和工具用法总称为这门语言的惯用法,它们反映的就是该语言的思维方式。
编程语言也类似,以一门编程语言为中心的,以解决工程问题为目标的编程语言用法、辅助库、工具的固定使用方法称为该门编程语言的原生编程思维。图4-3是自然语言思维与编程语言思维的对比。
我们学习和使用一门编程语言,目标是用这门语言的原生思维方式编写高质量代码。学习Go,就要用Go的原生编程思维而不是用其他语言的思维方式写Go代码。掌握Go原生编程思维就是我们通往高质量Go编程的学习方向和必经之路。