结束语:Go 去向何方?
读到这里,读者已经跟随本书走过了 Go 的全景与历史、语言特性、并发、内存、编译器与工具链 五大部分。作为收尾,我们不妨跳出具体的实现细节,谈一谈 Go 留下的遗产、眼下面临的挑战, 以及它可能的去向。
Go 留下了什么
回望全书,Go 最深的一笔遗产,或许不是某一项具体技术,而是它反复示范的一种设计姿态:
把复杂度安置在最不打扰用户的地方,让简单成为结果而非起点。这条主线在书中处处显形,调度器
把线程管理的复杂度(9)藏进运行时,只给用户一个
go 关键字;垃圾回收(13)把亚毫秒停顿的全部精巧留在内部,
让用户几乎无需调参;泛型(8)等了十三年,只为找到一个
不牺牲编译速度与简单性的实现;模块系统(17)用最小
版本选择这一最简算法,换掉了业界惯用的约束求解器。每一处都在回答同一个问题:一项复杂度,
凭什么值得被引入?
正是这种克制,让 Go 在诞生十余年后,依然能让用户写出向后兼容的程序,依然以「简单、工程 友好」立足于工业界。它证明了一件事:一门成功的语言,靠的未必是最强的表达力,而可能是最 清醒的取舍。
它面临的挑战
Go 并非没有代价。书中也一再点出它取舍的另一面:非移动式 GC 带来的内存碎片要靠尺寸类去兜
(12.1);满屏的 if err != nil
(7)是「错误即值」的必然账单,而把它写短的种种语法尝试
至今未被接纳(7.5);泛型的字典间接在热路径上有时反而更慢
(8.4);cgo 的跨界开销让 Go 与 C 世界的互通远谈不上廉价
(15.6)。这些都不是 bug,而是设计取舍刻下的边界,
理解它们,才算真正理解了 Go。
它可能的去向
Go 的演化仍在继续,而且大多遵循同一条节奏,先发布最小可用的一版,在真实使用中观察需求, 再审慎扩充。书中已经能看到这条线的延伸:垃圾回收正朝着更好的缓存局部性演进(Green Tea GC, 13.11);编译器把优化从静态猜测推向数据驱动(PGO, 15.3);泛型的运行时开销在被持续打磨;可观测性 (16)与结构化诊断仍在补全。可以预见的是,无论加入什么, Go 大概率都不会拿编译速度、向后兼容与「读一行代码就能看懂」这几条底线去交换。它的未来, 多半不是某个特性大爆发的版本,而是这种小步慢走的延续。
笔者结语
编写本书的难度非常高,这个难度一方面来源于源码自身发展过程带来的复杂性,在编写本书的过程中代码本身也在发展。 本书最初写于 Go 1.10,为了让保证内容能既能跟上代码进化的速度,又要保证编写的质量, 则必须在编写的过程中时刻关注那些最基本的、最通用的和最经典而长存的设计。
Go 语言源码的研究不仅仅局限于跟用户态代码直接相关的运行时的代码,这只是 Go 源码的一小部分。 完整理解 Go 的源码不仅需要对各方面系统理论的理解,还需要把控 Go 自身编译器与运行时之间配合, 这基本上就决定了本书无论是编写起点,还是阅读起点很高; 这种复杂性还来源于各个标准库与运行时模块之间互相耦合, 由于 Go 在传统程序流程之上建立了一层运行时机制,进而会导致很多传统意义上的标准库设计变得非常的精巧。 除非某个标准库或者某个组件是纯粹的不依赖底层实现的逻辑代码,否则很少有内容能够独立成章不与其他章节产生连接, 也就为线性呈现知识的写作过程提高了难度。 除了运行时和编译器之外,书中讨论的标准库依然需要笔者在编写之前就需要对各个模块间的耦合关系相当了解, 同时也要求读者在阅读过程中时刻在脑海中勾勒这些耦合关系,也就进一步提高了阅读本书的门槛。
本书从 2018 年年末至今前后编写大约两年多的时间,从最初的一个关于运行时调度器的源码分析, 发展到运行时代码的分析,直到现在的甚至包含了编译器、链接器以及工具链的对整个 Go 源码进行的分析。 总之本书在编写过程中对本书组织结构作出的改动非常之多,有兴趣的读者也可翻看本书的提交历史。
好在目前已经克服了这个困难。回顾这个过程,期间伴随着诸多想要放弃的念头, 其中一个主要的原因就是内容过于复杂,完成全书编写产生的投资回报比极低, 但如今还是坚持了下来,这少不了关注本书编写的热心读者的支持。
最后,笔者希望读者已经从本书收获了自己期望的知识,并深化了自身对 Go 这一语言的深入理解, 理解到编写一门成功的编程语言不仅仅需要的是系统而复杂的工程与优化知识, 更需要的是对问题持续不断的思考、讨论、实践与重新设计这一工程闭环。 源码的研究虽然已经结束,但要将研究中获得的知识进一步运用到生产实践中构建更多的价值, 这又将是一个全新的起点。