要学 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 。就像上面说的,当范型被大量运用以后,难度应该要提高一个量级。这是我的一点点经验,分享给大家,希望有帮助。

24608 次点击
所在节点    Go 编程语言
180 条回复
pursuer
2020-10-05 09:06:37 +08:00
@blless 不使用 go 的原因其实也有其他的,比如我需要热重载(动态加载),go 的支持就不太行,最多只能用 plugin,还只有 linux 能用,像 java,c#,c/c++都有动态加载方案,java 和 c#甚至还能共用符号空间,c/c++其实也有一些第三方 elf 加载器可以管理符号空间。然后就是像 c#,kotlin,java 也有不少语法糖和类型推断能力,go 相比较没有什么优势了。不过语言优劣争来争去确实意义不大,符合需求就好。
reus
2020-10-05 09:34:41 +08:00
@Nugine0 最开始的四个分支的 match,就是用来说明 rust 可以怎样处理各种情况的,你说它错,又提出用 ? 才是“真正写法”,或者用两个 match 分支,你是对原先多分支 match 的完全否定,甚至说我“被钓鱼”。然而我已经找出了实际的用例给你看了,别人并不是用 ? 运算符,也不是只用两个分支的 match 来处理,完完全全就是像最开始另一位网友给出的示意代码那样,匹配 Ok 的两种情况和 Err 的两种情况。

你要是不记得自己说过什么,我给你复制出来:
“前面人给的 rust 代码是错的,你却拿这个举例,算不算被钓鱼?”
“ rust 的真正写法是 read(&mut buf)?,或者用两个分支的 match 分别处理成功和失败。成功分支中有可能是已读完的情况。”

你要是非认为前面人给的 rust 代码是错的,那没影响,我可以改用前面 github 上的三个现成的实例。
麻烦拿你“真正的写法”,或者“两个分支的 match”,去给这些项目提 pull request 。别人接受了,你这段话,才是对的。

我看你根本就是不懂 go 的,又说你好奇我怎样用 panic / recover 处理错误,结果我给出代码了,你对于关键的问题,完全没了解。
https://github.com/golang/go/blob/869c02ce1f635960bfc2f06bb52e2b4e17eaa199/src/encoding/gob/error.go#L36
这一行是关键,用 panic / recover 处理错误,只会 recover 特定类型的错误,并不会 recover 所有 panic 。
所以,如果有其他方式的 panic,那是会再次抛出的,根本不会被转化成 error 。
panic / recover 处理错误,处理的是预期中的错误,也就是不用 panic / recover 也要处理的那一类错误
而非预期的错误,因为不可能是预定义的类型,所以根本就不管,仍然是继续 panic,不会被 recover
你还不是水平低下?这么简单的事实,你自己理解错误,反而说我双标?
Hanggi
2020-10-05 10:07:40 +08:00
Go 有类似 Spark 或 Flink 这种分布式系统吗?
Nugine0
2020-10-05 10:16:39 +08:00
@reus
我看你是根本不懂 rust 。

read 的返回值是 Result<usize, io::Error>,不能用 ErrorKind 直接匹配,这是我说“错”的原因。

两分支是最原本的写法,用来论证“要么成功要么失败”,进一步论证 go 双元组的设计不好。

你说的四分支是等价版本,把直接跳转改成了 fall through,本质上是按情况处理 union type 的两个可能模式。

我说 14+29=42 不对,21+21=42 是对的。你偏要说 10+10+10+12=42 也是对的,反过来喷我,我也是服了。

变点写法就看不懂了?某一层封装一下,不就能用问号运算符了?这么简单的事实,还用我专门说?

标准库用问号运算符,把 Interrupt 暴露出去,一搜一大把,你难道也要说不对不对别人不是这么写的?

https://doc.rust-lang.org/src/std/io/buffered.rs.html#274

回到 go 上

https://dave.cheney.net/tag/panic

"When you panic in Go, you’re freaking out, it’s not someone elses problem, it’s game over man."

把 panic 当 exception 用,就是违反语言机制设计意图的,就是一种滥用。

再怎么 panic/recover,作者也不敢越过库的边界,无法代替 err 。

我说这是混淆的写法,有何问题?
wangxiaoaer
2020-10-05 10:19:09 +08:00
这个假期原本是那么的无聊和枯燥,这个帖子的出现让它不再单调。
Nugine0
2020-10-05 10:34:42 +08:00
@reus

https://eli.thegreenplace.net/2018/on-the-uses-and-misuses-of-panics-in-go/

https://golang.org/doc/effective_go.html#recover

标准库为了简化错误处理去用 panic,违背设计意图,恰恰说明了 err 的繁琐,if err != nil 连标准库的人也不想写。

能用不代表它就是好的。
plko345
2020-10-05 10:51:29 +08:00
@reus rust 能解决空指针
reus
2020-10-05 11:06:37 +08:00
@Nugine0 你想论证的“要么成功要么失败”,是假命题。因为实际情况有四种或者以上,不是只有两种,你的论证不成立。
请不要试图篡改事实,事实是别人说了 10+10+10+12 = 42,是你自己跳出来说 21 + 21 = 42 才是“真正写法”,是你自己首先否定 10+10+10+12 = 42,而我没有否定任何一种写法。
不要再给我加戏,再说一次。

Dave Cheney 是语言设计者吗?他自己的观点,就能当成设计意图?

看看 go 官方博客里怎么说的吧: https://blog.golang.org/defer-panic-and-recover

“For a real-world example of panic and recover, see the json package from the Go standard library. It encodes an interface with a set of recursive functions. If an error occurs when traversing the value, panic is called to unwind the stack to the top-level function call, which recovers from the panic and returns an appropriate error value (see the 'error' and 'marshal' methods of the encodeState type in encode.go).”

滥用?官方博客直接告诉你可以这样用了。

不越过库边界和这有什么关系??说的是替代 if err != nil 这种写法,而不是替代 error 。
承认 go 开发者可以不用写 if err != nil,有那么难吗?
更不用说很多人根本就不觉得写 if err != nil 有什么问题,就你这种根本就不用 go 的人在瞎逼逼
dyume
2020-10-05 11:10:48 +08:00
java 只学了一点皮毛,可以直接学 go 吗?
reus
2020-10-05 11:24:15 +08:00
@Nugine0 这就很搞笑了,你帮我贴出了一篇支持我的观点的文章
你贴出的 effective go 里明确写了:
“With our recovery pattern in place, the do function (and anything it calls) can get out of any bad situation cleanly by calling panic. We can use that idea to simplify error handling in complex software. ”
不用 panic / recover 处理错误,你就在那里喷 if err != nil 繁琐
用了 panic / recover 处理错误,你就在那里喷违背设计意图
你就没想过 panic / recover 本来就包含了这个意图?
怎么你都能喷
你就是想喷而已

看看你转移了多少次话题吧:
“go 稍微给点语法糖都不会被喷 if err != nil,但还是坚持,估计以后也会一直被喷。”
告诉你可以用函数替代了
“写函数替代 if err != nil 不能改变控制流,用 panic/recover 又与你之前的言论矛盾,我很好奇你是怎么写的。”
告诉你怎么写了
“用 panic/recover 来处理 error 就是一种混淆的做法,不然为什么不设计成 try/catch ?”
告诉你官方也推荐这么用了
“能用不代表它就是好的。”
呵呵,连万能句式都出来了
reus
2020-10-05 11:32:12 +08:00
@plko345 把 expect 、unwrap 这些可能 panic 的方法都删除吧
ztxcccc
2020-10-05 11:33:43 +08:00
@blless 你要简单为啥不用 PHP
ConradG
2020-10-05 11:52:53 +08:00
核心问题其实就一条,golang 不是作为通用高级语言而设计出来的,但是很多人却非要它来做通用高级语言做的事。
mx8Y3o5w3M70LC4y
2020-10-05 11:54:27 +08:00
啊?还有不支持泛型的语言??
blless
2020-10-05 12:00:41 +08:00
@ztxcccc 首先,我说的简单不止是语法简单,是语言表达足够简单,php 符号我就不说了各种?$->还有一堆语法你觉得比 go 简单我没话说。
另外,动态语言跟静态编译语言完全两码事,动态语言不能编译时发现很多代码问题在软件工程上就已经很致命了,再加上动态运行时的性能问题。大型软件用动态语言我只能表示服气好吧?你没看上面都是讨论 rust java 啥的,你 php 边上凉快看戏不爽吗
Nugine0
2020-10-05 12:06:47 +08:00
@reus

四种或以上的情况难道不能归类为 成功 和 失败?别人贴的模式匹配是错的,难道不能指出来?写法是等价的,你看不出来,好意思说我?也奉劝你,不要再给我加戏。

语法糖不等于函数,明明是你在转移话题。

上网查查,panic 在编程语言中的语义就是恐慌,是致命的。我也明确说了,把 panic 当 exception 就是滥用? Dave 确实不能代表设计意图,我也不能断定,难道你就能代表?懂的都懂,比你我更懂,行了吧?

我看是没有必要再回了,继续下去没有任何意义。
reus
2020-10-05 12:34:14 +08:00
@Nugine0 那我就继续更新呗:
“go 稍微给点语法糖都不会被喷 if err != nil,但还是坚持,估计以后也会一直被喷。”
告诉你可以用函数替代了
“写函数替代 if err != nil 不能改变控制流,用 panic/recover 又与你之前的言论矛盾,我很好奇你是怎么写的。”
告诉你怎么写了
“用 panic/recover 来处理 error 就是一种混淆的做法,不然为什么不设计成 try/catch ?”
告诉你官方也推荐这么用了
“能用不代表它就是好的。”
呵呵,连万能句式都出来了
“语法糖不等于函数”
明明你不想写 if err != nil,那现有的机制就能解决,怎么变成非语法糖不可了?
这就是明显的转移话题,试图把话题从“if err != nil”,转移到“语法糖”

用 panic / recover 可不是我提出来的,是官方标准库,官方博客,官方 FAQ 里都有的。
别试图偷换概念。
fpure
2020-10-05 12:37:01 +08:00
又见识了一场语言圣战
sharpy
2020-10-05 15:14:23 +08:00
Result Option 这些结构只要支持代数数据类型都能自己写吧,这有啥可争的?
Nugine0
2020-10-05 17:35:19 +08:00
@reus 看来你还没懂为什么现有机制不好用,不够用,不合适用。

if err != nil 的处理通常是提前返回,处理起来非常重复繁琐。但函数不能改变控制流,panic 或类似 try 和 check 的新语法才能改。

panic/recover 是用来处理致命错误的,这是共识。拿它来代替普通错误处理是不合适的,这也是常见观点。跨越库的边界时,还得老老实实地判断错误。

真合适的话,官方为什么不设计成 try/catch ?真够用的话,那些官方参与的错误处理提案和讨论难道在放屁?

以前还有 go 不需要泛型的说法,现在呢?

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

https://yangjunhui.monster/t/712344

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

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

© 2021 V2EX