本文转载自微信公众号《我的大脑是炸鱼》,作者陈建宇。转载本文请联系脑筋急转弯公众号。大家好,我是炸鱼。前段时间,我的一个朋友在某网站钓鱼的时候,给我发了一个主题是?的链接。他说是让我去学习和观察社会的意见,这样我才能尽快取得积极的成果,BenYu说的满脸问号。原答案如下:用很短的词来表达Go语言中“泛型、异常、通道、注解、模块依赖”的设计是错误的。据说各种编程语言都没有“最佳实践”。这些故事恰好发生在我刚开始使用Go几天后。偶尔翻翻议题和提案,略读历史事件。图片来自《Golang入门》,站在我的角度,来看看这些年Go官方为了特性而纠结的事情。涉及:泛型。错误处理。依赖管理。注解。泛型为什么Go语言这么长时间没有泛型?是因为官方围棋不够“聪明”,不会抄作业吗?这显然是错误的。这有几个原因:泛型本身并不是绝对必要的。泛型并不是Go语言的早期目标。其他功能更为重要。为了专注于这些,Go团队的人手有限。历史尝试在之前的尝试中,Go团队的一些成员进行了很多泛型提案实验。基本时间线(via@changkun)如下:时间简介authorTypeFunctions2010IanLanceTaylorGeneralizedTypes2011IanLanceTaylorGeneralizedTypesv22013IanLanceTaylorTypeParameters2013IanLanceTaylorgo:generate2014RobPikeFirst班级类型2015BryanC.MillsContracts2018IanLanceTaylor,RobertGriesemerContracts2019IanLanceTaylor,RobertGriesemer合同冗余(2019)的设计2019IanLanceTaylor,RobertGriesemer约束类型参数(2020,v1)2020IanLanceTaylor,RobertGriesemer约束类型参数(2020,v2)2020IanLanceTaylor,RobertGriesemer约束类型参数(2020,v3)2020IanLanceTaylor,RobertGriesemerWeObserveLook,10多年过去了,IanLanceTaylor仍在开发泛型提案,不断思考Go泛型并坚持思考,值得学习。接下来就是计划2021年结束的我们,明年(2022年)Go1.18左右会看到Go泛型,基本跑不动了。出来之前可以看看《Go 1.17 支持泛型了?具体怎么用》,可以当玩具。接下来可以预见,泛型出来后,很可能会逐步改写一堆工具库和数据结构,比如《Go 提案:增加泛型版 slices 和 maps 新包》,已经在摩拳擦掌。届时Go源码书的部分内容也会过时,需要注意Go版本的时效性。错误处理在日常工程中,我们写的和看到最多的可能就是这段标志性的Go代码:funcmain(){x,err:=foo()iferr!=nil{//handleerror}y,err:=foo()iferr!=nil{//handleerror}z,err:=foo()iferr!=nil{//handleerror}s,err:=foo()iferr!=nil{//handleerror}}这个是在抱怨最多的业界甚至可以作为歌斐的互认。至于设计方向,Go是不是盲目设计的?它是粗制滥造的,并建立了错误的错误返回约定。比如:funcfoo()err{returnnil}其实不是,Go团队在设计中有意识地选择了一个明确的设计方向,如下:Useexpliciterrorresults。使用显式错误检查。这与其他语言不同,因为Go团队也认识到了异常处理这种不可见的错误检查带来的问题。设计草案部分受到这些问题的启发。如下:目前,Go官方不打算去掉“显式”的做法。新版Go2错误处理的核心目标是:“更容易的错误检查,减少专门用于错误检查的Go程序代码量和花费的时间”。从Go2的走势来看,主要是增加关键字和修饰来解决这个问题,相当于堆砌积木,而不是直接把他干掉。这在Go核心团队内部非常清楚。依赖管理Go语言一开始是完全基于GOPATH作为依赖管理模型的,当时争议很大。存在以下核心问题:依赖需要手动拉取下载,没有强版本控制的概念,给开发者带来困难(例如:不兼容的升级,必须拉取同一个副本)。依赖和项目代码必须在GOPATH下运行,不能随意放置。于是在Go1.0~Go1.11,各路大神出手,社区中出现了dep、glide、godep等各种依赖包管理工具。时间线后续在RussCox的大力推动下,Go团队力排众议推进了Gomodules的开发:时间线如下:Gomodules(原vgo)自Go1.11开始推广。从Go1.13开始不再推荐使用GOPATH使用模式。Go1.14表示已经准备好生产(readyforproduction)。为什么这么晚?为什么Gomodules诞生这么晚?这是Go团队的设计错误吗?我想,是和不是。Go的诞生,原本是为了解决谷歌几位大佬的痛点。Google的依赖管理本身就是一个monorepo模型。公司内部有自己的一套工具和流程,在设计之初并没有强烈的需求。如下:图片来自MonoRepovsMultiRepo。有兴趣的读者可以阅读《Why Google Stores Billions of Lines of Code in a Single Repository》了解详情。Go被社区开源后,大规模使用后又爆发了这个问题,社区自行发布了解决方案。可惜问题百出,一个也没有解决。官方团队自己开始了。请注意,没有任何技术解决方案是完美的。Gomodules也被很多人吐槽过,存在争议。注意大多数Go开发人员都有使用其他语言的经验。在其他语言中,注释是一个强大的工具,不使用它们会让人非常不舒服。图片来自网络,还听说没有注解,自嘲不会“写”代码,所以一来就发现了Go语言的注解怎么用向上。一些疑惑我有个朋友经常听到下面的疑惑,甚至无奈地问:“如何在函数前声明,直接开始交易呢?”“为什么Java可以完美注解,而Go不能,很难理解,我不能接受。。。”“Go支持什么级别的注解?”Go的“注解”支持非常有限,基本上都是//gobuild、go:generate等辅助功能,达不到标准的装饰器。为什么不支持没有全面支持注释的装饰器?显然,这不是Go的设计错误。这是故意的,和错误处理的设计理念有关。有人对Go问题提出了类似的建议:GoContributor@ianlancetaylor给出了明确的答案,Go在设计上更倾向于清晰明确的编程风格。优缺点如下:优点:不知道Go加装饰器能得到什么好处,在issue上也没能清楚地论证。缺点:很明显会有意外设置。注释不被接受的原因如下:与现有代码方法相比,该装饰器的新方法并没有提供比现有方法更多的优势,并且大到足以推翻原有的设计思路。社区投票很少(表情投票),用户反馈不多。可能有朋友会说,有注解作为装饰器,代码会简单很多。但实际上,Go团队的态度很明确:Go认为可读性更重要。如果只是多写一点代码,权衡之后,还是可以接受的。如果还款流程是在职场工作多年的小伙伴,不难发现Go的发展历程和业务的发展节奏是相似的。社区的抱怨主要有两个,如下:为什么这个功能不这样设计?为什么不支持此功能?为什么Go语言不是这样设计的呢?我会先入为主,用其他语言的最佳实践来教围棋团队设计、抛、接!其实想想,我们是在做一个生意,这个生意就是Go语言。我们需要先做业务建模,确定Go的核心思想,然后才能继续迭代设计。Go语言的设计定义显然是:要简单明了,不要含蓄,要避免复杂,所以社区传达的是“少即是多”的设计理念。这样一想,很多提案被实施,被否决等等,就可以理解Go语言的设计理念和团队理念了。暂不支持为什么不支持Go语言的XXX函数?Go的泛型和注解等经典函数。暂时还没有支持的有以下三种可能:还没想好。很久以前就被拒绝了。优先级不够高。其实就像我们的业务迭代一样,Go团队的人力资源是有限的,做事也有优先级。上文提到的RussCox是现任Go团队负责人,每年都会召开相关会议讨论相关事宜。像Go泛型,显然没有,也不会影响Go在业务前期的短期发展,在国内还是有一定的占有率的。2011年我没有想清楚,所以我一直在思考和尝试......还有注释,或者你想到的。很多goissue其实早就被拒绝过很多次了,自然没有人支持,也是因为他不太可能直接出现。推进模型Go现在在推进或回报新技术改进时采用相同的模型。首先设计一个可以在编译时指定的“变量”。示例:通用G变量。模块的GO111MODULE变量。在Go的不断迭代中,推动使用和反馈,进而推动变量的默认启用,逐步移除。可以参考GO111MODULE的流程。综上所述,当我们学习很多语言和技能时,我们会利用已有的知识去识别它们,然后针对新的对象构建一个新的认知树。很容易有先入为主的认知行为。但如果不及时想一想,就很容易走偏。认为XXX就是XXX是有偏见的,你的Go语言应该是XXX。我们行业经常讨论,网上的A同学35岁下岗,那你我35岁就100%下岗?相反,Go语言10多年来一直基于自己的设计理念。难能可贵的是,它一直保持着大致一致的lessismore设计理念。我们需要知道软件设计中没有灵丹妙药。Go语言的设计理念有好有坏,社区中很多人对简单的理念嗤之以鼻。
