一、GO:学起来简单,但很难掌握
本章涵盖
- 是什么让 Go 成为一门高效、可扩展和多产的语言
- 探究为什么GO简单易学却难精通
- 展示开发人员常见的错误类型
犯错是每个人生活的一部分。正如阿尔伯特·爱因斯坦曾经说过的,
一个从未犯过错误的人也从未尝试过新事物。
最终重要的不是我们犯了多少错误,而是我们从错误中学习的能力。这个断言也适用于编程。我们在一门语言中获得的资历并不是一个神奇的过程;它包括犯许多错误,并从中吸取教训。这本书的目的就是围绕这个想法。它将帮助你,读者,成为一个更熟练的 Go 开发者,通过观察和学习人们在语言的许多领域中犯的 100 个常见错误。
这一章快速回顾了为什么GO这么多年来成为主流。我们将讨论为什么尽管GO被认为简单易学,但掌握它的细微差别却很有挑战性。最后,我们将介绍本书涵盖的概念。
1.1 Go 大纲
如果你正在读这本书,很可能你已经爱上了 Go。因此,本节提供了一个简短的提示,是什么让 Go 成为如此强大的语言。
在过去的几十年里,软件工程有了长足的发展。大多数现代系统不再是由一个人编写的,而是由多个程序员组成的团队编写的——有时甚至是数百人,如果不是数千人的话。如今,代码必须具有可读性、表达性和可维护性,以保证系统的持久性。同时,在我们这个快速发展的世界中,最大限度地提高灵活性和缩短上市时间对于大多数组织来说至关重要。编程也应该遵循这一趋势,公司努力确保软件工程师在阅读、编写和维护代码时尽可能地高效。
为了应对这些挑战,谷歌在 2007 年创建了 Go 编程语言。从那时起,许多组织已经采用这种语言来支持各种用例:API、自动化、数据库、CLI(命令行界面)等等。今天许多人认为 Go 是云的语言。
就特性而言,Go 没有类型继承、没有异常、没有宏、没有部分函数、不支持惰性变量求值或不变性、没有运算符重载、没有模式匹配等等。为什么语言中缺少这些特性?官方的 Go FAQ (go.dev/doc/faq
)给了我们一些启示:
为什么 Go 没有特征 X?您最喜欢的功能可能会丢失,因为它不合适,因为它影响编译速度或设计的清晰度,或者因为它会使基本的系统模型太难。
通过特性的数量来判断编程语言的质量可能不是一个准确的标准。至少,这不是GO的目标。相反,当组织大规模采用一种语言时,Go 利用了一些基本特征。其中包括以下内容:
稳定性——虽然 Go 经常更新(包括改进和安全补丁),但它仍然是一种稳定的语言。有些人甚至认为这是这门语言最好的特性之一。
表现性——我们可以通过我们如何自然和直观地编写和读取代码来定义编程语言中的表现性。数量减少的关键字和解决常见问题的有限方法使 Go 成为大型代码库的一种表达性语言。
编译——作为开发人员,还有什么比等待构建来测试我们的应用更让人恼火的呢?快速编译一直是语言设计者有意识的目标。这反过来又提高了生产率。
安全 ——Go 是一种强大的静态类型语言。因此,它有严格的编译时规则,确保代码在大多数情况下是类型安全的。
Go 是从底层开始构建的,具有可靠的特性,比如具有 goroutines 和通道的出色的并发原语。不太需要依赖外部库来构建高效的并发应用。观察并发性在这些日子里是多么重要,也证明了为什么 Go 对于现在和可预见的将来都是如此合适的语言。
一些人也认为 Go 是一种简单的语言。从某种意义上说,这并不一定是错的。例如,一个新手可以在不到一天的时间里学会这门语言的主要特征。那么,如果GO很简单,为什么要读一本以错误概念为中心的书呢?
1.2 简单并不意味着容易
简单和容易是有细微差别的。简单,应用于一项技术,意思是学习或理解起来不复杂。然而,容易意味着我们不需要太多努力就可以实现任何事情。GO学起来简单,但不一定容易掌握。
让我们以并发性为例。2019 年,一项专注于并发 bug 的研究发表了:“理解 Go 中真实世界的并发 bug。¹” 这项研究是首次对并发 bug 的系统分析。它关注多个流行的 Go 存储库,比如 Docker、gRPC 和 Kubernetes。这项研究中最重要的一点是,大多数阻塞错误都是由通过通道的消息传递范式的不正确使用引起的,尽管人们认为消息传递比共享内存更容易处理,更不容易出错。
对于这样的外卖,应该有什么合适的反应?我们应该认为语言设计者在消息传递方面是错误的吗?我们是否应该重新考虑如何处理项目中的并发性?当然不是。
这不是一个对抗信息传递和共享内存并决定谁是赢家的问题。然而,作为 Go 开发人员,我们需要彻底了解如何使用并发性,它对现代处理器的影响,何时支持一种方法,以及如何避免常见的陷阱。这个例子强调了虽然像通道和 goroutines 这样的概念很容易学习,但在实践中却不是一个容易的话题。
这个主题——简单并不意味着容易——可以推广到 Go 的许多方面,而不仅仅是并发性。因此,要成为精通GO的开发者,我们必须对这门语言的许多方面有透彻的理解,这需要时间、精力和错误。
这本书旨在通过深入研究 100 个 Go 错误来帮助我们加速迈向熟练的旅程。
1.3 100 个 Go 错误
我们为什么要读一本关于常见GO错误的书?为什么不用一本挖掘不同主题的普通书来加深我们的知识呢?
在 2011 年的一篇文章中,神经科学家证明了大脑生长的最佳时间是我们面临错误的时候。我们都经历过从一个错误中学习的过程,并且在几个月甚至几年后回忆起那个事件,当一些背景与它相关时?正如珍妮特·梅特卡夫(Janet Metcalfe)在另一篇文章中介绍的那样,这种情况的发生是因为错误具有促进效应。主要意思是我们不仅能记住错误,还能记住错误周围的上下文。这是从错误中学习如此高效的原因之一。
为了加强这种促进作用,本书尽可能多地用真实世界的例子来说明每个错误。这本书不仅仅是关于理论;它还帮助我们更好地避免错误,做出更明智、更有意识的决策,因为我们现在理解了它们背后的基本原理。
告诉我,我会忘记。教我,我会记住。让我参与进来,我会学到东西。
——未知
这本书提出了七大类错误。总的来说,这些错误可以归类为
BUG
不必要的复杂
可读性较弱
次优或不完善的组织
缺乏 API 便利性
优化不足的代码
缺乏生产力
接下来我们介绍每一个错误类别。
1.3.1 错误
第一种错误可能也是最明显的错误是软件错误。2020 年,Synopsys 进行的一项研究估计,仅在美国,软件错误的成本就超过 2 万亿美元⁴。
此外,错误还会导致悲剧性的影响。例如,我们可以提到加拿大原子能有限公司(AECL)生产的 Therac-25 放射治疗机。由于比赛条件,这台机器给病人的辐射剂量超过预期数百倍,导致三名病人死亡。因此,软件错误不仅仅是钱的问题。作为开发人员,我们应该记住我们的工作是多么有影响力。
这本书涵盖了大量可能导致各种软件错误的案例,包括数据竞争、泄漏、逻辑错误和其他缺陷。虽然准确的测试应该是尽早发现这类 bug 的一种方式,但我们有时可能会因为时间限制或复杂性等不同因素而错过案例。因此,作为一名 Go 开发者,确保我们避免常见的错误是至关重要的。
1.3.2 不必要的复杂性
下一类错误与不必要的复杂性有关。软件复杂性的一个重要部分来自于这样一个事实,即作为开发人员,我们努力思考想象中的未来。与其现在就解决具体的问题,不如构建进化的软件来解决未来出现的任何用例。然而,在大多数情况下,这样做弊大于利,因为这会使代码库变得更加复杂,难以理解和推理。
回到过去,我们可以想到许多用例,在这些用例中,开发人员可能倾向于为未来需求设计抽象,比如接口或泛型。这本书讨论了我们应该小心不要用不必要的复杂性伤害代码库的主题。
1.3.3 可读性较弱
另一种错误是削弱可读性。正如 Robert C. Martin 在他的书《Clean Code:A Handbook of Agile Software crafts》中所写的,花在阅读和写作上的时间比远远超过 10 比 1。我们大多数人开始在可读性不那么重要的单独项目上编程。然而,今天的软件工程是有时间维度的编程:确保我们在几个月、几年,甚至几十年后仍然可以使用和维护应用。
在用 Go 编程时,我们可能会犯很多会损害可读性的错误。这些错误可能包括嵌套代码、数据类型表示,或者在某些情况下没有使用命名结果参数。通过这本书,我们将学习如何编写可读的代码,并关心未来的读者(包括我们未来的自己)。
1.3.4 次优或不适应的组织
无论是在进行一个新项目时,还是因为我们获得了不准确的反应,另一种错误是次优地和单向地组织我们的代码和项目。这样的问题会使项目更难推理和维护。这本书涵盖了GO中的一些常见错误。例如,我们将了解如何构建一个项目,以及如何处理实用工具包或init
函数。总之,查看这些错误应该有助于我们更有效、更习惯地组织我们的代码和项目。
1.3.5 缺乏 API 便利性
另一种类型的错误是犯一些削弱 API 对客户的便利性的常见错误。如果一个 API 不是用户友好的,它将缺乏表现力,因此更难理解,更容易出错。
我们可以考虑许多情况,比如过度使用any
类型,使用错误的创建模式来处理选项,或者盲目应用影响我们 API 可用性的面向对象编程的标准实践。这本书涵盖了一些常见的错误,这些错误阻止我们向用户公开方便的 API。
1.3.6 优化不足的代码
优化不足的代码是开发人员犯的另一种错误。这可能是由于各种原因造成的,比如不理解语言特征,甚至缺乏基础知识。性能是这个错误最明显的影响之一,但不是唯一的。
我们可以考虑为其他目标优化代码,比如准确性。例如,这本书提供了一些确保浮点运算准确的常用技术。与此同时,我们将讨论大量可能对性能代码产生负面影响的情况,例如,由于并行化执行不佳,不知道如何减少分配,或者数据对齐的影响。我们将通过不同的棱镜解决优化问题。
1.3.7 缺乏生产力
在大多数情况下,当我们着手一个新项目时,我们能选择的最佳语言是什么?我们工作效率最高的一个。熟悉一门语言的工作方式并充分利用它是达到熟练的关键。
在本书中,我们将介绍许多案例和具体的例子,这些案例和例子将帮助我们在 Go 中工作时更有效率。例如,我们将着眼于编写高效的测试来确保我们的代码工作,依靠标准库来提高效率,并充分利用分析工具和 linters。现在,是时候深入研究这 100 个常见的GO错误了。
总结
Go 是一种现代编程语言,能够提高开发人员的工作效率,这对于当今大多数公司来说至关重要。
GO学起来简单,但不容易掌握。这就是为什么我们需要加深我们的知识来最有效地使用语言。
通过错误和具体的例子来学习是精通一门语言的有效方法。这本书将通过探究 100 个常见错误来加快我们的熟练程度。
¹ T. Tu,X. Liu 等,“理解 Go 中真实世界的并发 bug”,发表于 2019 年 4 月 13 日-17 日的 ASPLOS 2019。
J. S. Moser,H. S. Schroder 等人,“注意你的错误:将成长心态与适应性后错误调整联系起来的神经机制的证据”,《心理科学》,第 22 卷,第 12 期,第 1484-1489 页,2011 年 12 月。
³ J. Metcalfe,“从错误中学习”,《心理学年度评论》,第 68 卷,第 465–489 页,2017 年 1 月。
⁴ Synopsys,“美国软件质量差的代价:2020 年报告。”2020. news.synopsys.com/2021-01-06-Synopsys-Sponsored-CISQ-Research-Estimates-Cost-of-Poor-Software-Quality-in-the-US-2-08-Trillion-in-2020
。
R. C. Martin,《干净的代码:敏捷软件工艺手册》。普伦蒂斯霍尔,2008 年。