前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >Go语言并不简单

Go语言并不简单

作者头像
子润先生
修改2021-06-17 10:24:06
2930
修改2021-06-17 10:24:06
举报
文章被收录于专栏:用户8644135的专栏

Go 不是一种很简单的编程语言。尽管它的许多方面都很简单:语法很简单,大多数语义也很简单。然而,语言不仅仅是语法,我们希望利用它编写出实用的代码。利用 Go 编写有用的代码并不总是那么容易。

事实证明,通过某种方式将一些简单的功能组合在一起,编写出有用的代码可能会非常棘手。在 Ruby 中,如何删除某个数组中的一项?list.delete_at(i)。如何通过值删除条目?list.delete(value)。非常简单!

然而在 Go 中,事情可没有那么容易,为了删除索引 i,你需要执行以下操作:

代码语言:javascript
复制
list = append(list[:i], list[i+1:]...)

Go

Copy

为了删除值 v,你必须使用循环:

代码语言:javascript
复制
n := 0
for _, l := range list {
    if l != v {
        list[n] = l
        n++
    }
}
list = list[:n]

Go

Copy

这未免也太复杂了?也未必,我认为即使没有 Go 语言经验,大多数程序员也可以看懂上述代码。但它确实不简单。我这个人比较懒,我会从 SliceTricks 上复制这类代码,因为我想专心解决实际问题,不想为这类小事苦恼。

此外,Go 语言也很容易出现使用错误或性能不佳的情况,特别是对于经验不足的程序员而言。例如,我们来比较一下:将上述复制到一个新数组,和复制到一个新的预分配数组

代码语言:javascript
复制
(make([]string, 0,len(list))):

InPlace             116ns/op      0 B/op   0 allocs/op
NewArrayPreAlloc    525ns/op    896 B/op   1 allocs/op
NewArray           1529ns/op   2040 B/op   8 allocs/op

Go

Copy

尽管在大多数情况下 1529ns 足够快了,而且也不必过分担心,但是在许多情况下,性能确实很重要,而且拥有能保证实现最佳性能的 list.delete(value)是非常有必要的。

图片

再举一个例子:goroutine。“使用 goroutine 并不难,你只需要添加关键字 go,就可以了!”没错,这样确实可以了,但是如果我需要同时运行 500 万个 goroutine 呢?到时候,你会纳闷,所有内存都去哪儿了?而且你很难避免意外“泄漏”goroutine。

有许多模式可以限制 goroutine 的数量,但哪一种都不简单。下面就是一个简单的例子:

代码语言:javascript
复制
var (
    jobs    = 20                 // Run 20 jobs in total.
    running = make(chanbool, 3) // Limit concurrent jobs to 3.
    done    = make(chan bool)    // Signal that all jobs are done.
)

for i := 1; i <= jobs; i++ {
    running <- true //Fill running; this will block and wait if it's already full.

    // Start a job.
    go func(i int) {
        defer func() {
           <-running      // Drain runningso new jobs can be added.
            if i == jobs {// Last job, signal that we're done.
                done <-true
            }
        }()

        // "dowork"
        time.Sleep(1 *time.Second)
        fmt.Println(i)
    }(i)
}

<-done // Wait until all jobs are done.
fmt.Println("done")

Go

Copy

我加了一些注释是有原因的:对于不熟悉 Go 的人来说,这段代码非常难以理解。上述代码也不能确保数字会按照一定的顺序输出(这可能是一项需求,当然也可能不是)。

Go 的并发原语很简单且易于使用,但是将它们组合起来,解决常见的现实问题就没有那么简单了。

RichHickey 在“Simple Made Easy”中提出,我们不应该将“简单”与“易于编写”混为一谈:即便你只需编写一两行代码,也并不意味着底层的概念很简单(这里的简单指的是浅显易懂)。

这句话值得人寻味。在大多数情况下,我们不应该为了“易于编写”而牺牲“简单”。但这并不意味着我们不应该考虑如何让编程更加简单。即便概念很简单,也并不意味着易于使用,人们可能会错误地使用,或使用的方式会引发 bug。将 Hickey 的论点推到极致,就会出现 Brainfuck 之类语言,当然这很愚蠢。

理想情况下,编程语言应该减少推理其行为所需的认知负担,增加这种认知负担的方法有很多:复杂的语言功能就是其中之一;而人们不得不花费精力实现一些简单的概念也是一种负担,因为我需要多考虑一段代码。尽管我不太关心代码格式或语法选择,但我认为减少阅读代码时的认知负担很重要。

缺少泛型是导致 Go 不那么简单的部分原因。现在很难实现 slices 包之类以通用的方式完成的操作。而泛型可以让这成为可能,同时也会让编程变得更复杂(使用了更多的语言功能),但是它们也可以让编程更加容易,并降低其他方面的复杂性。

这些是无法克服的问题吗?不,我仍然会使用 Go,而且也会一如既往地喜欢 Go。但是,我不认为 Go 是你“可以在 5~10 分钟之内学会的语言”。

归根结底,学习语言不仅仅要学习编写 if 和 for 的语法,你需要学习的是思维方式。我见过许多 Python 或 C#开发人员尝试在 Go 语言中实现那些语言的某些概念或模式。常见的做法包括将结构嵌入作为继承,将 panics 作为异常,通过 interface{}实现“伪动态编程”等等。这些做法很难取得良好的结果。

当第一次编写 Go 程序时,我也犯了同样的错误,这是很自然的事情。在刚接触 Ruby 的时候,我曾尝试用 Ruby 编写 Python 代码(由于这两种语言很相似,所以结果相对好一点,但仍然有很多奇怪的做法,比如使用 for 循环)。

这就是为什么我不喜欢人们通过 Go 教程学习这门语言的原因,教程只会讲解基本的语法,还有其他的一些知识。这只能让你大致感受一下 Go 语言,但并不能帮助你真正学习这门语言。

本文系转载,前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文系转载前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档