说说我为什么不喜欢 Go 语言

我每天的工作都用go语言,我对它很熟悉,但是我并不喜欢go语言,也不懂为什么这语言能火,下面从几个角度阐述。

开发者工程学

    我从没见过一门语言能引发如此公然地敌对开发者工程学。举个例子,Rob Pike(go语言之父)多次公然反复地反对任何关于go语言平台的语法高亮,而且对于很多不错的用户问题,他的公开回答是傲慢而无礼的。

Gofmt编写出来就是为了减少关于代码格式化的无意义的讨论。我很遗憾的说,再多的关于语法高亮的无意义的讨论都是无用功,或者我更愿意称这为“spitzensparken blinkelichtzen”(用于嘲讽输错命令的用户)。

 上述来自于 2012 Go-Nuts thread,除此之外

语法高亮是幼稚的,我三岁学算术的时候都不用彩色棒了(http://en.wikipedia.org/wiki/Cuisenaire_rods)。现在我只用单色的数字符号。

问题在于Rob所关心的人之中没人经历过阅读困难、视觉障碍、联觉。Rob对这个想法的不认可使得Go的官方网站和文档编写一直处于强调自由状态。

Go团队不同于Rob Pick,但是他们在其他方面分享了他对人机工程学的看法,在一个关于union/sum类型的讨论中,用户ianlancetaylor直接拒绝了这一要求,他特别指出了一个符合人体工程学的好处,并认为它太微不足道,不值得费心:

 

从开放源码发行版之前开始,这在过去已经讨论过好几次了。过去的共识是sum类型不会向接口类型添加太多内容。一旦你把它们都整理好,最后你会得到什么,如果一个接口类型,编译器检查你已经填入了一个类型开关的所有情况。对于一种新的语言变化来说,这是一个相当小的好处。

 

这种态度与其他语言中关于union类型的观点不一致。 JWZ 在2000年批评Java时写道:

同样,我认为用于模拟枚举和关键字的可用习惯用语相当蹩脚。 (例如,编译器没有办法发出救命的警告,“枚举值x'没有在switch''处理。)

Java 团队听取了在这方面的批评,现在 Java 可以为 switch 发出此警告在枚举类型。 其他语言 - 包括Rust,Scala,Elixir和朋友等现代语言,以及Go自己的直接祖先,C  - 同样在可能的情况下发出警告。 显然,这种警告很有用,但对于Go团队来说,开发人员的舒适度并不重要,值得考虑。

政治

不,不是邮件列表和会议类型。一种更深入更有趣的类型。

就像每一种语言一样,go是一种政治工具。它体现了一组关于软件应该如何编写和组织的特定信念。在Go的例子中,语言体现了由语言本身强制执行的“熟练的程序员”和“不熟练的程序员”的极其严格的等级体系。

在非熟练的程序员方面,该语言禁止被认为是“太高级”的功能。“go”没有泛型,也没有办法编写更高阶的函数,这些函数泛化于一个以上的具体类型,以及关于逗号、未使用的符号和其他通常可能发生的不道德行为的极其严格的规定性规则。代码。这是GO程序员所生活的世界——一个比Java 1.4更具约束性的世界

在熟练的程序员方面,程序员对这些特性是可信的,并且可以将用它们构建的东西暴露给分歧双方的其他程序员。语言实现包含通用函数,这些函数不能在go中实现,并且满足语言无法表达的类型关系。这就是Go实施者所生活的世界。

我不能说google是go的起源,但在google之外,这种将程序员划分为“值得信赖”和“不值得信赖”的未经分析的政治立场是很多关于语言的争论的基础。

Go Code的打包和分发

go get是一个令人失望的放弃责任。包装边界是通信边界,Go团队对“供应商一切”的回应等于拒绝帮助开发人员彼此就其代码进行通信。

我可以尊重Go团队所采取的立场,这不是他们的问题,但这使得他们与其他主要语言不一致。考虑到C库包管理尝试的灾难性历史以及Autotools的存在作为一个如何在足够长的时间范围内出错的例子,看到本世纪的语言团队洗手是非常令人惊讶的情况。

GOPATH


对所有源使用单个单片路径使得依赖关系之间的版本冲突几乎不可避免。供应商的解决方案部分地解决了这个问题,代价是存在大量的存储库膨胀和非平凡的链接更改,如果在同一个应用程序中链接了相同库的出售和非出售副本,则可能会引入错误。

再一次,Go团队的“不是我们的问题”反应令人失望和令人沮丧。

Go中的错误处理


可能失败的操作的标准Go方法涉及返回多个值(不是元组; Go没有元组),其中最后一个值是类型错误,这是一个接口,其nil值表示“没有发生错误”。

因为这是一个约定,所以它在Go的类型系统中是不可表示的。没有通用类型表示错误操作的结果,可以在其上编写有用的组合函数。此外,它并没有严格遵守:除了良好的意义之外,其他任何东西都不会阻止程序员在某些其他位置返回错误,例如在返回值序列的中间,或者在开始时 - 因此处理错误的代码生成方法是也充满了问题。

在Go中,不可能以任何比一些变化更简洁的方式构成错误的操作

    a, err := fallibleOperationA()
    if err != nil {
        return nil, err
    }

    b, err := fallibleOperationB(a)
    if err != nil {
        return nil, err
    }

    return b, nil

在其他语言中,这可以不同地表达为

    a = fallibleOperationA()
    b = fallibleOperationB(a)
    return b


在有例外的语言中,或作为

  return fallibleOperationA()
        .then(a => fallibleOperationB(a))
        .result()


在具有可以对案例值进行操作的抽象的语言中。

这具有实际影响:执行长序列的错误操作的代码花费大量的打字工作来编写(即使编辑器支持生成分支),并且需要大量的认知努力来阅读。风格指南有所帮助,但混合风格使其变得更糟。考虑:

    a, err := fallibleOperationA()
    if err != nil {
        return nil, err
    }

    if err := fallibleOperationB(a); err != nil {
        return nil, err
    }

    c, err := fallibleOperationC(a)
    if err != nil {
        return nil, err
    }

    fallibleOperationD(a, c)

    return fallibleOperationE()

如果你嵌套它们,上帝会帮助你,或者想要做一些比将错误传回堆栈更有趣的事情。