Go 是一门非常不错的编程语言,并且逐渐取代 Python 成为很多人的首选语言。但它也有一些缺点让很多开发者忍不住吐槽,比如它在函数式编程、通道 / 并行切片处理、内存垃圾回收、错误处理等方面都有一些问题。本文作者将 Go 存在的“硬伤”设计记录了下来,与大家分享、讨论。

Go 是一门非常不错的编程语言。然而,我在公司的 Slack 编程频道中对 Go 的抱怨却越来越多(猜到我是做啥了的吧?),因此我认为有必要把这些吐槽写下来并放在这里,这样当人们问我抱怨什么时,我给他们一个链接就行了。

先声明一下,在过去的一年里,我大量地使用 Go 语言开发命令行应用程序、scc、lc 和 API。 其中既有供客户端调用的大规模 API,也有即将在 https://searchcode.com/ 使用的 语法高亮显示器。

我这些批评全部是针对 Go 语言的。但是,我对使用过的每种语言都有不满。 我非常赞同下面的话:

“世界上只有两种语言:人们抱怨的语言和没人使用的语言。”  ——  Bjarne Stroustrup

1、不支持函数式编程 

我并不是一个函数式编程狂热者。 说到 Lisp 语言,我首先想到的是语言障碍。

这可能是 Go 语言最大的痛点了。 与大部分人不同,我不希望 Go 支持泛型,因为它会为多数 Go 项目带来不必要的复杂性。 我希望 Go 语言支持适用于内置切片和 Map 的函数式方法。 切片和 Map 具有通用性,并且可以容纳任何类型,从这个意义上讲,它们已经非常神奇。在 Go 语言中只有利用接口才能实现类似效果,但这样一来将丧失安全性和速度。

例如,请考虑下面的问题。

给定两个字符串切片,找出二者都包含的字符串,并将其放入新的切片以备后用。

existsBoth := []string{}
for _, first := range firstSlice {
for _, second := range secondSlice {
if first == second {
existsBoth = append(existsBoth, proxy)
break
}
}
}

上面是一个用 Go 语言实现的简单方案。当然还有其它方法,比如借助 Map 来减少运行时间。这里我们假设内存足够用或者切片都不太大,同时假设优化运行时间带来的复杂性远超收益,因此不值得优化。作为对比,使用 Java 流和函数式编程把相同的逻辑重写如下:

var existsBoth = firstList.stream()
.filter(x -> secondList.contains(x))
.collect(Collectors.toList());

上面的代码隐藏了算法的复杂性,但是,你更容易理解它实际做的事情。

与 Go 代码相比,Java 代码的意图一目了然。 真正灵活之处在于,添加更多的过滤条件易如反掌。 如果使用 Go 语言添加下面例子中的过滤条件,我们需要在嵌套的 for 循环中再添加两个 if 条件。

var existsBoth = firstList.stream()
.filter(x -> secondList.contains(x))
.filter(x -> x.startsWith(needle))
.filter(x -> x.length() >= 5)
.collect(Collectors.toList());

有些借助 go generate 命令的项目可以帮你实现上面的一些功能。但是,如果缺少良好的 IDE 支持,抽取循环中的语句作为单独的方法是一件低效又麻烦的事情 。

2、通道 / 并行切片处理 

Go 通道通常都很好用。 但它并不能提供无限的并发能力。它确实存在一些会导致永久阻塞的问题,但这些问题用竞争检测器能很容易地解决。对于数量不确定或不知何时结束的流式数据,以及非 CPU 密集型的数据处理方法,Go 通道都是很好的选择。

Go 通道不太适合并行处理大小已知的切片。

3、多线程编程、理论和实践

几乎在其它任何语言中,当列表或切片很大时,为了充分利用所有 CPU 内核,通常都会使用并行流、并行 Linq、Rayon、多处理或其它语法来遍历列表。遍历后的返回值是一个包含已处理元素的列表。 如果元素足够多,或者处理元素的函数足够复杂,多核系统会更高效。

但是在 Go 语言中,实现高效处理所需要做的事情却并不显而易见。

一种可能的解决方案是为切片中的每个元素都创建一个 Go 例程。 由于 Go 例程的开销很低,因此从某种程度上来说这是一个有效的策略。

toProcess := []int{1,2,3,4,5,6,7,8,9}
var wg sync.WaitGroup
for i, _ := range toProcess {
wg.Add(1)
go func(j int) {
toProcess[j] = someSlowCalculation(toProcess[j])
wg.Done()
}(i)
}
wg.Wait()
fmt.Println(toProcess)

上面的代码会保持切片中元素的顺序,但我们假设不必保持元素顺序。

这段代码的第一个问题是增加了一个 WaitGroup,并且必须要记得调用它的 Add 和 Done 方法。这增加了开发人员的工作量。如果弄错了,这个程序不会产生正确的输出,结果是要么输出不确定,要么程序永不结束。此外,如果列表很长,你会为每个列表创建一个 Go 例程。正如我之前所说,这不是问题,因为 Go 能轻松搞定。问题在于,每个 Go 例程都会争抢 CPU 时间片。因此,这不是执行该任务的最有效方式。

你可能希望为每个 CPU 内核创建一个 Go 例程,并让这些例程选取列表并处理。创建 Go 例程的开销很小,但是在一个非常紧凑的循环中创建它们会使开销陡增。当我开发 scc 时就遇到了这种情况,因此我采用了每个 CPU 内核对应一个 Go 例程的策略。在 Go 语言中,要这样做的话,你首先要创建一个通道,然后遍历切片中的元素,使函数从该通道读取数据,之后从另一个通道读取。我们来看一下。

toProcess := []int{1,2,3,4,5,6,7,8,9}
var input = make(chan int, len(toProcess))
for i, _ := range toProcess {
input <- i
}
close(input)
var wg sync.WaitGroup
for i := 0; i < runtime.NumCPU(); i++ {
wg.Add(1)
go func(input chan int, output []int) {
for j := range input {
toProcess[j] = someSlowCalculation(toProcess[j])
}
wg.Done()
}(input, toProcess)
}
wg.Wait()
fmt.Println(toProcess)

上面的代码创建了一个通道,然后遍历切片,将索引值放入通道。 接下来我们为每个 CPU 内核创建一个 Go 例程,操作系统会报告并处理相应的输入,然后等待,直到所有操作完成。这里有很多代码需要理解。

然而,这种实现有待商榷。如果切片非常大,通道的缓冲区长度和切片大小相同,你可能不希望创建一个有这么大缓冲区的通道。因此,你应该创建另一个 Go 例程来遍历切片,并将切片中的值放入通道,完成后关闭通道。 但这样一来代码会变得冗长,因此我把它去掉了。我希望可以大概地阐明基本思路。

使用 Java 语言大致这样实现:

var firstList = List.of(1,2,3,4,5,6,7,8,9);
firstList = firstList.parallelStream()
.map(this::someSlowCalculation)
.collect(Collectors.toList());

通道和流并不等价。 使用队列去仿写 Go 代码的逻辑更好一些,因为它们更具有可比性,但我们的目的不是进行 1 对 1 的比较。 我们的目标是充分利用所有的 CPU 内核处理切片或列表。

如果 someSlowCalucation 方法调用了网络或其它非 CPU 密集型任务,这当然不是问题。 在这种情况下,通道和 Go 例程都会表现得很好。

这个问题与问题#1 有关。如果 Go 语言支持适用于切片 /Map 对象的函数式方法,那么就能实现这个功能。 但是,如果 Go 语言支持泛型,有人就可以把上面的功能封装成像 Rust 的 Rayon 一样的库,让每个人都从中受益,这就很令人讨厌了(我不希望 Go 支持泛型)。

顺便说一下,我认为这个缺陷妨碍了 Go 语言在数据科学领域的成功,这也是为什么 Python 仍然是数据科学领域的王者。 Go 语言在数值操作方面缺乏表现力和能力,原因就是以上讨论的这些。

4、垃圾回收器 

Go 的垃圾回收器做得非常不错。我开发的应用程序通常都会因为新版本的改进而变得更快。但是,它以低延迟为最高优先级。对于 API 和 UI 应用来说,这个选择完全可以接受。对于包含网络调用的应用,因为网络调用往往会是瓶颈,所以它也没问题。

我发现的问题是 Go 对 UI 应用来讲一点也不好(我不知道它有任何良好的支持)。如果你想要尽可能高的吞吐量,那这个选择会让你很受伤。这是我开发 scc 时遇到的一个主要问题。scc 是一个 CPU 密集型的命令行工具。为了解决这个问题,我不得不在代码里添加逻辑关闭 GC,直到达到某个阈值。但是我又不能简单的禁用它,因为有些任务会很快耗尽内存。

缺乏对 GC 的控制时常令人沮丧。你得学会适应它,但是,有时候如果能做到这样该有多好:“嘿,这些代码确实需要尽可能快地运行,所以如果你能在高吞吐模式运行一会,那就太好了。”

我认为这种情况在 Go 1.12 版本中有所改善,因为 GC 得到了进一步的改进。但仅仅是关闭和打开 GC 还不够,我期望更多的控制。 如果有时间我会再进行研究。

5、错误处理 

我并不是唯一一个抱怨这个问题的人,但我不吐不快。

value, err := someFunc()
if err != nil {
// Do something here
}
err = someOtherFunc(value)
if err != nil {
// Do something here
}

上面的代码很乏味。 Go 甚至不会像有些人建议的那样强制你处理错误。 你可以使用“_”显式忽略它(这是否算作对它进行了处理呢?),你还可以完全忽略它。比如上面的代码可以重写为:

value, _ := someFunc()
someOtherFunc(value)

很显然,我显式忽略了 someFunc 方法的返回。someOtherFunc(value)方法也可能返回错误值,但我完全忽略了它。 这里的错误都没有得到处理。

说实话,我不知道如何解决这个问题。 我喜欢 Rust 中的“?” 运算符,它可以帮助避免这种情况。V-Lang https://vlang.io/ 看起来也可能有一些有趣的解决方案。

另一个办法是使用可选类型(Optional types)并去掉 nil,但这不会发生在 Go 语言里,即使是 Go 2.0 版本,因为它会破坏向后兼容性。

结  语 

Go 仍然是一种非常不错的语言。如果你让我写一个 API,或者完成某个需要大量磁盘 / 网络调用的任务,它依然是我的首选。现在我会用 Go 而非 Python 去完成很多一次性任务,数据合并任务是例外,因为函数式编程的缺失使执行效率难以达到要求。

与 Java 不同,Go 语言尽量遵循“最小惊喜“原则。比如可以这样比较字两个符串是否相等:stringA == stringB。但如果你这样比较两个切片,那么会产生编译错误。这些都是很好的特性。

的确,二进制文件还可以变的更小(一些 编译标志和 upx 可以解决这个问题),我希望它在某些方面变得更快,GOPATH 虽然不是很好,但也没有人们想得那么糟糕,默认的单元测试框架缺少很多功能,模拟(mocking)有点让人痛苦......

它仍然是我使用过的效率较高的语言之一。我会继续使用它,虽然我希望 https://vlang.io/ 能最终发布,并解决我的很多抱怨。V 语言或 Go 2.0,Nim 或 Rust。现在有很多很酷的新语言可以使用,我们开发人员真的要被宠坏了。

作者 | Ben Boyter

译者 | 老夫子

英文原文 :https://boyter.org/posts/my-personal-complaints-about-golang/

为什么go语言还没有火起来?go语言有哪些劣势?相关推荐

  1. python能做机器人吗_最火的Python语言也能做机器人仿真,你会不?

    原标题:最火的Python语言也能做机器人仿真,你会不? 最近接到好多刚踏出大学的学弟学妹们的问候,面临人生中的第一次求职,可有感觉自己没在学校学到一技之长,但又不想随便找份工作将就,那这种情况下该怎 ...

  2. 计算机行业到底还能火多久?

    有人发贴说自己是2020届本科毕业,专业应用数学,打算考研,问该不该考计算机专业,农村贫困家庭,没有什么爱好,只希望以后的工作挣钱就行了,就是害怕三四年以后读研出来计算机行业不行了. 那么,计算机行业 ...

  3. Python还能火多久?

    来自学霸的干货分享! 想了解数据方向的同学一定不要错过哟~ PS:不要收藏,一口气读完. 当然,也可以直接戳评论区看直播回放! 嘉宾背景 李大鹏, 北京工业大学电子信息工程实验班大四毕业生, 已保送至 ...

  4. 传智教育|Java目前市场趋势怎么样?Java还能火多久?

    Java年年被唱衰,于是很多想要学习的小伙伴就开始犹豫不决,总是担心学了之后Java招聘量直线下降,从而陷入不得不内卷的境地. 因此就Java还能火多久这个问题,给大家做一个系统的回答. 1.市场需求 ...

  5. python真的那么火吗-Python语言为什么这么火?

    Python现在已经成为一种非常常见的语言,特别是在当今的数据科学中,由于其易于读写,它越来越受到程序员的追捧.从市场的整体需求来看,对Python人才的需求逐年增加,人气也在逐步上升.那么Pytho ...

  6. “直播带货”还能火多久?

    这一年来,直播带货一直都挺火,从2019年淘宝双十一超过200亿的直播带货的规模,到2020年因为疫情各种线下服装店.超市因为封锁纷纷转战线上,各种老板CEO直播下海,一派生机盎然,加上李佳琦和薇娅, ...

  7. 80% 的学校还在给新生上 C 语言,它们 OUT 了吗?

    作者 | 梁唐 来源 | TechFlow(ID:techflow2019) 头图 |  CSDN 下载自东方IC 大家好,最近有小伙伴在后台问我,大一新生学校在教C语言,是不是已经过时了?第一门语言 ...

  8. C 语言还值得学习吗?C 语言会过时吗?C 语言解惑

    C 语言还值得学吗? 答案是肯定的. 第一,学习C有助于更好的理解C++,Java,C#,Perl以及其他基于C的特性的语言.第一开始就学习其他语言的程序员往往不能很好的掌握继承自C语言的基本特性. ...

  9. 《羊了个羊》还在火!创始人被制成展牌,竟成母校招生“活广告”?

    自从<羊了个羊>爆火之后,身边似乎总能看见各种"打脸"现场: 今天:"以后再玩<羊了个羊>熬夜到一两点我就是狗!" 明天:"什 ...

最新文章

  1. 6D位姿估计Point Pair Feature (PPF)算法详解
  2. 【解决方案】module 'cv2.cv2' has no attribute 'xfeatures2d'
  3. Windows10安装并使用Unity3D项目AirSim教程(附问题解决方案及相关库下载)
  4. 自动化测试===unittest配套的HTMLTestRunner.py生成html报告源码
  5. C#处理JSON 数据
  6. 自动化测试---页面截图
  7. Redis脚本插件之————执行Lua脚本示例
  8. 入门不简单(《Beginning C# Objects中文版》书评)
  9. bzoj 2763: [JLOI2011]飞行路线 分层图
  10. 深入理解 JVM 第三版
  11. Flash Builder实用快捷键集锦
  12. 工具-python实现电影字幕的自动翻译
  13. md5的特点以及加密原理
  14. Python: PS 图像特效 — 模糊玻璃
  15. 【无标题】2021年施工员-装饰方向-岗位技能(施工员)考试题及施工员-装饰方向-岗位技能(施工员)考试试卷
  16. Spring2.5.X与Junit4.5配搭问题
  17. 基于低代码开发平台实现集团战略督办管理系统
  18. Adobe Acrobat XI 闪退问题
  19. Mac安装brew/yum
  20. uc浏览器、QQ浏览器的h5页面,点击分享按钮,分享到微信、qq、微博

热门文章

  1. C语言-验证哥德巴赫猜想
  2. 中国省份名字的由来!
  3. 【面向对象应用~.~】——项目开发团队分配管理软件
  4. 车牌号校验规则,包括新能源车
  5. 树莓派3b+快速编译opencv成功案例指导(保姆级教程)
  6. SpringBoot+Mybatis+Elasticsearch 实现模糊分页查询并标记关键字
  7. 使用 PhpStorm 来开发和 Debug Laravel Sail 项目
  8. docker 镜像启动并完成服务部署
  9. 分析C语言的声明——《C专家编程》
  10. JWT之token机制与双token详解