02|拒绝“Hello and Bye”:Go语言的设计哲学是怎么一回事?
02|拒绝“Hello and Bye”:Go语言的设计哲学是怎么一回事?
讲述:Tony Bai
时长21:49大小19.93M
简单
显式
组合
并发
面向工程
小结
思考题
赞 105
提建议
精选留言(30)
- 自由2021-10-15Tony Bai 老师,你好,例举两个我认为复合 Go 语言设计哲学的例子,我的技术能力捉襟见肘,说的不对地方还希望老师斧正。 一、异常处理 Go 语言核心开发者 Dave 曾说过 “You only need to check the error value if you care about the result”,在我们不处理错误的时候,我们不应该对它的返回值抱有任何幻想。 Go 的异常处理逻辑,没有引入 exception,而是使用了多参数返回,在返回中带上错误,由调用者来判定这个错误。 - 简单 - 没有隐藏的控制流 - 完全交给你控制 error - 考虑失败,而不是成功 二、类型别名 在特定情况下,帮助代码逐步修复。 类型别名的存在,是 渐进式代码修复(Gradual code repair) 的关键,什么是渐进式代码修复?举一个🌰 重构。重构代码,我们当然希望重构后的好处,能够适用于所有代码,但是,重构的好处与代价是成正比的,往往一次重构会伴随着大量的修改,随着代码量越来越大,一次完成所有修改变得不可行。修复需要逐步完成。在代码量少时,我们可以一次性完成所有的修复,这样的修复被称为原子代码修复(atomic code repair),它的概念很简单,就是在一次提交中,更新所有的因为重构带来的问题修复,但是概念的简单会被实际的复杂性抵消,一次提交可能非常大,大的提交很难去一次性修复,出现问题也很难去溯源,最重要的是,可能会与其他同学的工作产生冲突,例如某个同学,在工作时,使用了旧的 API,合并代码时,并不会产生冲突,而我的提交错过了它的引用。 因此,我们需要一个过渡期,这个过渡期就是为了逐步替换,也就是渐进式代码修复,将旧的引用,逐步替换,同时将旧的换为新的,这就是渐进式代码修复,它的缺点是比原子代码修复的工作量更大,但是它更容易提交、审查,并且保证了,没有人引用后再删除旧的类型别名。展开
作者回复: 👍。说的很好。很认同你提到的基于“类型别名”的渐进式代码修复(Gradual code repair) 思路。这也是类型别名最初被引入go的初衷(https://github.com/golang/proposal/blob/master/design/18130-type-alias.md)。我觉得它也是go面向工程设计哲学的体现。另外type alias在基于现有实现进行扩展并做出新的封装方面也有“奇效”。
共 5 条评论34 - 王智2021-10-26表示身为一名Java工程师,在看到组合的时候有一点疑惑,我的想法是这里的组合就是将另一个类里面的东西平移过来,类似于java中的继承,我想问的是如果存在两个类包含相同名字的方法或者属性,这个go怎么处理?还是直接就不允许呢?go语言从来没接触过,不懂就问
作者回复: 好问题! go的组合有多种形式。按你提到的“继承”型组合中,如果组合的两个类型中有相同名字的字段,那怎么解决呢?看下面中的例子: package main type T1 struct { a int } type T2 struct { a int } type T struct { T1 T2 } func main() { var t T // t.a = 5 // 编译报错:ambiguous selector t.a t.T1.a = 5 t.T2.a = 6 println(t.T1.a) // 5 println(t.T2.a) // 6 } 如果T组合的两个类型T1和T2都包含字段a,那么我们不能直接使用t.a,而是通过t.T1.a和t.T2.a分别指代各自类型中的字段a。
共 4 条评论16 - 罗杰2021-10-1521 世纪的 C 语言,的确实至名归。依然有几个小问题:1. Go 有 GC,我们使用 Go 来开发后端的所有服务,有个 PVP 的服务,需要逐帧计算客户端上报的结果是否正确,此时对于内存的分配就要特别小心,开发起来很不顺畅。是否这种服务的性质不太合适使用 Go 来开发;2. 有人吐槽 Go 核心人员不想做的东西,就是 Less is more,自己想做就是各种哲学,这个问题,老师怎么看?
作者回复: 罗杰出品的问题,都是精品问题^_^。 先来看第一个问题,Go最初设计目标是通用的系统编程语言,但Go选择支持了GC。Go的GC虽然在go团队的努力下,开销越来越小,但开销小,低延迟不代表没有,这就决定了Go在一些对性能极其敏感的领域可能并不是最好的选择。你的问题中也提到了pvp服务,想必你们也是采用了面向服务的架构,这种架构本身就是可以天然适合技术异构的。如果觉得不妥,也别强求,果断换非GC语言,比如c、c++或是rust。如果说非要坚持用go来完成,那么说明你是go的骨灰粉,在解决问题的过程中,你也会完成一次go技能的升华。 第二个问题,Go语言的简单或者说功能特性少,的确来自与less is more的理念。保持一门小语言,让语言更容易学习与理解。同时每个特性都是经过精心打磨与实现,不能再少了。上周我看了rob pike最新一期的talk,他还在说 “Go语言中变量声明的方式有些多了”,这也是我在实际编码过程中的体会。如果重新来过,我想rob pike会更彻底的执行less is more,将变量声明方式再减少一种。所以说,特性少不是不想做,而是经过深思熟虑,那个特性的确没必要加入到语言中。
共 6 条评论14 - liaomars2021-10-15go的异常处理,使用起来简单,但是不方便,请问老师这是在践行go的简单设计哲学吗?
作者回复: 从go设计者的初衷来看(https://golang.google.cn/doc/faq#exceptions),go没有采用像java那样的结构化异常处理的确是出于对“简单”原则的考虑。 在java中错误处理与真正的“异常”是混杂在Try-catch机制中的,并没有明显的界限,无论是错误还是异常,一旦throw,方法的调用者就得负责处理它。 但在go中,错误处理与真正的异常处理是严格分开的,也就是说不要将panic掺和到错误处理中。 错误处理是常态,go中只有错误是返回给上层的。一旦出现panic,这意味着整个程序处于即将崩溃的状态,返回给上层几乎也是“无济于事”,所以在go中,一个常见的api设计思路是 不要向外部抛出panic(don't panic!)。如果api中存在panic的可能性,那么api自己要负责处理panic,并通过error将状态返回给上层。如果api无法处理panic,那程序就很大可能是要崩溃了,这种panic多是因为程序bug导致的。
共 9 条评论11 - 学昊2021-10-15本人老java码农了。进阶的代码设计是设计模式,让代码能更优雅的实现。终极的代码设计是哲学,是代码中表达出的价值观。共 1 条评论9
- Geek_3990422021-10-15自动加入分号是不是也是简单的设计哲学呢,能让编译器做的事不需要交给开发者。
作者回复: 手动点赞
9 - 张申傲2021-12-01尤其认同 Go 语言的“面向工程”这一设计哲学。作为 Java 的资深用户,每天都深受编译速度慢、依赖树失控、代码风格不统一等问题的困扰。Go 语言的设计哲学恰恰迎合了现代大规模业务系统的开发和维护。
作者回复: 👍
共 3 条评论6 - 费城的二鹏2021-10-15老师讲的很透彻,如数家珍。5
- 悟二空2022-04-27只有 TonyBai 老师的这门专栏课,我会把每条留言评论都认真看完,因为老师都有很认真的在回复,一点儿也不含糊。能学到非常多的知识,非常感谢老师,我一定能学好Go语言并进入自己想去的公司的。
作者回复: 💪
4 - Shanks-王冲2021-10-19放弃之前,看这篇,找找最初的心动,哈哈:)4
- Howe2021-12-11老师,请教两个问题: 1、文中提到的正交独立是什么意思?不是很理解。 2、Go不支持面向对象,那意味着复用性不好,这种后面老师会讲工程实践吗?
作者回复: 1. 正交(orthogonality)是从几何学中引入的术语,如果两条线以直角相交,如图形上的轴线,就是正交的。如果说两个事物是正交的,那么我们说 这两个事物是独立且解耦的,一个事物的变化不影响另外一个事物。 我们经常用“正交”来评价一个系统的设计,比如在一个设计良好的系统中,数据库代码将与用户界面正交:你可以在不影响数据库的情况下,独>立进行界面的演进。 编程语言的语法元素间也存在着正交的情况,比如文中提到的类型定义与方法是正交的。这意味着一个类型可以有方法,也可以没有方法。而方>法本质上接收类型作为其第一个参数的函数而已(具体参考第24讲)。 在Go语言中,正交的语法还有一些,比如接口就与Go语言其他部分是正交的。 但正交的两个语法特性组合起来可以实现其它特性,这也是我们在一个系统中经常做的事情。 2. 难道不用面向对象就不能复用了么?:) Go有自己的组合的设计哲学,组合也可以实现复用。课程后面会有讲解。
共 4 条评论3 - van2021-11-09您好,不好意思,之前提的问题可能不对,其实我真正想问的是其他主流比如java有没有利用多核并行?比如java构建了很多线程,这些线程的调度在多核cpu上已经天然是多核并行的,这个并不是java的语言特性,而是操作系统的特性。 因为go用的是用户态协程,所以go做了一个大换血,自己手动支持多核并行。共 1 条评论3
- 进化菌2021-10-21Go 语言的设计哲学总结为五点:简单、显式、组合、并发和面向工程。可能对go还不熟悉,很多地方还是没有特别的感觉,学完之后回头看,应该会是不一样的感受吧~3
- lesserror2021-10-15感谢 Tony Bai 这一篇的分享,很精彩。有以下几点困惑,麻烦有时间回答一下: 1. go1.16.4 版本中的 poolLocal 结构体的实现和本文中的不太一样呢? 2. 水平组合“模式”还有点缀器、中间件等方式后面的文章中会有例子吗? 3. 很多课程中,都有“并发原语”一词,百度查了一下,有一些理解。老师这里会有比较通俗易懂的理解吗?展开
作者回复: 你真非常细致和喜欢思考,提出的问题都很棒!👍 第一个问题,专栏中的poolLocal实现的确是早期的版本,不过也无伤大雅,文中的目的就是为了说明:类型嵌入的组合方式。感谢 你指出。不过这块我就不改了。 第二个问题,在讲解接口类型时,应该会有具体例子。 第三个问题,在后面讲解并发的章节中,我希望我的讲解能让你觉得更通俗易懂^_^。
共 2 条评论3 - 迷途书童2022-06-24Go语言的设计哲学有什么权威出处吗?还是老师自己总结的?
作者回复: 自己总结的。但也依据了go核心团队的一些talk中的内容以及个人对go的理解。
2 - Alexhuihui2021-10-23“水平组合是一种能力委托(Delegate),我们通常使用接口类型来实现水平组合。“,原文这段话没理解,老师能再解释一下水平组合吗
作者回复: 这个我在后面讲解接口时会详细展开。
2 - Hua.R2021-10-16搭车问一下,去年我曾在大佬博客咨询过慕课网的专栏出版纸质书的问题,大佬回答当年底出版。大半年过去后没有动静我又问了一次你说已经提交出版社。那大概多久可以看到实物呢?
作者回复: 编辑给我的最新消息是在年末左右。图书的出版周期都长一些。
2 - Holy2022-04-18go底层代码深入发现了很多巧妙的地方, defer+panic简单使用,里面的实现不一般 内存管理多级缓存快速减少锁,为GC埋下众多伏笔, Mutex兼顾公平 GMP模式实现等等, 每个版本迭代,持续进化(GOPHER坐享其成)展开
作者回复: 👍
1 - 麦芽糖2022-01-15设计哲学很重要,就比如一个人的价值观,或者背景。 在不了解别人价值观的时候去评价是不合理的,具备同样价值观的人才能走的更好。 比如国家之前的相互不理解,是因为国家的文化不同。 同样,需要理解 Go 的设计哲学显得非常重要。 设计哲学有 ● 简单 ● 显示 ● 组合 ● 并发 ● 面向工程 简单。 比如关键字就很少。入门快。 显示。 如不同类型不能做运算,避免意料之外的事情发生。 组合。 暂时还不是很理解。 并发。 天生就为多核 CPU 开发,同时有自己的 goroutine 用户层轻量级线程,性能更好。 面向工程。 格式化、调试、工具链、默认参数等。展开
作者回复: “具备同样价值观的人才能走的更好” 👍
1 - 不负青春不负己🤘2021-10-22最新版本Go 支持泛型,也就是不完全显式,算是平衡?
作者回复: 个人感觉go加入泛型更多是出于对社区意见的响应,加上go核心团队的确找到了一条可兼容Go1的实现方案。于是就打算加了。像Go语言之父Rob Pike还是保持谨慎态度,最近不是刚刚发issue,建议go团队不要在1.18标准库中使用泛型,观望一下。
1