有一天在工作中,我们在工作聊天室里讨论 Go 编程语言。有一次,我对一位同事的幻灯片发表了评论,大概是说
“我认为这就像成为 Go 程序员的七个阶段中的第三阶段。”
很自然地,我的同事们想知道其余的阶段,所以我简要地概述了它们。这里,扩展了更多背景,是成为 Go 程序员的七个阶段;看看你是否能在这条道路上看到自己。
第一阶段:你相信你可以让 Go 进行面向对象编程
在您初步运行 A Tour of Go 后,您开始思考“现在,我如何才能让这种语言更像面向对象的语言……?” 毕竟,您已经习惯了那些东西。您想要编写健壮的代码。您想要多态性。
“肯定有办法!” 你说,然后你找到了 结构体嵌入。它允许您巧妙地将方法从封闭对象委托给嵌入对象,而无需复制代码。太棒了!
当然,这不是真的。结构体嵌入仅允许您委托方法调用。即使它看起来像您正在进行多态方法分派,但关系不是 IS-A。它是 HAS-A,因此方法调用的接收者不是封闭对象:接收者始终是将方法调用委托给的嵌入对象。
你不在 Go 中进行面向对象编程。句号。
第二阶段:你相信 goroutine 会解决你所有的问题
您被 Go 吸引是因为它承诺让您轻松运行并发代码,这确实通过 goroutine 做到了!您只需要使用 go 关键字,就可以让几乎任何函数或方法调用并发运行。因此,很自然地,您希望通过尽可能多地并行运行代码来最大化代码的效率。并且因为您通过使函数调用自动创建 goroutine 来隐藏了这个事实,所以调用者甚至不需要意识到这一点。
是的,所以它可能会使你的代码更复杂一些,但看,现在一切都并发运行了!
Go 允许你创建数百万个 goroutine 而不会牺牲太多效率,但你不应该仅仅因为你可以就使用 goroutine。并发代码是比在单线程中流动的代码更难维护和调试。我的意思是,你有没有认真考虑过当从多个 goroutine 同时访问时,你的共享对象是否真的正确同步了?你确定执行顺序绝对正确吗?你真的检查过这些 goroutine 在不再需要时是否真的退出了吗?
Goroutine 最好的用途是在它们必要时才使用,除非你的要求指示你必须在内存中完成所有事情或诸如此类的事情,否则你永远不应该放弃使用良好的多进程模型。
最后,尽量不要在用户背后生成 goroutine,特别是如果你正在编写库。显式调用 go 调用通常会给用户更多的灵活性和权力。
Goroutine 只能带你走这么远。仅在真正有意义时才使用它们。
第三阶段:你相信接口会解决你所有的问题,而不是面向对象编程
在对你无法使对象以多态方式行为感到失望之后,你突然意识到 接口提供的功能。接口允许你描述 API;肯定有办法使用它来编写更健壮的代码。
所以现在当你编写库时,你为所有东西定义接口。你只导出接口并拥有私有结构体,这样封装就非常完美了。它还应该给你在切换底层实现时提供更大的灵活性,因为现在你已经成功地将 API 与其实现解耦了。
接口确实给你带来了很多权力,但它不是万能的解决方案。它仍然没有提供面向对象编程意义上的真正的多态性。你也受到接口只能定义 API,并且你不能将任何数据与之关联的事实限制。
此外,虽然在某些情况下,仅导出接口而不是具体结构体是有道理的,但这真的不应该是你的默认操作模式。接口最好是小型的(而不是描述为对象定义的整个方法列表)。此外,如果你不小心,你将不得不编写大量的额外代码来满足接口,或者编写需要大量类型断言的代码。
为了最大程度地利用接口,你只应在想要使某些类型可互换时才使用它们。
第四阶段:你相信通道会解决你所有的问题
在你花费大量时间思考如何使 Go 按照你的方式工作之后,你现在正在寻找使一切按照你的方式工作的缺失部分。“等等,还有 通道!”
通道隐式地正确处理并发访问。你相信你应该能够通过巧妙地使用通道来处理同步、返回值(类似于 future/promise)以及使用带有各种通道的 select 语句进行流程控制,从而解决迄今为止的许多障碍。
同样,通道非常有用,但它们的用途仅与其最初的目的相同,即提供一个在 goroutine 之间传递值的原语。
我相信你会发现许多使用通道的 Go 惯用语:用于超时、阻塞 I/O、同步技巧等。但同样,因为通道是并发构造,滥用它们会导致更复杂、难以调试的代码。
第五阶段:你现在相信 Go 没有人们声称的那么强大
“为什么?!为什么编写 Go 代码如此痛苦?它不允许我按照我一直以来的方式编写代码。”
你感到沮丧。没有多态性。并发很难。通道不能解决你的问题。你甚至不明白 Go 存在的意义。你觉得你已经被剥夺了其他语言提供的所有好用的工具和构造。
你认为更强大的工具来表达抽象思想是绝对必要的。Go 就是不行。
Go 非常固执己见。我来自 Perl 背景,有一段时间我简直不敢相信 Go 有多么受限制。所以,是的,我理解你感到沮丧。
但这是因为语言真的有限制,还是因为你试图让语言按照你认为应该的方式工作,而没有考虑语言作者希望你做什么?
第六阶段:你意识到第一到第五阶段都只是你的想象
在某个时候,你勉强决定 按照大多数标准库的编写方式 编写 Go 代码。你也放弃了尝试变得聪明,并开始编写直截了当的代码。
然后你就明白了:你只是不想接受 Go 的方式。
一切都开始变得有意义。
认真地说,学习 Go 确实需要一些反学习。我不得不稍微反学习面向对象编程,并接受这样一个事实,即无论语言给你多少有用的工具,编写并发代码对于凡人来说都太难了。我也不得不反学习使用异常。
我没有与 Go 的作者核实,所以这只是我个人的看法,但我认为该语言的重点是让开发者更难编写复杂的代码。它给你足够的工具来编写执行复杂任务的代码,但通过取消某些关键工具,你最终编写的代码更简单,更难搞砸。
一旦我决定接受这些功能和构造的现状,编写 Go 代码就变得容易得多,而且肯定更有趣了。
第七阶段:你现在心平气和
你已经接受了 Go 的方式。你现在用 Go 编写所有东西,包括你通常会用 Perl/Ruby/Python 编写的东西。你意识到 if err != nil 不再困扰你。你只在必须时才使用 goroutine 和通道。
你与 Gopher 合二为一。 你感受到它光荣的 chi,当你意识到它的仁慈允许你用如此庄严的语言编写代码时,你会哭泣。
恭喜你。现在你是一名 Go 程序员了。
即使这听起来有点半开玩笑,但这些确实是我在适应 Go 时感受或经历过的实际问题。也许你同意,也许你不同意,但第六阶段对我来说实际上就是那样。我最终放弃了试图让 Go 按照我想要的方式工作,并决定按照 Go 告诉我的方式 编写。这听起来很傻,但从那以后,事情真的开始变得有意义了。
希望新的 Gopher 少花时间思考如何弯曲语言并感到沮丧。祝你编程愉快!
8 条评论