为什么 Go 不是一款好的编程语言 已翻译 100%

oschina 投递于 2014/12/03 11:26 (共 28 段, 翻译完成于 12-08)
阅读 33680
收藏 95
Go
7
加载中

我喜欢 Go. 常用它实现各种功能(包括在写本文时的这个博客). Go 很实用,但不够好。 不是说它有多差, 只是没那么好而已。 

一门编程语言, 也许会用上一辈子, 所以选择的时候要注意。 

本文专注于 Go 的各种吐槽。 老生常谈的有之,鲜为人知的也有。 

我用 Rust 和Haskell 作为参照 (至少, 我以为, 这俩都很不错)。 本文列出的所有问题, 都有解决方案。 

BreakingBad
翻译于 2014/12/03 15:17
5

常规编程

那么问题来了

我们写代码可以用于许多不同的事情。假如我写了一个函数用来对一列数字求和,如果我可以用该函数对浮点数、整数以及其他任何类型进行求和那该多棒。如果这些代码包含了类型安全并且可以快速的写出用于整型相加、浮点型相加等的独立函数就更完美了。

开源中国七里香
翻译于 2014/12/03 22:34
3

好的解决方案:基于限制的泛型和基于参数的多态

到目前为止,我遇到的最好的泛型编程系统是rust和haskell所共用的那个。它一般被称作”被限制的类型“。在haskell中,这个系统被称作”type class“。而在Rust中,它被称作”traits“。像这样:

(Rust, version 0.11)

fn id<T>(item: T) -> T {
   item
}

(Haskell)

id :: t -> t
id a = a

在上面这个简单了例子中,我们定义了一个泛型函数id。id函数将它的参数原封不动传回来。很重要的一点是这个函数可以接受任何类型的参数,而不是某个特定的类型。在Rust和haskell中,id函数保留了它参数的类型信息,使得静态类型检查可以顺利工作,并且没有为次在运行期付出任何代价。你可以使用这个函数来写一个克隆函数。

同样,我们可以应用这种方式来定义泛型数据结构。例如:

(Rust)

struct Stack<T>{
   items: Vec<T>
}

(Haskell)

data Stack t = Stack [t]

跟上面一样,我们在没有运行期额外消耗的情况下得到完全的静态类型安全。

crab2313
翻译于 2014/12/04 11:24
3

现在,如果我们想写一个通用的函数,我们必须告诉编译器“这个函数只有在它的所有参数支持这个函数中所用用到的操作时,才有意义”。举个例子,如果我们想定义一个将它的三个参数相加,并返回其和的函数,我们必须告诉编译器:这三个参数必须支持加法运算。就象这样:

(Rust)

fn add3<T:Num>(a:T, b:T, c:T)->T{
   a + b + c
}

(Haskell)

add3 :: Num t => t -> t -> t -> t
add3 a b c = a + b + c

在上面这个例子中,我们告诉haskell的编译器:“add3这个函数的参数必须是一个Num(算数数类型)“。因为编译器知道一个Num类型的参数支持加法,所以这个函数的表达式可以通过类型检查。在haskell中,这些限制也可应用于data关键字所做的定义中。这是一个可以优雅地定义百分之百类型安全的灵活泛型函数的方式。

crab2313
翻译于 2014/12/04 11:11
2

go的解决方案:interface{}

Go的普通类型系统的结果是,Go对通用编程的支持很差。

你可以非常轻松的写通用方程。假如你想写一个可以打印被哈希的对象的哈希值。你可以定义一个拥有静态类型安全保证的interface,像这样:

(Go)

type Hashable interface {
   Hash() []byte
}

func printHash(item Hashable) {
   fmt.Println(item.Hash())
}

现在,你可以提供给printHash任何Hashable的对象,你也得到静态类型检查。这很好。

但如果你想写一个通用的数据结构呢?让我们写一个简单的链表。在Go里写通用数据结构的惯用方法是:

(Go)

type LinkedList struct {
    value interface{}
    next  *LinkedList
}

func (oldNode *LinkedList) prepend(value interface{}) *LinkedList {
   return &LinkedList{value, oldNode}
}

func tail(value interface{}) *LinkedList {
   return &LinkedList{value, nil}
}

func traverse(ll *LinkedList) {
   if ll == nil {
       return
   }
    fmt.Println(ll.value)
   traverse(ll.next)
}

func main() {
   node := tail(5).prepend(6).prepend(7)
   traverse(node)
}

发现什么了吗?value的类型是interface{}。interface{}就是所谓的“最高类型”,意味着所有其他的类型都是interface{}的子类型。这大致相当于Java中的Object。呀!(注意:对于Go中是否有最高类型还有争议,因为Go宣称没有子类型。不管这些,保留类比的情况。

Ley
Ley
翻译于 2014/12/03 23:12
2

在Go里面“正确”构建通用数据结构的方法是将对象设置为最高类,然后把它们放入到数据结构中。大约在2004年,Java就是这么做的。后来人们发现这完全违背了类型系统的本意。当你有这样的数据结构时,你完全消除了一个类型系统能提供的所有好处。比如,下面这个是完全有效的代码:

node := tail(5).prepend("Hello").prepend([]byte{1,2,3,4})

而这在一个良好结构化的程序里完全没有意义。你可能期望的时一个整数链表,但在某个情况下,一些疲惫、靠咖啡清醒的程序员在截止日期前偶然在某处加入了一个字符串。因为Go里面的 通用数据结构不知道它们值的类型,Go的编译器也不会改正,你的程序在你失去从interface{}里面捕获时将崩溃。

相同的问题在任何通用数据结构里都存在,无论是list、map、graph、tree、queue等。

Ley
Ley
翻译于 2014/12/05 23:10
2

语言可扩展性

问题

高级语言通常有复杂任务的关键字和符号简写。比如,在很多语言中,迭代一个如数组一样的数据集合中所有元素的简写:

(Java)

for (String name : names) { ... }

(Python)

for name in names: ...

如果我们能在任何集合类型上操作,不仅仅是那些语言内建类型(如数组),会很美好。

如果我们可以定义类型的相加也会很美好,那么我们可以这么做

(Python)

point3 = point1 + point2

鑫鑫向融
翻译于 2014/12/04 13:19
2

好的解决方案:把运算符视作函数

将内建的运算符和某个特别命名的函数对应起来,亦或将关键字视作特定函数的别名,这样做可以很好的解决该问题。

某些编程语言,像Python,Rust和Haskell允许我们重载运算符。我们只需要给我们自定义的类添加一个函数,自此,当我们使用某个运算符的时候(例如”+“),解释器(编译器)就会直接调用我们所添加的函数。在Python中,运算符”+“对应于__add__()函数。在Rust中,”+“运算符在Add这个trait中定义为add()函数。在Haskell中,”+“对应于Num这个type class中的(+)。

crab2313
翻译于 2014/12/04 12:41
2

许多语言都有扩展关键字的方法,例如for-each循环。Haskell没有循环,但是像Rust,Java和Python这样的语言中都有”迭代器“这样的概念使得for-each循环可以应用于任何种类的数据集合结构。

某些人可能会用这个特性做一些很操蛋的事情,这是一个潜在的缺点。例如,某些疯狂的家伙使用”-“来代表两个向量之间的点乘。但这并不完全是运算符重载的问题。无论使用何种语言,都可以写出胡乱命名的函数。

crab2313
翻译于 2014/12/04 12:50
2

Go的解决方案:没有

Go语言不支持操作符重载或者关键字扩展。

那么如果我们想给其他的东西(例如树,链表)实现range关键字的操作怎么办?太糟糕了。这不是语言的一部分。你这能在内建对象上使用range关键字。对于关键字make也一样,它不能给非内建数据结构申请内存和初始化。

crab2313
翻译于 2014/12/04 13:35
2
本文中的所有译文仅用于学习和交流目的,转载请务必注明文章译者、出处、和本文链接。
我们的翻译工作遵照 CC 协议,如果我们的工作有侵犯到您的权益,请及时联系我们。
加载中

评论(158)

chai2010
chai2010

引用来自“句龙胤”的评论

因为这个语言是一个反原则反理论的一个随意设计的东西。那个人,在Unix时代,就沉迷于减少字母:比如CREATE减少为CREAT,现在他把这个CREAT修复了。
但是,他依然在这个Go里把function搞成func,就好象少写tion就能提高开发效率一样。

这不是小问题,这是非常大的问题,这关系到一个语言的原则性,连原则都不注重的语言,还会有什么发展?过眼云烟而已。

java又臭又长,System.out.println,但这才是正道,javascript也是用完整的function,因为省掉tion只会带来坏处。这是一个基本的原则。
func 的缩写好像是 G 公司老板建议的: http://talks.golang.org/2014/hellogophers.slide#29
高克
高克

引用来自“句龙胤”的评论

因为这个语言是一个反原则反理论的一个随意设计的东西。那个人,在Unix时代,就沉迷于减少字母:比如CREATE减少为CREAT,现在他把这个CREAT修复了。
但是,他依然在这个Go里把function搞成func,就好象少写tion就能提高开发效率一样。

这不是小问题,这是非常大的问题,这关系到一个语言的原则性,连原则都不注重的语言,还会有什么发展?过眼云烟而已。

java又臭又长,System.out.println,但这才是正道,javascript也是用完整的function,因为省掉tion只会带来坏处。这是一个基本的原则。

引用来自“简单代码”的评论

+1

引用来自“sikele”的评论

因为IDE进步了,名字再臭再长也有提示。

引用来自“cyper”的评论

说的没错,理论上java中的看起来最长,但其实用起来,java是最短的!(效率是最高的),这是因为: 只要敲so再提示,IDEA就帮你生成了System.out.printf("");并且光标停在引号中间,也就是说在java中只需要输入so就可以了,这得比多少语言简单。 同样function在IDEA中只要敲fu就出来了,不比func更TMD简单?

引用来自“cyper”的评论

本人上班做开发都是使用用IDE的(你不用你NB), 配合zen coding哪用敲那么多.靠,整天比长比短有JB用, 开发效率高,性能又还很牛逼才是王道,另外另忘了。不论是IDEA,WebStorm,PhpStorm,Eclipse, NetBeans, PyCharm他MD都是用java语言写的,。为什么java这么慢的东西能开发出这么多IDE。难道是jetbrains还有一大堆公司傻B了。
因为 直接可以再多个操作系统上使用 ,不用重复开发。对于这么复杂的IDE这个因素我觉得很重要。当然 我不是再说Java慢。
高克
高克

引用来自“糊涂茶”的评论

个人理解,Go语言是目标是提供服务器专用语言,直接生成多线程的代码,但实际上C/C++就可以做的很好,只是人工成本比较高,Google于是搞了个怪物版,GO为了追求服务多线程的执行,改变的比较大,于是就牺牲了语法上的很多东西,语法搞的很不友好。原本C/C++就可以做.

引用来自“中山野鬼”的评论

c++不知道。c看开发工具了。一个团队的业务面稳定的话,c的自身开发工具(包括测试工具)沉淀下来,开发效率也是会很快的。包括模块代码自动生成,甚至你可以做“自定义语法分析”然后映射成标准c代码。自己构建的内部标准数据结构及操作规范手册。c累是累在上述一切都要团队自己积累。除非你有一套第三方库的设计说明文档、测试包和源码。单纯的源码(且不谈二进制库)对代码可维护没什么意义的。c和其他语言不一样,需要调整底层做系统优化。
有理
无锡首席大都督程序员

引用来自“妹子说名字长丁丁长”的评论

别的不说,没有靠谱的断点调试器,傻瓜式的类库,开发环境太糟了,花几个月用go做的服务器,后来彻底重写了,打死不用go

引用来自“WhatisAnt”的评论

liteide就可以断点调试啊。不明白你说的靠谱是什么意思?断点调试还分靠谱不靠谱?
我笑了
W
WhatisAnt

引用来自“糊涂茶”的评论

个人理解,Go语言是目标是提供服务器专用语言,直接生成多线程的代码,但实际上C/C++就可以做的很好,只是人工成本比较高,Google于是搞了个怪物版,GO为了追求服务多线程的执行,改变的比较大,于是就牺牲了语法上的很多东西,语法搞的很不友好。原本C/C++就可以做.
你所说的语法很不友好是什么意思?
W
WhatisAnt

引用来自“妹子说名字长丁丁长”的评论

别的不说,没有靠谱的断点调试器,傻瓜式的类库,开发环境太糟了,花几个月用go做的服务器,后来彻底重写了,打死不用go
liteide就可以断点调试啊。不明白你说的靠谱是什么意思?断点调试还分靠谱不靠谱?
W
WhatisAnt

引用来自“matyhtf”的评论

还是我大PHP好用啊,完全没有这些东西,动态语言的PHP要比静态的C/C++,Java,GO之类灵活太多。PHP想要类似Go的socket网络通信功能,安装一个Swoole扩展就可以。
呵呵
jQer
jQer

引用来自“唐海”的评论

go和nodejs都是很差的语言.node那异步回调也能被大吹特吹,异步回调是C++早已玩烂的东西好吗!
我支持C#,c#ide完善,语言设计精妙,现在c#跨平台能力比java还强,c#的异步语法甩nodejs一百条街

引用来自“jQer”的评论

C++都不算个语言,好吗? 异步回调是C就有的. js中的异步回调来自Scheme - > Lisp方言.

引用来自“唐海”的评论

C没有lambda,回调还是比较原始的,属于第一阶段 c++回调基本就跟javascript差不多,用起来很蛋疼,回调容易撕烂逻辑顺序,属于第二阶段 最好的还是C#的异步回调,属于终极阶段
别闹了, 回调的老祖宗是纯函数式语言, lisp haskell. javascript的回调来自于lisp方言sheme. c#算老几.
leaxoy
leaxoy

引用来自“句龙胤”的评论

因为这个语言是一个反原则反理论的一个随意设计的东西。那个人,在Unix时代,就沉迷于减少字母:比如CREATE减少为CREAT,现在他把这个CREAT修复了。
但是,他依然在这个Go里把function搞成func,就好象少写tion就能提高开发效率一样。

这不是小问题,这是非常大的问题,这关系到一个语言的原则性,连原则都不注重的语言,还会有什么发展?过眼云烟而已。

java又臭又长,System.out.println,但这才是正道,javascript也是用完整的function,因为省掉tion只会带来坏处。这是一个基本的原则。

引用来自“简单代码”的评论

+1

引用来自“sikele”的评论

因为IDE进步了,名字再臭再长也有提示。

引用来自“cyper”的评论

说的没错,理论上java中的看起来最长,但其实用起来,java是最短的!(效率是最高的),这是因为: 只要敲so再提示,IDEA就帮你生成了System.out.printf("");并且光标停在引号中间,也就是说在java中只需要输入so就可以了,这得比多少语言简单。 同样function在IDEA中只要敲fu就出来了,不比func更TMD简单?

引用来自“cyper”的评论

本人上班做开发都是使用用IDE的(你不用你NB), 配合zen coding哪用敲那么多.靠,整天比长比短有JB用, 开发效率高,性能又还很牛逼才是王道,另外另忘了。不论是IDEA,WebStorm,PhpStorm,Eclipse, NetBeans, PyCharm他MD都是用java语言写的,。为什么java这么慢的东西能开发出这么多IDE。难道是jetbrains还有一大堆公司傻B了。
java跨平台 开发周期短
ywzjackal
ywzjackal

引用来自“ywzjackal”的评论

通常上,如果一个人最开始是从事C/C++编程,并且长时间工作在C/C++环境中,然后深入了解过GO后,应该会喜欢上GO。如果这个人长时间工作在Java等其他高级语言中(我不是说C++不是高级语言,我指的是其他高级语言),他们通常会很反感GO。
GO不能跟Java Php Ruby Python .Net Node 甚至是C++比,应为Go仅仅是Better C,没有取代其他语言的意思,GO想向C一样的重视执行效率,又不想太枯燥,它的很多应用是基于RPC的,而且它很擅长这个。

引用来自“jQer”的评论

这是不可能的, GO再怎么升级优化,都无法达到C/C++的性能,这是纯正的C/C++程序员最介意的事情

引用来自“cbh”的评论

go的目标是替代c/c++,而事实上,现在用go的,大部份原来用python,php,ruby的开发人员(这个不是我说的,是写go的这位大神自已说的) 真不知道你是怎么得出这个结论的
第一,GO不可能替代任何语言,没有一种语言适应所以需求和环境,各种语言都是互相补充的。第二,没有调查就没有发言权,大神说的不代表适应所有层次的开发人员。你可以试着让几个经常开发java的人去接触go,然后过几天得到的反馈统计结果。第三,我文字内容有限定条件:“通常上”。
返回顶部
顶部