日英文

Anywhere, it is a good in the past, recall the number of times many, all would be light. 

任何地方,再好的过去,回忆的次数多了,一切也就淡了。

每日掏心话

你知不知道。我用所有的勇气走近你,你的好与不好,我都在不断地发现,不停地接收,我心里能够装下的,全是和你有关的东西。

责编:乐乐 | 作者 | Martin Tournoij | 译者 | 弯月 | 文章转自: CNDN,侵删
链接:arp242.net/go-easy.html

编程技术圈(ID:study_tech)第 1195 次推文

往日回顾:新版Win10来了!网友:丑哭了

     

   正文   

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

事实证明,通过某种方式将一些简单的功能组合在一起,编写出有用的代码可能会非常棘手。在 Ruby 中,如何删除某个数组中的一项?list.delete_at(i)。如何通过值删除条目?list.delete(value)。非常简单!
然而在 Go 中,事情可没有那么容易,为了删除索引 i,你需要执行以下操作:
list = append(list[:i], list[i+1:]...)
为了删除值 v,你必须使用循环:
n := 0
for _, l := range list {
    if l != v {
        list[n] = l
        n++
    }
}
list = list[:n]
这未免也太复杂了?也未必,我认为即使没有 Go 语言经验,大多数程序员也可以看懂上述代码。但它确实不简单。我这个人比较懒,我会从 SliceTricks 上复制这类代码,因为我想专心解决实际问题,不想为这类小事苦恼。
此外,Go 语言也很容易出现使用错误或性能不佳的情况,特别是对于经验不足的程序员而言。例如,我们来比较一下:将上述复制到一个新数组,和复制到一个新的预分配数组
(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
尽管在大多数情况下 1529ns 足够快了,而且也不必过分担心,但是在许多情况下,性能确实很重要,而且拥有能保证实现最佳性能的 list.delete(value)是非常有必要的。
图片
再举一个例子:goroutine。“使用 goroutine 并不难,你只需要添加关键字 go,就可以了!”没错,这样确实可以了,但是如果我需要同时运行 500 万个 goroutine 呢?到时候,你会纳闷,所有内存都去哪儿了?而且你很难避免意外“泄漏”goroutine。
有许多模式可以限制 goroutine 的数量,但哪一种都不简单。下面就是一个简单的例子:
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 的人来说,这段代码非常难以理解。上述代码也不能确保数字会按照一定的顺序输出(这可能是一项需求,当然也可能不是)。
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 语言,但并不能帮助你真正学习这门语言。

PS:欢迎在留言区留下你的观点,一起讨论提高。如果今天的文章让你有新的启发,欢迎转发分享给更多人。版权申明:内容来源网络,版权归原创者所有。除非无法确认,我们都会标明作者及出处,如有侵权烦请告知,我们会立即删除并表示歉意。谢谢!

欢迎加入后端架构师交流群,在后台回复“学习”即可。

在这里,我为大家准备了一份2021年最新最全BAT等大厂Java面试经验总结
别找了,想获取史上最简单的Java大厂面试题学习资料
扫下方二维码回复「手册」就好了


猜你还想看
阿里、腾讯、百度、华为、京东最新面试题汇集
HashMap 面试二十一问!看它就够了!

不要再满屏写 try...catch 了!这个更香!

纠结!拼多多和国家电网 Offer ,我选了活着
嘿,你在看吗?