第四条原则: 面向工程,“自带电池”
大规模软件开发存在的各种问题:
- 程序构建慢
- 失控的依赖管理
- 开发人员使用编程语言的不同子集(比如 C++支持多范式,这样有些人用 OO,有些人用泛型)
- 代码可理解性差(代码可读性差,文档差等)
- 功能重复实现
- 升级更新消耗大
- 实现自动化工具难度高
- 版本问题
- 跨语言构建问题
很多编程语言设计者或其拥趸认为这些问题并不是一门编程语言应该去解决的,但 Go 语言的设计者并不这么看,他们以更高更广阔的视角去审视软件开发领域尤其是大规模软件开发过程中遇到的各种问题,并在 Go 语言最初设计阶段就将解决工程问题作为 Go 的设计原则之一去考虑 Go 语法、工具与标准库的设计,这也是 Go 与其他偏学院派、偏研究性编程语言在设计思路上的一个重大差异。
Go 的目标就是为了让开发者能够更容易、更高效地构建规模化(scale)的软件。
- 用 Go 构建的软件系统的并发规模,比如:这类系统并发关注点的数量、处理数据的量级、同时与之交互的服务的数量等;
- 开发过程的规模,包括代码库大小、参与开发、相互协作的工程师的数量等。
Go 是如何解决工程领域规模化所带来的问题的呢?
语言
- 重新设计编译单元和目标文件格式,实现 Go 源码快速构建,让大工程的构建时间缩短到类似 Python 的交互式编译的编译速度;
- 如果源文件导入它不使用的包,则程序将无法编译。这可以充分保证任何 Go 程序的依赖树是精确的。这也可以保证在构建程序时不会编译额外的代码,从而最大限度地缩短编译时间;
- 去除包的循环依赖,循环依赖会在大规模的代码中引发问题,因为它们要求编译器同时处理更大的源文件集,这会减慢增量构建;
- 在处理依赖关系时,有时会通过允许一部分重复代码来避免引入较多依赖关系。比如:net 包具有其自己的整数到十进制转换实现,以避免依赖于较大且依赖性较大的格式化 io 包;
- 包路径是唯一的,而包名不必唯一的。导入路径必须唯一标识要导入的包,而名称只是包的使用者如何引用其内容的约定。包名称不必是唯一的约定大大降低了开发人员给包起唯一名字的心智负担;
- 故意不支持默认函数参数。因为在规模工程中,很多开发者利用默认函数参数机制,向函数添加过多的参数以弥补函数 API 的设计缺陷,这会导致函数拥有太多的参数,降低清晰度和可读性;
- 首字母大小写定义标识符可见性,这是 Go 的一个创新。它让开发人员通过名称即可知晓其可见性,而无需回到标识符定义的位置查找并确定其可见性,这提升了开发人员阅读代码的效率;
- 在语义层面,相对于 C,Go 做了很多改动,提升了语言的健壮性,比如:去除指针算术、去除隐式的数字转型等;
- 内置垃圾收集,这对于大型工程项目来说,大大降低了程序员在内存管理方面的负担,程序员使用 GC 感受到的好处超过了付出的成本,并且这些成本主要由语言实现者来承担;
- 内置并发支持,为网络软件带来了简单性,简单又带来了健壮,这是大型工程软件开发所需要的;
- 增加 type alias,支持大规模代码库的重构。
标准库
Go 被称为“内置电池(battery-included)”的编程语言。“内置电池”原指购买了电子设备后,在包装盒中包含了电池,电子设备可以开箱即用,无需再次出去购买电池。如果说一门编程语言是“自带电池”,则说明这门语言标准库功能丰富,多数功能无需依赖外部的第三方包或库,Go 语言恰是这类编程语言。
工具
开发人员在工程过程中需要使用工具。而 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
在提供丰富的工具链的同时,Go 语言的语法、package 系统以及命名惯例的设计也让针对 Go 的工具更容易编写,并且 Go 在标准库中提供了官方的词法分析器、语法解析器和类型检查器相关 package,开发者可以基于这些包快速构建 Go 工具。
