要学 Go 的赶紧上车

2020-10-03 12:59:17 +08:00
 kidlj

Go 预计在 2021 年开始支持范型,那个时候代码对新手的复杂度将提升一个量级。当前 Go 语言只有一种明显的抽象,就是 interface (接口),范型是另一种,而且比接口要更难以理解和编写、阅读。

2017 年我决定转 Go 的时候,调查了社区的成熟度,比如 kafka,redis,mongo,mysql,pg 等等中间件的库是否完善,那时候这些该有的都已经有了。现在又过了三年,Go 的社区更加成熟了,特别是 Docker 和 Kubernets 主导的云原生生态的崛起,Go 语言占了半壁江山还多。

前一阵坐我背后的一个写 Python 的同事在学习 Go,问我一个简单的问题。一个函数的返回值是 error 类型,被他当作是返回变量,因此代码看不懂。这可能是只写过动态类型语言( python, javascript, php 等)都要面对的一个思维转变。这个时候学习 Go,除了静态类型的思维转变以及 interface 这一层抽象,你会发现 Go 大部分时候像一个动态类型的语言,而且特性非常少(比 Python 少得多),Go 开源代码和标准库也非常 accessible 。

不过当范型推出来以后,虽然从 draft design 来看范型的设计已经非常简单,但对于没接触过静态类型语言的同学来说这是一个不小的挑战,甚至函数的签名都难以分辨。因此这是一个肉眼可见的复杂度提升。而且可以预计的是,当范型可用的时候,社区里大量的开源项目和库将迁移到范型的实现(因为代码更紧凑和通用),我觉得那个时候代码不会像当前一样 accessible 。

所以这个时候上车,用大约半年到一年时间熟悉 Go 的静态类型和 interface,等到范型推出的时候,可以比较轻松地过渡过去。

下面是一些学习资料的推荐:

  1. <The Go Programming Language> 是 Brian Kernighan 和一位 Go 开发组成员 Alan 合写的一本书。虽然这本书出来好几年了,但是 Go 从 1.0 以后就没怎么变化,所以还是很适用。推荐阅读英文版,中文版在大部分时候翻译得不错,但是在一些难以理解的部分,翻译并不能让事情更容易理解,甚至还会出现错误。
  2. Web 框架推荐 Echo 。写过 Node.js 的同学会发现,Echo(或 gin ) 就是 Express 或 Koa 的翻版,都是 middleware 和 c.Next 的执行方式,属于极简框架,很容易上手。
  3. Go 的并发很简单,只有 Goroutine 和 channel 一种方式。官方出的那本书里讲解得非常清晰,必读。不过一开始如果理解起来有困难的话,甚至可以跳过,因为很多时候不太用得着。比如用 Echo 框架来写业务,大部分时候不涉及并发,并发是在 Echo 框架的层面实现的(一个请求一个 goroutine ),所以业务代码就不需要并发了。
  4. Go modules 很好用。新推出不久的 pkg.go.dev 可以查询某个库被哪些开源项目 import,可以方便地学习这个库的使用方式。比如想看哪些开源项目使用了 Echo 框架,点开 Echo 的 import by tab 就看到了。这里是示例: https://pkg.go.dev/github.com/labstack/echo/v4?tab=importedby

我写 Go 有两年时间,肯定不算是一个 Go 的高手,但也不害怕去阅读一些大型项目的代码,比如 Kubernetes 。这就是 Go 的魔力,very accessible 。就像上面说的,当范型被大量运用以后,难度应该要提高一个量级。这是我的一点点经验,分享给大家,希望有帮助。

24607 次点击
所在节点    Go 编程语言
180 条回复
Nugine0
2020-10-04 21:04:55 +08:00
@reus
前面人给的 rust 代码是错的,你却拿这个举例,算不算被钓鱼?
我在谈事实,你却直接攻击别人“双标”。看来这就是某些人的素质,真是“高”啊。

1. rust 的真正写法是 read(&mut buf)?,或者用两个分支的 match 分别处理成功和失败。成功分支中有可能是已读完的情况。
go 的 if 是 fall though 的,需要理解的代码范围更多。
你以为我要说 rust 更优雅?错了。
我要说的一直是:go 自称简单,但实际上并不简单。
一个自称简单的语言和一个公认难学的语言比心智负担,说出来丢不丢人?

2. 我定义的正常函数就是要么成功要么失败,语义不同就是增加负担,对人不友好就是反人类,还要怎么解释?
io 本来就有固有的复杂度,却在这里节省一次调用,简直莫名其妙。
我要不要再去研究一下“节省”和“不节省”的等价性?我认为这就是设计失误。你开心就好。

3. rust 的 lint 的确是语言机制的一部分,具体表现为 rustc 内置 lint 和 clippy 扩展 lint 。我还说 doc 和 tests 也是 rust 语言机制的一部分,请问有没有意见?

再强调一下我的论点:go 自称简单,但实际上并不简单。

能看成捧一踩一的,你开心就好。
reus
2020-10-04 21:23:31 +08:00
@Nugine0
错的?他只不过将需要处理的情况都写成 match 分支,到你嘴里就成了错的?
read(&mut buf)? 根本没处理 Interrupted 。
用两个 match 分支处理,那分支里面的代码被你吃了? Ok 里面需要处理 0 和非零的情况,Err 里面又有需要特别处理 Interrupted 的情况。
我可没说过 go 简单,谁和你说 go 简单,你就和他去辩论,别找我,别自己竖个靶子就来打。
事实?你的观点充满了偏见,拿自己的定义当普遍真理,和你讨论,纯粹就是 flamewar,毫无价值可言。
Nugine0
2020-10-04 21:31:49 +08:00
@reus 再换成 C,没人会说 C 难吧。
看看 C 有什么,基于错误码的错误处理,预处理器。
C 也要挨个判断返回值,但人家有宏。
go 稍微给点语法糖都不会被喷 if err != nil,但还是坚持,估计以后也会一直被喷。

Linux 内核是用 C 写的,是个人都能轻松学会 C,能读 C 代码不代表能懂。
go 所谓的简单,真的能让人“懂”大型项目?光阅读有什么用?我看未必,把代码复杂度和语法复杂度混淆起来而已。

go module 就凭空增加了一项学习成本,将要出来的泛型也是。

减掉的复杂度最终都会加回来,成为内卷的工具,等着瞧。
hefish
2020-10-04 21:35:01 +08:00
go 的面向对象有点不适应。反而是 php 的面向对象语法居然挺适应。。。ε=(´ο`*)))唉。。。
Nugine0
2020-10-04 21:39:16 +08:00
@reus
编译不过就是错,不知道你口中的“错”还能是什么?
你认为我有偏见,你这说法本身就是偏见。搁这套娃呢,火气真大。

我讲了设计失误,你上来就是“双标”,谁有偏见?
reus
2020-10-04 21:40:51 +08:00
@Nugine0 我操,我在这个帖子里讨论了半天的错误处理,是你自己忽然扯出来什么“go 自称简单,但实际上并不简单。”。我有和谁讨论过简单不简单的问题?????????
你能不能不要自说自话?
语法糖个屁啊,我就是用一个函数替代 if err != nil 的。喷的都是不会的,就知道人云亦云。
有学习成本怎么了?又不是弱智,学不会还是怎么了?
你和 go 有什么深仇大恨你自己解决,你不同意楼主的说法你就直接发帖,别 @ 我,我不和你讨论这些。
reus
2020-10-04 21:55:10 +08:00
@Nugine0 编译不过??示意代码还要管能不能编译??

要看能编译的是吧?

https://github.com/cpjreynolds/rivet/blob/de560ca3f6cc3f57832e62da811f1c3fd38cc495/src/io.rs#L13

https://github.com/jonhoo/volley/blob/d115aaba24cce1c939d79594bc4f9ff5c5299fa8/servers/rust/main.rs#L55

https://github.com/isamert/scheme.rs/blob/acc8dca48c87abeaeb3e75d8790cfd516fb5a6f9/src/utils/chars.rs#L30

随便都能搜出现成的用例,你要不要去告诉他们,他们这样写是错的?真正的写法是 read(&mut buf)? ?或者 match 用两个分支才是对的,用四五个就是不对?
Nugine0
2020-10-04 21:59:09 +08:00
@reus 写函数替代 if err != nil 不能改变控制流,用 panic/recover 又与你之前的言论矛盾,我很好奇你是怎么写的。

“就算返回的是指针,且函数有问题,那后面如果遇到解指针,那就会直接 panic,表明程序需要改,是不需要写代码做检查的。”
CosimoZi
2020-10-04 22:00:26 +08:00
求求你们学点 plt 吧
Nugine0
2020-10-04 22:02:16 +08:00
@reus 绝了,我说“编译不过就是错”,没说必须要用两个分支,真能脑补。
我也没说 rust 更优雅,我说的是 go io.Reader 是没有 union type 造成的设计失误。
Nugine0
2020-10-04 22:14:09 +08:00
@reus 火气这么大,不如去写点代码冷静一下,然后再来教别人怎么错误处理。
reus
2020-10-04 22:39:39 +08:00
@Nugine0 就是用 panic / recover 啊,矛盾在哪里??你对我那句话的理解,明显有严重偏差。

以标准库的 errors.As 为例,这个函数只返回一个 bool,对于误用的情况,全部直接 panic: https://github.com/golang/go/blob/9706f510a5e2754595d716bd64be8375997311fb/src/errors/wrap.go#L77

我那句话的意思是,有问题的程序,会直接 panic,而不需要写代码掩盖代码本身的问题。errors.As 也没有返回 error,就是基于这样的设计理念。

这和用 panic / recover 处理错误,根本毫无关系。

用 panic / recover 处理错误的方式,go 标准库里面就有: https://github.com/golang/go/blob/869c02ce1f635960bfc2f06bb52e2b4e17eaa199/src/encoding/gob/error.go#L17
当然,我用的比这个复杂一些,例如 if err != nil,是放到 error_ 里面的。

我就想问,你们这些喷 if err != nil 的人,知道这个事实吗?知道 go 可以不用 if err != nil 处理错误吗??

“前面人给的 rust 代码是错的”
“rust 的真正写法是 read(&mut buf)?,或者用两个分支的 match 分别处理成功和失败。”
别改口,v2ex 是不能修改历史发言的,你说过什么,想表达什么意思,清清楚楚。

对着你这种明明水平不高,还满嘴“正常人思维”、“反人类”、“心智负担”、“大道至简”的人,能不火起的人,我会非常佩服他。
Nugine0
2020-10-04 23:09:52 +08:00
@reus

你对我那句话的理解,明显有严重偏差。

“错”是指编译不过,“真正”的写法是指能编译通过的写法。非得说成这样才明白?

你说过什么,想表达什么意思,清清楚楚。

你说 panic 是代码本身的问题。

但 error 显然是运行时的问题,二者不能混为一谈。这不是我一个人的看法,网上文章多了去了。

能用 panic/recover,不代表 if err != nil 就是好的。别人说事物 A 不好,你说可以用事物 B,并反过来指责别人,真是荒谬。

我倒是很佩服能莫名其妙发火的人,别误会,说的不是你。
Nugine0
2020-10-04 23:23:53 +08:00
@reus 用 panic/recover 来处理 error 就是一种混淆的做法,不然为什么不设计成 try/catch ?

当然,你完全可以不理会我的看法,毕竟我“水平不高”。
reus
2020-10-04 23:25:57 +08:00
@Nugine0 得了吧,你就是那种明明做了,却非要说不是,非要不认的人。
傻子都知道你想说的是谁,你非要此地无银,“别误会,说的不是你”,真是笑死个人。
就是不肯承认自己说错了,非要诸多辩解。
你在其他帖子里怎样吹 ? 运算符,以为没有记录吗?忽然发现 ? 不能包打一切了,忽然发现别人的多个分支的 match 也是对的,就来死嘴硬,说什么编译过了才是“真正的写法”。
谁不明白你那点心思啊?
我又什么时候说过 if err != nil 好了啊?你能不能把你这毛病改改,别给别人加戏啊?
“go 稍微给点语法糖都不会被喷 if err != nil,但还是坚持”,我这不是告诉你用函数就可以替代 if err != nil 吗?倒成了我的不是了?
reus
2020-10-04 23:32:53 +08:00
@Nugine0 你这个问题,就等于“豆腐花放糖就是一种混淆的做法,为什么不放盐?为什么不放卤水?”

你没发现你提出的,都是一些甜咸问题吗?

甜的就是反人类,咸的就是不反人类,诸如此类。

我当然不想理会你这种无聊的问题。
Nugine0
2020-10-04 23:52:39 +08:00
@reus 语法糖和函数一样?不然为什么有 check 语法的提案?

学到个教训,不说清楚就会引来死缠烂打。我原来的意思就是编译错误,因为 io Error 和 io ErrorKind 不同。

前提都是错的,那什么结论都有可能。你非要往其他方向理解,那你说的都对。
Nugine0
2020-10-04 23:58:55 +08:00
@reus

有问题的程序,会直接 panic,而不需要写代码掩盖代码本身的问题。

用 panic / recover 处理错误,正是掩盖了代码本身的问题。

这不是双标,那什么是双标?
blless
2020-10-05 03:21:09 +08:00
叹为观止…原来讨厌 go 的程序员过不去的坎居然还是 if err,有这功夫我 ide 一套快捷键都搞完了
cmdOptionKana
2020-10-05 08:27:29 +08:00
@blless 而且 goland 还可以简化显示 if err,连阅读代码的问题都没有。

这是一个专为移动设备优化的页面(即为了让你能够在 Google 搜索结果里秒开这个页面),如果你希望参与 V2EX 社区的讨论,你可以继续到 V2EX 上打开本讨论主题的完整版本。

https://yangjunhui.monster/t/712344

V2EX 是创意工作者们的社区,是一个分享自己正在做的有趣事物、交流想法,可以遇见新朋友甚至新机会的地方。

V2EX is a community of developers, designers and creative people.

© 2021 V2EX