Go 语言设计失误,缺乏远见?

298次阅读  |  发布于3年以前

前段时间我有一个朋友在某站点上摸鱼时,给我甩来一个主题为《golang 设计者是如何偿还技术债的》链接。

说是让我学习、围观一下社区观点,早日好修成正果,本鱼表示满脸问号。

原回答如下图:

主要是以极短的话语表述 Go 语言的 “泛型、异常、channel、annotation、模块依赖” 的设计是失误的。

说是没有向各种编程语言的 “最佳实践” 各取所需。

那些故事

刚好煎鱼也入门 Go 没几天,偶尔翻过 issues 和 proposal,看了一点点历史事件。

图来自 Introduction to Golang也从我的观点来围观一下 Go 官方这些年为特性挣扎过的那些事。

涉及:

  1. 泛型。
  2. 错误处理。
  3. 依赖管理。
  4. 注解。

泛型

为什么 Go 语言这么久都没有泛型,是不是 Go 官方不够 “聪明”,抄作业都不会抄。这显然是不对的。

有如下几点原因:

  1. 泛型本质上并不是绝对的必需品。
  2. 泛型不是 Go 语言的早期目标。
  3. 其他 feature 更重要,把精力放在这些上面,Go 团队人力很有限的。

历史尝试

在以往的尝试中,Go 团队有人进行过不少的泛型 proposal 试验。基本时间线(via @changkun)如下:

简述 时间 作者
Type Functions 2010年 Ian Lance Taylor
Generalized Types 2011年 Ian Lance Taylor
Generalized Types v2 2013年 Ian Lance Taylor
Type Parameters 2013年 Ian Lance Taylor
go:generate 2014年 Rob Pike
First Class Types 2015年 Bryan C.Mills
Contracts 2018年 Ian Lance Taylor, Robert Griesemer
Contracts 2019年 Ian Lance Taylor, Robert Griesemer
Redundancy in Contracts(2019)'s Design 2019年 Ian Lance Taylor, Robert Griesemer
Constrained Type Parameters(2020, v1) 2020年 Ian Lance Taylor, Robert Griesemer
Constrained Type Parameters(2020, v2) 2020年 Ian Lance Taylor, Robert Griesemer
Constrained Type Parameters(2020, v3) 2020年 Ian Lance Taylor, Robert Griesemer

我们观察一下,10 年过去了,Ian Lance Taylor 依然在开展泛型提案,持续地在思考着 Go 泛型。

坚持思考,这一点值得我们学习。

对 Go 泛型历史有兴趣的读者可以看看《[为什么 Go 的泛型一拖再拖?] 》,给了明确完整的内容介绍和过程描述了。

下一步计划

在 2021 年尾巴的我们,明年(2022年) Go1.18 左右就可以见到 Go 泛型,基本跑不了。

想想就激动,如下图(此刻是 4 个月后):

图来自网上

在出来前可以看看《[Go 1.17 支持泛型了?具体怎么用] 》,可以作为玩具用了。

接下来可以预见泛型出来后,一堆工具库和数据结构很大可能会被逐步改写,像是《[Go 提案:增加泛型版 slices 和 maps 新包] 》,早已摩拳擦掌。

届时 Go 源码类别的书的部分内容也会失时效,需要关注 Go 版本的时效性。

错误处理

在日常工程中,我们写的、看到最多的可能就是这一段标志性 Go 代码:

func main() {
 x, err := foo()
 if err != nil {
   // handle error
 }
 y, err := foo()
 if err != nil {
   // handle error
 }
 z, err := foo()
 if err != nil {
   // handle error
 }
 s, err := foo()
 if err != nil {
   // handle error
 }
}

这是在业内被吐槽的最多的,甚至都可以用来作为 Gopher 的互认。

设计方向

那 Go 是瞎设计的吗,就粗制滥造,搞个错误 err 的返回约定惯例。像是:

func foo() err {
    return nil
}

其实并不是,Go 团队在设计上有意识地选择了显式的设计方向,如下:

这和其他语言不一样 ,是由于 Go 团队也认识到了异常处理的不可见错误检查所带来的问题。

设计草案有一部分是受到了这些问题的启发。如下:

目前 Go 官方也没有打算去掉 “显式” 这一做法,新版 Go2 错误处理的核心目标是:“错误检查更加轻便,减少专门用于错误检查的 Go 程序代码的数量和所花费的时间。”。

从 Go2 的趋势来看,主要是增加关键字和修饰来解决这个问题,相当于是堆积木了,而不是直接把他干掉的。

这在 Go 核心团队内是非常明确的。

进一步深入

对 Go 语言错误处理还想进一步深入的,推荐看下面这几篇文章:

依赖管理

Go 语言在一开始是完全基于 GOPATH 作为依赖管理的模式,当时也闹了不少的争议出来。有以下核心问题:

  1. 依赖要手动拉取和下载,没有强版本化的概念,开发者很难受(例如:不兼容升级、要拉取同一份)。
  2. 依赖和工程代码必须在 GOPATH 下才能运行,不能任意摆放。

所以在 Go1.0~Go1.11 中,各路神仙发招,社区出现了各种诸如 dep、glide、godep 等依赖包管理工具。

时间线

后续 Go 团队在 Russ Cox 的强势推进下,力排众议,推动 Go modules 的发展:

时间线如下:

为什么这么晚

为什么 Go modules 这么晚才诞生,这是不是就是 Go 团队的设计失误呢?

我认为,是也不是。

Go 的诞生一开始是为了解决 Google 几位大佬自己的痛点。

在 Google 的依赖管理上,本身是大仓库(Monorepo)的模式,企业内部有自己一整套工具和流程,设计之初没有这块的强诉求。

如下:

图来自 Mono Repo vs Multi Repo

有兴趣的读者详细可阅读《Why Google Stores Billions of Lines of Code in a Single Repository》,

Go 在社区开源后,大规模使用后这个问题就爆发了,社区自行释出了方案。可惜,五花八门,也都没有解决好。官方队伍就自己上手了。

要知道,没有技术方案是完美的。Go modules 也被不少人所吐槽,存在争议。

进一步深入

想更进一步深入 Go modules 的小伙伴,可以看看下述文章:

注解

Go 开发者中有大部分同学都有其他语言的使用经验。在其他语言中,注解是一个强大的工具,没得用会很不习惯。

图片来自网络

甚至有听过没有注解,就自嘲不会 “写” 代码了,所以一上来就找 Go 语言的注解怎么用了。

一些疑惑

我有一个朋友,经常会听到如下疑惑,甚至无奈的发问:

Go 的 “注解” 支撑的非常有限,基本都是 //go buildgo:generate 这类辅助,达不到标准的装饰器的作用。

为什么不支持

没有全面的支持注解来做装饰器,显然不算 Go 的设计失误,这是刻意为之,这是与错误处理的设计理念相关联。

Go issues 上有人提过类似的提案:

Go Contributor @ianlancetaylor 给出了明确的答复,Go在设计上更倾向于明确的、显式的编程风格。

优缺点如下:

因如下原因,没有接受注解:

可能有小伙伴会说了,有注解做装饰器了,代码会简洁不少。

但其实 Go 团队的态度很明确:

Go 认为可读性更重要,如果只是额外多写一点代码,在权衡后,还是可以接受的。

如果想自己在 Go 中实现完整注解的,可以详细阅读《[Go:我有注解,Java:不,你没有!] 》,可以给到你一些思路。

偿还的过程

如果是在职场中工作多年的小伙伴,其实不难发现 Go 的发展史和业务的发展节奏是类似的。

在社区中吐槽的主要是两块,如下:

不如此设计

为什么 Go 语言不如此设计?经典的像是 Go 的错误处理(error),很多小伙伴会先入为主,以其他语言的最佳实践,要教 Go 团队设计,要 throw,要 catch!

其实想一下,我们做一个业务,这个业务就是 Go 语言。我们需要先做业务建模,确定 Go 的核心思想,才能持续的迭代和设计。

Go 语言的设计定义很明显是:既要简单、还要显式,不能有隐式、要避免复杂,所以社区传递的是 “less is more” 的设计理念。

这么想,很多提案的落地,被拒等,都能了解到 Go 语言的设计哲学和团队理念。

还没有支持

为什么 Go 语言的 XXX 功能没有支持?经典的像是 Go 的泛型、注解等功能。

还没有支持的可能性有三点,如下:

  1. 还没有想清楚。
  2. 早就被拒绝了。
  3. 优先级不够高。

实际上和我们业务迭代一样,Go 团队的人力资源有限,做事会有优先级。前文所提到的 Russ Cox 就是现在 Go 团队 Leader,每年也会开相关的会议讨论事项。

像是 Go 泛型,显然没有,也不会影响到 Go 在业务初期的短期发展,国内依然存有一定的占用率。2011 年没有想清楚,也就一直持续思考和尝试了...

而注解,或是你们想到的。很多在 go issues 其实早就被拒绝过多次,自然还没有支持,也是因为他不大可能直接出现了。

推进的模式

Go 在推进或偿还新技术改进时,现在采取的模式都是一样的。会先设计一个编译时可以指定的 “变量”。

例如:

再在 Go 的不断迭代中,推进使用和反馈,再推进变量的默认开启,逐渐去除。

可以参考 GO111MODULE 的过程。

总结

我们在学习很多语言、技能时,会以既有的知识去认知,再对新的对象建立新的认知树,很容易会有先入为主的认知行为

但若没有及时思考,就很容易产生偏见。认为 XXX 是 XXX,你 Go 语言就应该是 XXX,这样是有失偏颇的

就像我们行业经常讨论的,网上的 A 同学,35 岁被裁员了。那你我,35 岁就 100% 会下岗吗?

相反,Go 语言这 10+ 年来,基于自己的设计理念。保持了大致一贯的 less is more 设计理念,是值得赞许的。

我们要知道软件设计,是没有银弹的。Go 语言的设计理念,有好有坏,社区也有不少人对大道至简的理念嗤之以鼻

你又是怎么看待 Go 语言的呢, 一起来交流和讨论:)

Copyright© 2013-2020

All Rights Reserved 京ICP备2023019179号-8