Dave Cheney: Go语言之禅



本篇内容是根据2020年3月份The Zen of Go音频录制内容的整理与翻译,

Dave Cheney 讲述了 Go 之禅(编写简单、可读、可维护的 Go 代码的十个工程价值)。是什么让 Go 代码变得优秀?编写 Go 代码时,我们应该牢记哪些指导原则?

过程中为符合中文惯用表达有适当删改, 版权归原作者所有.


Mat Ryer:你好,欢迎收听《Go Time》。我是主持人 Mat Ryer。今天我们要聊的话题是 Go 的禅意。在今天的节目中,我们一起聊的有 Johnny Boursiquot、Carmen Andoh 和独一无二的 Dave Cheney。大家好!

Carmen Andoh:大家好!

Dave Cheney:朋友们好!

Mat Ryer:Dave,你好吗?

Dave Cheney:我很好。大家在电波的另一端过得怎么样?

Mat Ryer:还不错。Carmen,好像有一阵子没见你上节目了吧,是不是?

Carmen Andoh:是啊,我都想大家了,回到节目真好……

Mat Ryer:是啊,我们也想你了,我正要说这个呢。

Carmen Andoh:Dave 是我的最爱!我可不是为了你回来的,我是为了 Dave 回来的,抱歉啊……

Johnny Boursiquot:哎呦,真扎心。

Mat Ryer:没关系,没关系。[笑声] Johnny,你怎么样,兄弟?

Johnny Boursiquot:我挺好的,没啥大事。

Mat Ryer:不错。最近有什么新鲜事吗?

Johnny Boursiquot:没什么特别的……就是日复一日吧,尽量对每个人都好。不像你,我觉得。

Mat Ryer:哦,那可真是个难事,太难了。

Johnny Boursiquot:从你跟我说的来看,确实不容易。[笑声]

Mat Ryer:这期节目叫《Go 的禅意》,Dave,我想问问你,能不能先给我们讲讲这个标题的含义?它是怎么来的?

Dave Cheney:好的,首先我要归功于很多地方,其中包括 Go Time 节目。上次我来节目时,我们讨论了“try”操作。Peter Bourgon 说的一句话也许对他来说只是随口一说,但对我来说(并非如此),他说“错误处理应该是显式的”,还表示“这是 Go 语言的核心价值之一,我们应该明确地写下来。”这就是其中一个灵感来源。

在讨论“try”操作的过程中,Daniela Petruzalek 提到其他语言都有其核心价值观,她提到了 Python 的禅意(Zen of Python)。这让我思考了大约一年。几个月前,我有幸被邀请去以色列参加第二届 GopherCon 大会,他们告诉我“你有一个小时的演讲时间”,我心想“我有什么能讲一个小时的呢?”

所以这个演讲和这些想法就是从那次机会中诞生的。这些想法并不是我独创的,只是我整理出来的东西。我还得为大家不得不阅读一个小时的文字道歉---那篇文档之所以那么长,是因为给了我一个小时的时间。如果只给我30分钟,可能会短很多。

Mat Ryer:[笑] 这很棒。你还创建了一个迷你网站,上面列出了这些规则或要点……我们该怎么找到它?

Dave Cheney:我们可以把链接放在节目的备注中,网址是 thezenofgo.netlify.com。我做这个网站的主要原因之一,或许有点像是在演讲技巧上的“小招数”。当我给别人演讲反馈时,我总是说“我有一个超级能力,我能控制观众的思维方式。”我做到这一点的方法就是把我想让观众记住的文字用大字写在我身后的屏幕上。所以如果我希望他们记住某个句子的重点,我就把它用百万级的字体显示在背后。

Mat Ryer:Dave,我要记下来。我肯定是把我的演讲弄错了。

Carmen Andoh:是啊,这简直是绝对的“绝地武士心灵控制术”。[笑声]

Dave Cheney:另外,如果你希望人们在 Twitter 上引用你的一些话,并附上照片,那就确保你希望他们重复的文字在你背后显眼地展示出来。

Mat Ryer:哦……!这主意不错。

Carmen Andoh:我试过这么做,但那是 Dave Cheney 的名言,所以照片上是我和我背后的 Dave Cheney 名言……抱歉。

Johnny Boursiquot:[笑]

Mat Ryer:我也有时这么做……不过有时空间不够,我就把引用删掉了…… [笑声]

Carmen Andoh:然后你就自己拿了功劳?

Mat Ryer:嗯……默认情况下就是这样,没办法。这是默认值。

Carmen Andoh:虽然 thezenofgo.netlify.com 是个不错的小网站,但我更喜欢你的博客文章,因为我不介意阅读一小时的文字。我觉得都挺有意思的。

Johnny Boursiquot:是的,博客内容更有背景和上下文。

Carmen Andoh:对。

Dave Cheney:创建这个迷你网站的部分原因是给观众带去一些简洁的内容。搞营销时,总要确保给别人留下点儿有你名字的东西,以便他们回到家时看着桌子上的东西说“哦,我记得这个。”

所以这个小网站就是观众带走的一个小纪念品……但总会有一个矛盾存在,即6,000字的博客文章和那些没有时间读完的人之间的矛盾。所以我觉得提供一些更简洁的东西是必要的。另外,必须向所有的灵感来源致敬,Rob Pike 的 Go 格言(Go Proverbs)也是一个重要的灵感来源。也许我们之后会详细讨论这个……

我有一个观点,就是这些格言更像是高级层面的东西。因为这些格言---无论是 Pike 的原版,还是受 Go 团队启发的版本---都是些诙谐的小观察,它们听起来很有道理,比如“哦,我现在明白了 (某个金句的含义)。”但对于从未接触过 Go 的人来说,真的有用吗?

所以我为每条格言都写了一个简短的解释……因为我的想法是,它们应该是对新手有用的。这里又回到了我对“惯用 Go”(Idiomatic Go)的批评,我觉得这个词有点排他性。我不想让这些格言模糊不清,以至于大家可以围绕它们争论不休。Python 的禅意最初是为了让 Python 社区的人们思考“等一下……这和我写 Python 的方式好像不一样。” [Tim Peters](https://en.wikipedia.org/wiki/Tim_Peters_(software_engineer “Tim Peters”)) 显然是在故意留下一点点“诡计”。

Mat Ryer:你说得对,每个小点的标题都挺诗意的,给人留下了很多解读空间。它们并不是代码,对吧?所以我很欣赏这一点,这也是非常重要的一点。我也见过这种情况,有人做演讲时会跳过一些基础知识,直接讲高阶内容,但那些没有基础的人听完后感觉大不相同。我见过几次这样的情况。所以我觉得这是我们需要注意的。而你网站上的描述肯定有帮助。

Dave Cheney:背景是这样的:在 Go 这样一个不断发展的社区中,大多数成员可能是新加入的。这个结论在数学上是成立的,经验上也是成立的。如果你去参加各种会议,你可以做个调查,问问“谁是第一次来?谁参加过很多次了?”你会发现大多数参加会议或聚会的人都是新手……所以我们应该为新手优化,而不是过度关注那些已经在社区里很久的人。

Mat Ryer:是的,如果必须做出这个选择的话,我觉得这是个不错的决定。因为有经验的人已经具备了更多寻找正确信息的能力,而新手则需要更多的关照。我每次做演讲时,总是想确保新手也能跟上。所以有时---甚至在这个节目里---我听起来像个傻瓜……这不是因为我真的是傻瓜,而是因为我在照顾新手。

Johnny Boursiquot:[笑]

Carmen Andoh:你提到了“新手”,我觉得有一个很美的说法是“初学者心态”,这也是我喜欢你演讲和博客标题的原因。有人可能会问“什么是‘初学者心态’?” 它的意思是 “在初学者的头脑中,选择是多样的;在大师的头脑中,选择是有限的。” 所以有时候 Rob 的那些格言---它们的简洁和机智来自于层层含义……当你真正理解它们时,那种豁然开朗的感觉是非常愉快的,但它也可能让那些还没达到那个层次的人感到被排斥。所以我很高兴你能把它讲得更清楚。

Johnny Boursiquot:我想简要谈一谈你提到的“惯用 Go”是排他性的观点。我对此有不同的看法,我认为这更像是一种追求的目标。当你加入一个社区时,那里总会有一种惯用的编程方式,无论是 Go、Rust 还是 Ruby。你会希望自己写的代码能与大家的风格一致,至少是符合大多数人认为正确的方式。

所以在我看来,写惯用的 Go 代码是一种追求,而不是一种排斥……但我也能理解你为什么会有那种看法。我把 Go 格言和 Go 的禅意对比来看,我觉得 Go 的禅意是你在通往 Go 格言的路上会理解和领会的东西。当你真正理解了 Go 格言时,你会说“哦,现在一切都豁然开朗了”,因为你已经使用 Go 一段时间了,能够理解格言中的多层次含义。而 Go 的禅意则更直接,是对你如何进行 Go 编程的一种解释。

所以我认为写惯用的 Go 代码是一种追求,应该是每个人努力的目标……而不应该成为排斥那些还不知道正确做法的人们的借口。

Dave Cheney:是的,我觉得你触及了我对“惯用 Go”这个概念的核心不适。并不是说我们不应该遵循惯用的编程风格,而是“惯用”这个词本身让我感到不安。我查了字典,发现“idiom”(惯用语)的定义是“我们在这里的做事方式”,一种被接受的标准。而任何一个有这种“我们就是这么做事儿”的文化,都可能产生群体思维---“我们这么写代码是因为我们就是这么写的。”

我觉得“惯用 Go”之所以具有排他性,不是因为我们不该写出符合惯用风格的好代码,而是这种风格是如何被推广的。C++ 作家 Scott Meyers 写了一篇非常棒的博客,讲述他如何撰写《Effective C++》书籍。他在文中打破了第四面墙,详细介绍了他写书的过程。我从中学到的最重要的一点是---首先,要避免绝对化的语言。比如,你不应该说“永远”和“绝不”,而应该说“倾向于”或“避免”。所以,首先要远离绝对化的语言,这是很重要的。

其次,我观察到,惯用 Go 这个词大多数情况下都是与否定词连用的。通常是“这不符合惯用风格”、“你做错了”。这正是我对惯用 Go 作为教学工具的担忧。

Carmen Andoh:很有道理。

Dave Cheney:这看起来合理吗?比如当你看到惯用 Go 被用来作为一种正当理由或目标时……我通常发现,它总是伴随含义暗示着某人的做法不符合惯用风格,不属于这个“部落”,没有遵循被接受的标准。他们做错了。

Mat Ryer:是的,这种批评中好像隐含着一种“幼稚”的意味,对吧?

Dave Cheney:完全正确。

Mat Ryer:……而且我们其实不需要这样做。你可以用其他方式传递同样的信息,不必诉诸那种表达…… 这也是有道理的。我发现,有时跟随他人的模式是有用的,即使我不完全理解这些模式存在的原因…… 然后我稍后会逐渐学到一些东西。我也经历过这种情况,你明白我的意思吗?有时候,如果你期望每个人立刻学会所有东西---那实在是太多要学的了。有时我们会说“哦,这很简单”,但那是个错误。这些事情一点也不简单。事实上,如果它真的那么简单,可能我们也不会感兴趣。

所以,是的,我觉得如果你期望人们很快就掌握所有知识,这可能真的会让人却步。因此,我喜欢这样一种想法,即使他们还没有完全理解这些概念的深层含义,仍然可以遵循这些想法。这对人们来说仍然是一个不错的捷径。

Dave Cheney:回到你的观点……我对惯用 Go 的担忧在于,每个人都是好意的,但我通常在代码评审、评论或在线讨论中看到它的表达方式都是“你做的事情不符合惯用风格”。让我吃惊的是,这种负面的反馈通常出现在这个人对 Go 最感兴趣、最投入学习、最兴奋的时候。

所以这整个过程的核心问题是:“我们如何重新构建这种评论或反馈,使其以一种积极的方式呈现?”这样就不需要有人听到“不要这样做,要那样做”。借鉴 Meyers 的工作,与其说“永远不要使用 C++ 中的某些(东西)”,不如建议“如果你有很多选择,先从这个开始。”这就是我想重新构建的方式。

Johnny Boursiquot:嗯,确实有道理。

Carmen Andoh:谈到编程中的惯用表达---代码可以有很多种方式编译成功,但为了提高可理解性、为了代码能够良好地扩展、适应、重构,我们需要在其上层构建一个共同的认知,这就是惯用 Go。

所以我听到的是,当你说“这不符合惯用风格”时,在某些情况下,这也许是可以的……如果那个人已经达到了某种对 Go 的理解水平。但我们也应该非常明确地说明“我们喜欢惯用 Go 的原因是它能在未来帮助我们,比如 X、Y 和 Z。” 我喜欢我们能够讨论这个话题,因为它超越了 Go,适用于任何编程语言。

Mat Ryer:我喜欢你刚刚提到的一个想法,那就是与其说“这不符合惯用风格”,即使你不提“惯用”这个词,你也可以说“这是一种做法。” 这是一种教学机会,不是吗?……我们并不总觉得有时间这样做,但有时只是换个角度表达你原本要说的话……突然间,就像你说的,Dave,这变成了一次积极的体验,而不是负面的。

Dave Cheney:确实如此。举个我最喜欢的例子 “当你启动一个 goroutine 时,总要知道它何时结束。” 这比说“嘿,你这里有泄漏”要好得多。如果你在代码评审中超链接到这样的建议,而不是简单地说“这里有问题”,那就更好了。

Carmen Andoh:是的。通过描述整个过程,比如从数据如何转换到最终结果,显示出某种程度的理解和意识……但我经常看到有人说“这不符合惯用 Go”时,其实他们只是在 货物崇拜(cargo culting),并不知道为什么。所以我喜欢这种想法:如果你准备好给出建设性的批评或反馈,比如“我们来这样做”,我希望给出反馈的人能够花些时间解释为什么这样做是更好的选择。但这确实很难做到。我认为当我们被要求即兴做出这种反馈时,几乎没有几个人能做到。所以我们往往会退回到“哦,这不符合惯用风格”,而不去理解其背后的原因。

Johnny Boursiquot:我本来想问你最喜欢的这些说法是哪一个,你之前也提到了一些……我注意到这些说法的大部分(至少八个)是非常明确的。你可以直接读出来,并且知道它是在告诉你做什么或避免什么。至少有两个,可能三个,可以被更主观地解释……我想到的最抽象的一个是 “适度是一种美德。” 显然,在我看来,适度是一种美德是最抽象的,它需要一些经验,像是你必须见过一些事物,才能知道“适当”的度是多少……然后是可维护性,不同的人对可维护性有不同的看法……第三个在我看来是“每个包只负责一个目的”。

所以这三个……取决于你与谁交谈,或者你所在的团队,这些东西对不同的人会有不同的意义。我想知道,尤其是对于第一个,尤其是关于适度部分,你个人使用的工具是什么?你怎么知道什么时候过犹不及,或者什么时候恰到好处?

Dave Cheney:是的,这是个很好的问题……我需要给一些编辑背景。讲到公开演讲---我相信每个人都有过这样的经历:你觉得“这是个好主意,我要提议做这个会议演讲。” 然后更好的是,你的提案被接受了,你非常兴奋,觉得“这太棒了!” 然后你开始做笔记,喝点咖啡,写写东西,然后过了一个月你会想“好了,时间不多了,我得开始写这个演讲了。” 你坐下来写演讲稿时突然觉得“糟糕,这个想法没我原先想得那么好。这不像我想象的那样有效。我犯了一个大错误。”

Carmen Andoh:[笑]

Dave Cheney:这是我每次写演讲时都会经历的一个过程。然后你得把自己拉出来,告诉自己“好吧……你已经答应做这个演讲了,主题也定下来了,错过了更改主题的时机,所以你得硬着头皮上了……”从我和其他演讲者的交谈中,我觉得这其实是每个人都经历过的常见过程。

所以稍微打破一下第四面墙,当我开始准备演讲时,我发现 Python 的禅意和 Go 的禅意之间的相似之处越来越少了。原本我以为两者有很多交集,但实际上,我发现我不得不对 Peters 的一些内容进行重解释。在演讲的结尾,我说“看,我已经挖掘了我能挖掘的东西。” 它们毕竟是不同的语言,尽管从血统上来看,Go 大概有 30% 的 Python 影响力……有些观点确实与材料契合得很好,而有些则有些牵强,还有一些根本没有任何相似之处。

所以,适度是一种美德。这个观点我觉得必须得提。比如“简单性”---我们无法在谈论 Go 时不提简单性。简单性是核心价值之一,如果这是 Go 程序员作为一个群体所珍视的事情列表,简单性一定要在其中。所以无论最终结论多么模糊和主观,你都必须说它是简单的。而这完全是主观的。

适度是一种美德……我思考了很多,尤其是我自己的经历。当我接触 Go 时,我对 Go 中不同于我之前使用的语言的东西非常兴奋……比如并发性、编译性---这比 JVM 语言要好得多……所以 Go 中那些独特的东西---我想要全部用上。并发用在所有地方,通道用在所有地方,每个 goroutine 都是独立的。“这些特殊功能在语言中有它的理由;所以我应该使用它们。” 这是我当时的理论。我不认为这是不常见的现象。

在另一个观点中,我写到,Go 的性能相对不错,所以人们会用它来做它擅长的事情。Go 具有良好的并发支持,所以用 Go 写的程序自然会大量使用并发。所以我们不能否认,语言中做得好的部分会是人们想要使用的部分。这就产生了一种张力:“我想用所有的特性。” 这种“新手心态”就是“我想用所有的功能。它们在那里是有原因的,我应该用它们。”

然而,当我们在学习 Go 并熟练掌握它的过程中,我们每个人都会意识到---就像生活中的道理一样,过度使用某些特性会导致代码难以阅读、难以维护,或者根本上就是太“聪明”了,超出了实际需要。这正是我过度使用 goroutine 时的体验。我把程序拆分成了很多小片段,结果忽略了它们实际上是相互等待的,基本上是顺序执行的,但我却让它变得更难理解。

我为 Go 项目贡献的一个大块代码是与 Adam Langley 一起在 SSH 包上工作。起初,几乎所有事情都使用了通道。每个消息---SSH 是一种堆栈协议,所以每个层级都通过通道通信……结果非常复杂,还需要检查通道的所有权、关闭通道等……我们基本忽略了这其实是一个分层协议,上层的事情不会发生,直到下层的事情完成……所以这些只不过是函数调用。

最后适度是一种美德。我留下了这样一个不太满意的结论:“你觉得你做到了最少吗?你是否以最少的方式使用了 Go 的特性?”

也许可以提供一些指导---大家还记得几年前那篇叫《选择无趣的技术》 的博客文章吗?这个想法是创新代币……大家听说过创新代币吗?

Mat Ryer:是的。但也许我们的一些听众没有听说过,所以也许你可以为他们解释一下……

Johnny Boursiquot:[笑] 当然……

Dave Cheney:我们肯定会把它放在节目备注中,但创新代币的概念是,每当你开始一个新项目时,你会带着你所有的知识。比如你在做一个新的 Web 应用程序---你基本上会从旧项目开始,也许会改动几处地方。可能你会说“哦,那个数据库…… 那是个糟糕的选择。对于这个项目,我认为我们需要一种不同的数据库。” 但是,一旦你选择了新的东西,比如更换数据库,你就花掉了一个创新代币。你承担了一个大的风险,因为那是未知的。这篇《选择无趣的技术》博客文章的主要观点是,你没有无限的创新代币。你只有两个,也许三个,更可能是一个。

所以如果你在考虑将“适度是一种美德”应用到你的 Go 代码中,可以说“嗯,这段代码确实用了很多并发。” 也许这是一个创新代币。使用大量 goroutine 来分解问题,那么我们就不应该再考虑其他复杂的数据结构了。关键在于意识到“嗯,我在 Go 的某个特定部分上用了很多。目标不是同时使用语言的所有部分。” 你已经在这个想法上押上了你的赌注。

Mat Ryer:是的,这有点像把极简主义进一步应用到你做出的技术选择上。这很有趣,因为这种事情有可能会成为那种标题式的建议,比如“不要使用通道”。它很容易陷入同样的陷阱。我要承认,我过去确实过度使用了通道……最后把自己搞得一团糟。但我发现这个学习过程非常有价值,所以我不一定想剥夺任何人的这种体验。你说得对---在一个真正的团队中,在一份真正的工作中,在一个真实的场景中,当然会有一些限制。你不能做太多。我喜欢这种不过度使用某些东西的想法,保持健康的使用方式。这是个不错的想法。

Dave Cheney:再给你举个例子,关于某些东西可能会被过度使用。在我第一次在 dotGo 上的演讲中,我想谈谈 Rob Pike 的函数式选项模式,以及我如何在我正在处理的代码设计中应用它。两年后,有不少人跟我说“我真的很喜欢那个演讲,但我不确定我能向其他人提出这些想法。这有点太怪异了。匿名函数返回匿名函数的想法太奇怪了。” 这其实促使了我另一个演讲的产生,关于“嘿,自由函数是一种工具,我们应该使用它们。”

但这是一个觉悟:如果你代码中的每一部分都是返回函数的函数,然后调用其他函数---那是一个很大的创新。如果你再混合大量的通道使用和 goroutine 使用---那是一个相当高端的操作。这会是一个你似乎使用了语言所有特性的案例。这是不是有点难以理解?

Carmen Andoh:是的……

Mat Ryer:是的,如果你在一个程序中用完了所有 25 个 Go 关键字,那我也认为你可能确实是这样做的。 [笑声]

Carmen Andoh: 最近有一篇关于 Go 程序员六个阶段的帖子,内容很有趣,是在 GitHub 上的一个简短的概述。大意是---你一开始很简单,但后来你会想:“不,我想用上所有的功能。” 然后随着时间的推移,你会意识到自己回到了最初开始的地方。然后还有一个有趣的向 Rob Pike 致敬的部分,不过没有语法高亮。 [笑声]

但这不仅仅是编程,甚至不仅是 Go,这是人类的天性。我们总是想要变得聪明。这也是为什么“幼稚”的词汇存在。“哦,那真是太幼稚了。” 这是当我们刚开始掌握某件事时,往往会过度使用它,或者把它弄得过于复杂…… 你一次又一次地看到,当你观察大师们时,他们有---我认为这就是为什么简单性如此重要的原因,或者说简单性其实很复杂,这往往来自于那些经历过艰难历程的人。我们也常听到一些名言,比如 Brian Kernighan 说的:“如果你在写代码时不够聪明去调试它,那以后你也调试不了它。”

所以这都是旅程的一部分,没有捷径可走。你必须允许学习者滥用一些功能,然后他们才能从另一端走出来。我觉得这就像但丁的《神曲》---你必须经历那一层层的地狱,越陷越深,然后才能从另一端走出来。你会没事的。 [笑声]

Dave Cheney: 我能问大家一个问题吗……?你有没有加入过一个项目---可能是加入了一家公司,或者只是换了团队,或者被分配到一个新项目---然后你心想:“呃,这段代码……呃。我得重写它。” [笑声] 这在广播中可能不太好展示,但……举手示意---谁有过这种经历? [笑声] 记录显示我举起了手,Johnny 把手放下了,Mat 也举起手了……

Carmen Andoh: 你知道吗---我把它内化为一种冒名顶替综合症。就像:“也许我没那么好。也许是我自己不行。” 我没有勇气去伸展我的肘部,去相信我所知道的可能是一种不错的方法,或者至少是一个同样好的方法。

Mat Ryer: 是的。对于我来说,这种情况在那些选择更多的语言中发生得更多。有 go fmt 来为我们做所有的格式化,并且去掉了那些决策,确实减少了一些困扰。

Carmen Andoh: 哦,所以你指的是格式化……

Mat Ryer: 是的,有时候我认为不仅仅是样式问题。即使是在面向对象的编程语言中,类的层次结构也有很多种不同的划分方式,有时候你会觉得“哦,这里有一个清晰的抽象”,而有些抽象则让人觉得有点凌乱,像是已经积了灰尘…… 当然,你会想:“现在可以写得更好了。” 这就是我们总是认为的,不是吗?

Johnny Boursiquot: 我之所以有点羞愧地举起手来,是因为我意识到自己确实做过……当你在一个项目中多次这样做时,你会意识到你带入项目的自负。你几乎是在否定几个月甚至几年的辛勤工作和努力…… 而且需求从来不会在第一次就明确。所以在项目中有很多你不了解的东西,然后你来到这里,心想:“哦,这简直是胡闹。我周末就要重写整个项目。” 这种自负简直就是:“得了吧,别那么自大。” [笑声]

Dave Cheney: 这真是太有意思了。我觉得随着我们在职业生涯中成长,可能会戴上一顶帽子,或者别上一个小徽章,上面写着“资深”之类的东西---也许我们不会再大声说出来了。也许我们只会在心里默默地想着。我们更加意识到“好吧,这段代码是在特定的时间和地点写的。” 但我能问你们这样一个问题吗---如果你被分配到一个项目,你有一群同事一起合作…… 但如果这是一个别人写的库呢---比如你要集成一个供应商的库,我就不指名道姓了,比如 AWS……

Johnny Boursiquot: [笑]

Dave Cheney: 你看着它,心想:“呃,这看起来不像我用这个语言写的任何其他代码。” 不仅仅是它看起来怪怪的,你还会想:“如果它出问题了,我得负责修复它。我把它集成到我的代码库里,我需要为它负责。” 那么,从平衡的角度看,我不如自己写一个是不是会更安全呢?

Carmen Andoh: 这与 2020 年的软件开发人员的常态背道而驰。如今你可以直接 npm-install 依赖包,但你却读了每一行你引入的依赖代码…… 至少这一点值得称赞!

我试图向我的孩子们解释编程是什么,我说:“如果你喜欢写作,编程实际上就是写作,因为你实际上是在把别人的段落放到你的书里,从各处拿过来。” Peter Bourgon 曾经发过一条推文:“我的 Bash 是像海明威一样的风格。简短、清晰、明了。” 而我则想:“我的 Bash 像莎士比亚---有点晦涩难懂,而且……”

Mat Ryer: 像悲剧? [笑声]

Carmen Andoh: “……我不知道别人能不能理解它。” 有点像悲剧…… [笑声]

Mat Ryer: 里面很多人死了吗……?

Carmen Andoh: Dave,我觉得这就是你在批评的那些库。它们可能工作得非常好,但它们不是你,不是你的表达方式…… 我觉得在你引入这些代码时,你必须接受这一点---当然,前提是代码本身没有问题,比如安全漏洞或猴子补丁(monkey patching)…… 这就是我告诉孩子们编程时必须接受的一个现实。

Dave Cheney: 在所有这些指控中我都无可辩驳,但我想引出的一个观点是我在《Go 的禅意》里提到的,维护性很重要。Peters 写到“可读性很重要”,在过去几年里,我花了很多时间思考这个问题,写了很多文章,做了很多演讲,也有很多经验,作为多个项目的技术负责人---老实说,我为大概 5-6 家不同的公司写过 Go 代码,而我已经离开了所有这些公司,这意味着其他人必须维护我留下的代码。

对此我感到焦虑…… 并不是说“哦,有人会搞砸我的工作吗?”,而是“我是不是在给别人留下债务。” 我已经多次说过,如果代码不可维护---举个例子,我在 Atlassian 写的第一段 Go 代码在我离开后被重写了,因为我是唯一的 Go 程序员。他们重写它是合理的。但这并不是一种语言发展的策略。当我们在不同工作之间转换时,留下的代码必须是可维护的、可持续的。

总结一下,这些都是关于如何编写可维护的 Go 代码的建议,其他人进入项目时能够理解并继续与之合作的代码,同时也是你自己愿意继续维护的代码。某种程度上,它是在尝试解释惯用 Go,而不是简单地说“不要这样做,不要那样做”,因为惯用 Go 有时会显得过于绝对化,比如“总是这样做,总是那样做。”

我真正想传达的是,为了我们的语言的成功以及任何项目的成功,代码必须是可维护的,而这一点超越了任何个人。

Mat Ryer: 是的。这甚至对你自己来说也很重要。当我们编写代码时,我们经常会想:“当我忘记了它,下个月再回来看时,我会对这段代码有什么感觉?届时它是否还会显得那么明显?” 现在显然很清晰,因为我们正在处理它,但一旦我们失去了所有的上下文,回头检查,发现出了问题时,我们会怎么想?

所以当你这样思考时---假设你会完全忘记这段代码,并在一个月后再回来检查,如果你是为那个未来的自己或者其他那些人编写的代码,我觉得这会有所帮助,而不是…… 当你正忙于编写代码时,一切都很清楚,因为我们刚花了几个小时弄清楚它。所以你拥有的额外信息会消失。除非你能以某种方式捕捉到这些信息,否则你将失去它。所以是的,考虑到这一点非常重要,即使是为你自己。

Dave Cheney: 我认为 Google C++ 团队有一个风格指南;网络上有一个版本。它的第一个条目是“为读者编写”,或者类似的内容。显然,这是一个已经渗透到 Go 中的理念,即编写的行为相比于几十次或成千上万次阅读的成本来说是无足轻重的。这可能是一个 Kernighan(译者注: 即BWK) 或 Strunk and White 的名言:“为读者编写。”

Carmen Andoh: 哈哈!Kyle Simpson 在《按键经济学》中的另一场精彩演讲。他讨论了 JavaScript 世界中的需求层次结构,即“我们如何对需求进行排名?” 读者的需求优先于编写者的需求,优先于维护者的需求…… 他在演讲中解释了这些惯用语的一些用途。简洁真的比可读性更重要吗?

我真的很喜欢那场演讲中的观点。它们和你说的话很相似,Dave。

Dave Cheney: 向 Bitbar 的作者致敬---你提出的“靠左写代码”、“提前返回”、使用守卫语句、不要把成功的代码放在缩进块里等等,这些完全就是为了读者着想的。

Carmen Andoh: 是的。你之前谈过可浏览性,Mat,我认为这与今天讨论的内容有异曲同工之妙。这是这个话题的视觉表达形式。

Mat Ryer: 是的,你说得对。想象一下数据库,当我们要写一些数据时,我们可能会选择将其与其他内容混合在一起,我们可能会选择非规范化(denormalize)…… 这样做在写入时可能有点昂贵。但如果有很多读取操作发生,那当然是合理的。当是在这样的世界里时,这感觉是正常的。但当是代码时,我不确定我们是否会这样想,也许我们应该…… 因为正如你所说,如果代码是成功的,它会被阅读的次数远远多于被编写的次数。

Carmen Andoh: 是的。随着 Go 用户量达到两百万,并且不断有新用户加入,我一直在想的一个问题是---大多数新用户都是带着之前的思维、假设、惯用语和文化来的…… 所以当你问到“当你加入一个公司时,你是否想重写所有东西”或者当你想要达成某种文化或者惯用语的共识时,如何应对这种碰撞……当你要让一个团队从另一种语言转向 Go,或者写一个新的服务,而这个团队传统上是一个 Java 团队或 Python 团队时……

就像一个美国人在另一个国家可能会犯一些文化错误一样,当你从其他语言转到 Go 时,你会犯哪些错误?

Dave Cheney: 这是一个价值 64,000 美元的问题。

Carmen Andoh: 是啊…… [笑声]

Dave Cheney: 回到我认为惯用 Go 不是一个好教学工具的观点上,它实际上是在说:“我们这里不这么做。你不符合潮流。这看起来很奇怪。” 举个例子,当 Go 在 Canonical 内部推广时,几乎每个人的第一个评论都是:“我的列表推导语句去哪了?这是什么垃圾?我得像个原始人一样写 for 循环?”

Carmen Andoh: 是啊。

Dave Cheney: 但这只是初学者的阶段。值得注意的是,当这个新程序员被安排到一个团队时,他可能会想:“我被告知不能用 Python 了。我必须用这门新语言;我已经有点焦虑了。” 然后你再告诉他:“我们这里就是这样做的。”---这是一个错误的信息。

Mat Ryer: 听起来像是在西部片里,不是吗?

Carmen Andoh: 是啊……

Dave Cheney: 这简直是“要么接受,要么滚蛋”的态度…… 这不会给任何人带来成功。人们要么对不能使用他们喜欢的语言感到不满,要么感到困惑,或者感到尴尬…… 这涉及到很多社会压力,比如“我被安排到了这个团队。我不想失败。” 所以我不喜欢这种惯用 Go 的想法,它指着别人的代码,敲敲屏幕说:“让它看起来像这个。” 这可能是一个非常教学化的工具,但它不是很人性化。

Carmen Andoh: 是的。

Mat Ryer: 我喜欢的另一个观点是“把并发留给调用者”。作为一个包的作者,你确实希望提供一个非常出色的包。尤其当它将以并发的方式使用时,这几乎是包中最有趣的部分,对吧?有时候你不得不为了简洁而放弃它。那么,把并发留给调用者还有哪些其他好处呢?为什么这很重要?

Dave Cheney: 说到这点,我要感谢 Peter Bourgon 提出的这个绝妙的观察。我估计还有其他人也对这个话题有所贡献,但你说得很对。如果你在写一个查找函数或者搜索函数,你会想:“我要发挥计算机的全部性能。我会启动一些 goroutine 并行处理。”这很棒,因为这是有趣的部分。你会想:“我可以做有趣的部分了。不用写 API 调用,或者写文档。我可以直接做有趣的部分。”

但这里的问题---我稍后会提供一个链接供参考---是 Peter 提到的……我记得他在 GoFest(在旧金山举办的一个活动)上做了一个演讲,叫做《我的做事方式》,讲的是他使用 Go 的一些经验。其中一个观点是“你不能忘记你启动的 goroutine。” 我的表达方式可能有点教条主义,但大意是“如果你启动了 goroutine,一定要知道它什么时候结束。” 这个观点可能不是列表中最好的,但 Peter 的观察是,你需要有一个高级概念来跟踪这个 goroutine。如果这个 goroutine 是你启动的,而且它占用了资源,你需要知道它什么时候停止,因为你可能需要回收这些资源。

有多少次你遇到测试不稳定,因为它们碰到了前一个测试用例的残余部分,而这些部分还没有完全关闭?并发的根本问题不在于我们无法启动并有效地使用它,而在于我们必须知道它什么时候完全关闭并结束。因为如果你想再次执行它,你不能和之前的实例互相冲突。

回到“把并发留给调用者”,最好的方法之一(对作者来说可能不太好)是放弃“谁来启动这个 goroutine?”的责任,将其交给调用者。给他们提供钩子……最简单的提供并发运行函数的方式就是写一个普通函数。他们可以自行放入 goroutine 中运行。

这些是 Peter 的一些想法,涉及到更复杂的概念,比如我们有工作者(workers)。你可以想象提供服务的工作者们,比如一个索引服务、搜索服务,或者接受连接的服务---它们在概念上是作为一个整体协同工作的;你需要一种方式来作为一个整体管理它们。因此,Go Kit 的 run 包中就有一些不错的想法。

Mat Ryer: 我想补充一点,如果你在包内部处理并发……在 Go 中,让一个东西并发运行是很容易的。但如果你想反过来,将并发代码同步化,尤其当它在某个包中运行,而你没有访问其他通道和相关内容的权限时,这并不容易。

所以这也是一个大家应该记住的好点---让用户自己处理并发更容易,因为他们知道自己在做什么,并且负责它的执行。这确实很遗憾。

不过,我可以告诉你一个小技巧---我最近就用过。写一个小示例来处理并发。这样你仍然可以实现它,证明它的可行性,你还是能看到它如何工作……如果这对你来说是有趣的部分,你还可以享受这部分的乐趣,但这不需要成为包 API 的一部分。它可以作为项目附带的示例。

Mat Ryer: 我认为 Go 的可测试示例(testable examples)是一个被低估的宝藏。每个 API 函数都应该有一个示例。之所以没有,是因为大多数时间,文档就够了。但它给阅读 GoDoc 的人极大的帮助---那里有一段样例代码,不仅仅是注释里的代码示例;它是经过测试的,和你在屏幕上看到的示例完全一致。这是一个非常简单却非常有力的功能。我想不出其他现代语言有类似的功能,能够如此深入地集成到测试包中,并且在 GoDoc 中得到高亮展示。这是一个隐藏的宝石。

Johnny Boursiquot: 我想谈谈一个观点:如果你觉得某个东西慢,就用基准测试来证明。这其实是我最喜欢的 Go 特性之一。你不需要引入第三方工具来测试代码的效率;它已经内置在语言之中。基准测试是测试框架的一部分。所以如果你觉得某个东西慢,不要仅凭感觉,而是写基准测试来证明它,然后再做优化。

我还想提的是,当我刚开始使用 Go 时,社区里经常有一种声音,说“Go 很快,Go 很快,但你必须知道如何使用 goroutine,你必须知道如何正确编写代码。” 那时有一种对纯粹性能的狂热。如果某个东西没有做到零分配、极其快速……那时候我们几乎每周都会看到新的 HTTP 路由器发布。也许那是我们作为一个社区需要成长和成熟的阶段,去更好地了解这门语言,并作为开发者成长……

但这种“如果它不够快(根据某些随意的标准),你就需要重新设计”的观点确实存在。所以,是的,请谈谈这个话题……因为对我来说,Go 已经解决了我大多数的性能问题。我很少需要真的去优化某一部分或某一段代码。也许这只是我个人的体验,但我不常需要担心优化我的 Go 代码。

Dave Cheney: 是的,如果你觉得它慢---坦白说,这也是我在做性能研讨会时的一点个人看法。我经常遇到这种“它已经是最快的吗?”的心态,不出意外,我对此有一些看法。

在这个讨论中,我认为有两个重要的方面---有一个可读性、可维护性和性能之间的连续体。大家是否同意,有时候为了获得最快的代码,代码的可读性和可维护性必须受到影响?如果这项工作的根本目标是可维护性最重要的话,那么当你为了性能而去牺牲代码的可读性,去优化性能,使得代码更加隐晦、难以维护……即使代码看起来不是那么糟糕,它也可能具有非常微妙的约束,比如某些变量只能被初始化一次,或者“这一行代码不能移动”。这些非常脆弱的东西,不仅损害了可读性和可维护性,还使代码变得非常敏感。

标准库中有一些例子,比如一个函数被分成两部分,简单的部分通常是可内联的,而异常情况则不是---这些代码非常微妙,如果没有注释说明“不要移动这一行”,你根本不会意识到它的重要性。

另一个我要提到的点是教条主义。有人会说“defer 是慢的”、“永远使用原子操作,绝不使用互斥锁”。这些绝对化的言论驱动了这样的思维方式。那么我们怎么知道什么时候该投入精力去编写更隐晦、难以维护的代码呢?使用 Go 附带的基准测试工具。不要为了---我给你举个例子……在 Go 1.14 中,defer 解锁互斥锁几乎和非 defer 版本一样快。当我说“几乎”时,我的机器上差不多慢了 0.7 纳秒。

所以从绝对化的角度来看,你可以说“看,它还是慢了”,但问题是那是一个没有竞争的互斥锁。只有一个 goroutine 获得锁,然后释放锁。一旦它是有竞争的,原子操作会导致 CPU 之间交换缓存行。让我告诉你,那 0.7 纳秒根本无关紧要。

Mat Ryer: 你说得对。这是一个很好的观点,因为只要你有几个程序在相互交互,事情就会变得非常不可预测……非常非常快地变得不可预测,至少在我看来是这样。但……这可能是物理的限制。所以这也是你说“证明它”的原因吧?你需要证明它,因为实际上,结果可能和你想象的完全不同,你可能会对代码实际在真实环境或大规模下的运行方式感到惊讶。

Carmen Andoh: 是的。关于惯用的 Go,我想说的一点是,你必须知道什么时候打破规则,而这对初学者来说尤其困难。你提到的教条主义---每个版本都会修复之前我们教条主义对待的问题……defer 就是一个很好的例子,在 Go 1.14 中得到了改进。

Johnny Boursiquot: 对于那些因为工作或兴趣而学习 Go 的初学者来说---要总是怀疑自己“我做得对吗?我这样写是惯用的吗?”这确实是很有压力的。或者你会担心有经验的人看了这个代码之后会说 Dave 刚才提到的那种话:“我们这里不这样做。” 所以这确实让人有些焦虑。

对所有正在学习 Go 的人,我想说,不要太在意它是不是惯用。尽情学习,尽情玩耍。希望有人会温和地告诉你“嘿,有更好的方法”,而不是仅仅说“这不是惯用写法”然后走开。但即使遇到那些批评者,也没关系。批评者永远不会少,对吧?只要专注于学习和玩耍就好。惯用的写法会随着时间自然形成……

我在学习 Go 的过程中并没有某个时刻突然意识到“哦,我现在写的代码是惯用的 Go 了!” 这种事不会发生。没有任何标志,也没有里程碑。你的代码会逐渐变得符合我们主观上认为是惯用的标准,但没有什么官方的门槛。所以尽情玩耍,尽情学习吧。

Carmen Andoh: 是的。我姐姐是大学的西班牙语教授,虽然她教的是语言,不是编程语言……但她已经教了二十多年了,她几乎可以预测谁能学好西班牙语,谁不能。她的判断依据是那些愿意在第一周就弄得一团糟的人……因为学习就是混乱的。所以,Johnny,你刚才说的正是这个意思---尽管去犯错。放手去做,做错了也没关系。

Mat Ryer: 太对了!

Carmen Andoh: 是的,没错。

Johnny Boursiquot: [笑声]

Dave Cheney: 关于惯用 Go,我想到的一件事是,最初这种做法是出于好意,比如“嘿,你是新人。我来给你介绍一下,教你一些规则,给你看看咖啡机在哪儿……” 但在某个时刻,它也可能变成一种门槛。我们只按照这种方式做事,我们不会改变。而这是我们应该注意的另一点---在任何健康的社区中,无论是口语还是编程语言的使用,都会不断演变。想想网络用语,20 年前没人会说‘lol’。这是我们发明的新词,它改变了交流方式。

所以,关于惯用 Go,我最后想说的一点是,它绝不应该成为设立门槛的工具。“我们一直都是这么做的”,这种回答永远不能是一个可持续社区的答案。

Mat Ryer: 你说得很好。我觉得这很好地总结了我们今天的节目。好了,看看我的手表,我想是时候进入我们的常规环节了---“不受欢迎的观点”。

Johnny Boursiquot: 哇,你把这个搞砸了…… [笑声]

Mat Ryer: 那么有人有不受欢迎的观点吗?这次不能是关于我的时间掌控的……Dave,你有什么不受欢迎的观点吗?

Dave Cheney: 我有不受欢迎的观点吗?嗯,我已经给了你们 11 个了。 [笑声]

Johnny Boursiquot: 哈哈,根据 Hacker News 的说法,你确实有不少。 [笑声] 真是有趣的一群人……

Carmen Andoh: 它不一定要关于 Go……可以是任何话题。是的,关于任何话题。现在就是你的时刻。

Dave Cheney: 所以……回想一下你的一天。想想你今天做了什么,再想想那些打断你工作思路的事情。很多打断你专注的事情可能是无法避免的,比如“我的狗刚刚下楼了”,或者是家人,或者是有人在敲门……但我发现很多这样的打断其实是自己造成的。手机通知、弹窗、聊天消息、Slack 的声音……所有这些都会打断你的专注。

对我来说很明显的是,作为知识工作者,我们有三项技能。第一是我们的经验;我们带到工作中的经验。第二是沟通能力。第三是专注力。这些技能按难度和优先级排列。经验---我们总是可以通过谷歌搜索或者与人交流来获取经验。沟通能力决定了你能多好地提出问题、组织思路、说服他人去做事情。但我认为最重要的,也是我最难掌握的一项技能,是专注力。

想想一天中的所有事情,“哦,我只是拿起手机,看看 Twitter。” 我故意让自己分心。而作为一群知识工作者---也许这适用于所有人---专注是我们的超级能力。如果你不能专注,你就无法完成任何事情。

Mat Ryer: 这是个很好的观点。我不认为这会是不受欢迎的观点。我还以为你会继续说你现在正在卖 Dave Cheney 为开发者设计的紧身衣呢。 [笑声]

Dave Cheney: 不,我觉得不受欢迎的观点是,这个世界上有很多东西在竞争我们的注意力。无论你有多少钱,你都无法发明出一天的第 25 小时。

Carmen Andoh: 是的……

Dave Cheney: 所以你的注意力是一种资源,现代工作环境中有很多事情在争夺这种资源---而且很多事情实际上对保持注意力是有害的。通过 Slack、聊天等方式随时待命……我并不是说即时聊天或团队聊天不好,但你能否诚实地告诉我---对听众们说---如果你一天关闭了聊天通知,你不会感到 a) 对忽视同事感到内疚,或者 b) 担心你的老板会觉得你在偷懒?

Carmen Andoh: 是的。

Dave Cheney: 但是,如果我们程序员不能创造出任何专注的空间,我们到底该如何工作呢?

Mat Ryer: 你说得非常有道理,Dave……实际上,我正在做一个新项目,正是基于这些原则。我认为你说得太对了。如果有一间房间里有 100 个开发者在工作,你不会走进去然后大喊“在这里!” 对吧? [笑声]

Carmen Andoh: 对。

Mat Ryer: 然而,这就是在 Slack 上发生的事情。这也是一种文化现象,因为它是即时通讯。你期望得到即时回复。

Carmen Andoh: 是的。如果你没有马上回复,别人会问“为什么你没回复?”

Mat Ryer: 是的,你会觉得自己让别人失望了……于是你马上做出反应,想赶紧处理掉它,这样你就可以回到专注状态。但实际上,你是在奖励这种行为,并间接强化了它。

Carmen Andoh: 是的。

Dave Cheney: 我想问在工作中的任何人,你是否有意识到自己在使用 Slack、Discord 或任何聊天系统时,会条件反射地按下 Alt+Tab 检查窗口?只是看看有没有需要你回复的内容。

Carmen Andoh: 哦……我认罪。

Dave Cheney: 因为你实际上是在破坏自己专注的能力。如果有一件事能够决定两个人谁更成功,那就是他们的专注能力……因为如果你不能专注,你就无法进行有效的沟通或深入的思考。无论是被环境打断,还是训练自己去打断专注状态,这都是个问题。所以,这是我去年对自己提出的挑战。每当我条件反射地想拿起手机时,我会拿起一本书。我总是带着一本书,试着读一页。

我喜欢早上起来看 Twitter,喜欢了解世界动态,就像我吃早餐时看新闻一样,但我意识到让自己分心是多么容易……它就在 Alt+Tab 的一步之遥,或者手机就放在我旁边,因为我们需要它来获取 2FA(双因素认证)令牌。我不能把手机放在楼下,否则我就不能登录工作。

Carmen Andoh: 或者如果你在待命轮值中……是的。

Dave Cheney: 所以,想想环境如何合谋让你一直处于可被打断的状态,而这又如何与保持专注的能力相矛盾。我记得很久以前 Nate Finch 发过一条推文,说“我们在开放式办公室里该如何专注?” 这是我的非主流观点。

Carmen Andoh: 这就像但丁的地狱---开放式办公室是真正的但丁地狱。 [笑声]

Johnny Boursiquot: 所以,我们应该注意到,当你加入 #GoTimeFM 的 Slack 频道时,所有关于打断的讨论都不适用,因为你是自愿加入我们有趣的讨论并选择被打断的。所以,请继续这样做吧。

Carmen Andoh: [笑] 说得好。

Mat Ryer: 是的。但我担心我们的时间快到了……Dave,非常感谢你。任何感兴趣的人可以查看节目的备注---

Dave Cheney: 谢谢你们邀请我。

Mat Ryer: 绝对是我的荣幸,一如既往。

Johnny Boursiquot: 谢谢,确实如此。

Mat Ryer: 有人在 Slack 频道里说了什么

Johnny Boursiquot: 你可以去找他们理论。 [笑声]

Dave Cheney: 我自己也不太清楚……

Mat Ryer: 不过,这次节目真的很棒。我认为我们应该从中汲取的一个重要教训是,如果你发现自己快要说出“这不是惯用的做法”时,试着想一想为什么我们认为那是惯用的做法。它的优点是什么?讨论这些想法,并对他人挑战这些想法持开放态度。我非常喜欢这个想法。我认为我们都应该花些时间去思考这个问题。

Dave Cheney: 我给大家留一个挑战。这是我给自己定下的挑战---我成为了 VMware 项目的技术负责人……就像在很多工作中发生的那样,我被雇为唯一的 Go 程序员,大家自然把我当作专家看待。但作为技术负责人,你需要热爱编写代码,给出指导,等等。不过,我给自己定下的规则是,我绝不能使用“就按我说的做,因为……”这种借口。“按这个方式做,因为我是专家。相信专家,相信你老爸。相信比你更优秀的人。你以后会明白的。”

我告诉自己绝不能使用这样的借口。所以,我写的很多东西、思考的很多问题,都是基于不能说“相信我,按我说的做”这一前提。我必须能为我所说的每一件事情找到合理的解释,比如“感谢你的代码审查。我对这个 API 有些顾虑,因为这个第二个参数并不总是使用,这对新手来说可能会造成困惑。” 当你不能说“相信我,按我说的做”时,你就必须做出这样的解释。

所以,我对大家的挑战是,当你与同事讨论时,尤其是在代码审查时,如果你发现自己说“我们已经讨论了这么多次,为什么你不能按我说的做呢?” 也许是因为你从未真正解释过原因。你依赖的是自己的历史,而不是进行开放的讨论。与其说“做”或“不做”,不如说“我认为这样做会更好,因为……” 这些都是你必须做出的解释。

所以,牢记 Scott Meyers 的话,永远不要用绝对的语言。要说“通常情况下,建议……” 或者当你做出例外时,讨论它。这是我给大家的挑战。当你和同事讨论代码时,不要简单地说“按我说的做”,而是说“我认为这样做会更好,原因是……” 因为归根结底,这只是你的观点。但与其使用绝对的语气,不如说“这是我的建议。你同意吗?”

Mat Ryer: 好吧,挑战接受。我希望所有听众也能接受这个挑战。Dave,非常感谢你。请一定要再来。而对于其他人,我们下次再见。

Mat Ryer: 大家好! *咳嗽* 这开场不太好。有时候,孩子们,事情不会按计划进行……这就是其中之一。就在错误的时间咳嗽了一下。不过别担心,编辑的力量会让这段肯定出现在最后的成品里。

Johnny Boursiquot: 是的,这段肯定会保留,是的。

Mat Ryer: 是吧?这会像 DVD 的花絮一样…… [笑声] 我们没有太多可用的片段,但像这样的小片段……有人咳嗽---就是金子;把它放进 DVD 花絮里。好吧,我再来一次……

Carmen Andoh: “爸爸,什么是 DVD 花絮?” [笑声]

Johnny Boursiquot: “什么是 DVD?” [笑声]

Carmen Andoh: “爸爸,什么是 DVD?” [笑声]

Mat Ryer: 哦对,我忘了世界已经进步了……

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mfbz.cn/a/886867.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

【Java的SPI机制】Java SPI机制:实现灵活的服务扩展

在Java开发中,SPI(Service Provider Interface,服务提供者接口)机制是一种重要的设计模式,它允许在运行时动态地插入或更换组件实现,从而实现框架或库的扩展点。本文将深入浅出地介绍Java SPI机制&#xff…

08_OpenCV文字图片绘制

import cv2 import numpy as npimg cv2.imread(image0.jpg,1) font cv2.FONT_HERSHEY_SIMPLEXcv2.rectangle(img,(500,400),(200,100),(0,255,0),20) # 1 dst 2 文字内容 3 坐标 4 5 字体大小 6 color 7 粗细 8 line type cv2.putText(img,flower,(200,50),font,1,(0,0,250)…

【AI学习】Mamba学习(二):线性注意力

上一篇《Mamba学习(一):总体架构》提到,Transformer 模型的主要缺点是:自注意力机制的计算量会随着上下文长度的增加呈平方级增长。所以,许多次二次时间架构(指一个函数或算法的增长速度小于二次…

国外电商系统开发-运维系统批量添加服务器

您可以把您准备的txt文件,安装要求的格式,复制粘贴到里面就可以了。注意格式! 如果是“#” 开头的,则表示注释!

【Qt】控件概述 (1)—— Widget属性

控件概述 1. QWidget核心属性1.1核心属性概述1.2 enable1.3 geometry——窗口坐标1.4 window frame的影响1.4 windowTitle——窗口标题1.5 windowIcon——窗口图标1.6 windowOpacity——透明度设置1.7 cursor——光标设置1.8 font——字体设置1.9 toolTip——鼠标悬停提示设置1…

《安富莱嵌入式周报》第343期:雷电USB4开源示波器正式发布,卓越的模拟前端低噪便携示波器,自带100W电源的便携智能烙铁,NASA航空航天锂电池设计

周报汇总地址:嵌入式周报 - uCOS & uCGUI & emWin & embOS & TouchGFX & ThreadX - 硬汉嵌入式论坛 - Powered by Discuz! 更新一期视频教程 【授人以渔】CMSIS-RTOS V2封装层专题视频,一期视频将常用配置和用法梳理清楚&#xff0…

鸿蒙harmonyos next flutter通信之MethodChannel获取设备信息

本文将通过MethodChannel获取设备信息,以此来演练MethodChannel用法。 建立channel flutter代码: MethodChannel methodChannel MethodChannel("com.xmg.test"); ohos代码: private channel: MethodChannel | null nullthis.c…

openpnp - 图像传送方向要在高级校正之前设置好

文章目录 openpnp - 图像传送方向要在高级校正之前设置好笔记END openpnp - 图像传送方向要在高级校正之前设置好 笔记 图像传送方向和JOG面板的移动控制和实际设备的顶部摄像头/底部摄像头要一致,这样才能和贴板子时的实际操作方向对应起来。 设备标定完&#xf…

加油站智能视频监控预警系统(AI识别烟火打电话抽烟) Python 和 OpenCV 库

加油站作为存储和销售易燃易爆油品的场所,是重大危险源之一,随着科技的不断发展,智能视频监控预警系统在加油站的安全保障方面发挥着日益关键的作用,尤其是其中基于AI的烟火识别、抽烟识别和打电话识别功能,以及其独特…

V2M2引擎源码BlueCodePXL源码完整版

V2M2引擎源码BlueCodePXL源码完整版 链接: https://pan.baidu.com/s/1ifcTHAxcbD2CyY7gDWRVzQ?pwdmt4g 提取码: mt4g 参考资料:BlueCodePXL源码完整版_1234FCOM专注游戏工具及源码例子分享

[Cocoa]_[初级]_[绘制文本如何设置断行效果]

场景 在开发Cocoa程序时,表格NSTableView是经常使用的控件。其基于View Base的视图单元格模式就是使用NSCell或其子类来控制每个单元格的呈现。当一个单元格里的文字过多时,需要截断超出宽度的文字,怎么实现? 说明 Cocoa下的文本…

[Go语言快速上手]函数和包

目录 一、Go中的函数 函数声明 多个返回值 可变参数 匿名函数 值传递和地址传递 函数执行顺序(init函数) 二、Go中的包 基本语法 主要包(main package) 导入其他包 包的作用域 包的使用 包名别名 小结 一、Go中的函…

[C++]使用纯opencv部署yolov11目标检测onnx模型

yolov11官方框架:https://github.com/ultralytics/ultralytics 【算法介绍】 在C中使用纯OpenCV部署YOLOv11进行目标检测是一项具有挑战性的任务,因为YOLOv11通常是用PyTorch等深度学习框架实现的,而OpenCV本身并不直接支持加载和运行PyTor…

使用python基于DeepLabv3实现对图片进行语义分割

DeepLabv3 介绍 DeepLabv3 是一种先进的语义分割模型,由 Google Research 团队提出。它在 DeepLab 系列模型的基础上进行了改进,旨在提高图像中像素级分类的准确性。以下是 DeepLabv3 的详细介绍: 概述DeepLabv3 是 DeepLab 系列中的第三代…

设计模式-策略模式-200

优点:用来消除 if-else、switch 等多重判断的代码,消除 if-else、switch 多重判断 可以有效应对代码的复杂性。 缺点:会增加类的数量,有的时候没必要为了消除几个if-else而增加很多类,尤其是那些类型又长又臭的 原始代…

基于vue框架的大学生四六级学习网站设计与实现i8o8z(程序+源码+数据库+调试部署+开发环境)系统界面在最后面。

系统程序文件列表 项目功能:学生,训练听力,学习单词,单词分类,阅读文章,文章类型,学习课程 开题报告内容 基于Vue框架的大学生四六级学习网站设计与实现开题报告 一、研究背景与意义 随着全球化进程的加速和国际交流的日益频繁,英语作为国际通用语言…

【前端开发入门】html快速入门

目录 引言一、html基础模板内容二、html文档流三、html 标签1.块级元素2.行内元素3.功能性元素4.标签嵌套 四、html编码习惯五、总结 引言 本系列教程旨在帮助一些零基础的玩家快速上手前端开发。基于我自学的经验会删减部分使用频率不高的内容,并不代表这部分内容不…

vue.js 原生js app端实现图片旋转、放大、缩小、拖拽

效果图&#xff1a; 旋转 放大&#xff1a;手机上可以双指放大缩小 拖拽 代码实现&#xff1a; html <div id"home" class"" v-cloak><!-- 上面三个按钮 图片自己解决 --><div class"headImage" v-if"showBtn">&l…

数据订阅与消费中间件Canal 服务搭建(docker)

MySQL Bin-log开启 进入mysql容器 docker exec -it mysql5.7 bash开启mysql的binlog cd /etc/mysql/mysql.conf.dvi mysqld.cnf #在文件末尾处添加如下配置&#xff08;如果没有这个文件就创建一个&#xff09; [mysqld] # 开启 binlog log-binmysql-bin #log-bin/var/lib/mys…

Linux集群部署RabbitMQ

目录 一、准备三台虚拟机&#xff0c;配置相同 1、所有主机都需要hosts文件解析 2、所有主机安装erLang和rabbitmq 3、修改配置文件 4、导入rabbitmq 的管理界面 5、查看节点状态 6、设置erlang运行节点 7、rabitmq2和rabbitmq3重启服务 8、查看各个节点状态 二、添加…