关于Go语言,你可能会讨厌的五件事

近年来,Go从新出现的编程语言中脱颖而出。不过要把Go称为“新晋者”似乎并不合适,因为谷歌早在2009年就推出了Go,并于2012年发布了第一个最终版(Go 1.0)。到现在为止,Go已经发展到了1.10版本,这个版本令人印象深刻,而且还在不断添加新的特性。

\\

为什么它被称为eGOtistic(自大狂)……

\\

大家都知道,Go在实现或语法方面喜欢“我行我素”。在英语中,这种情况被描述为“自以为是”。很多来自其他编程语言的概念在Go中并不存在,或者即使存在,它们的行为也变得“面目全非”。后一种情况可能会导致意想不到的错误,甚至让开发人员感到疑惑。

\\

严格的Go语法通常会让开发人员感到疲倦。Go编译器不允许出现未使用的导入和变量,并竭尽所能将它们拦截下来,甚至让花括号另起一行都不行。Go强制使用相对固定且几乎统一的编程风格。只要Go编译器不喜欢某些东西,到最后都变成了编译错误。

\\

Go提供了非常严格的类型安全。因为太过严格,我们甚至可以通过它来实现一些特殊效果和编程错误,其中一些我们稍后会在文中讨论。不过,我们很少有必要在Go中显式地声明类型,因为类型通常可以从赋值中获得,也就是类型推断。

\\

我不是要提供问答!

\\

一年多以前,我开始在工作中大量使用Go。Go算不上是我最喜欢的编程语言,但我承认,Go在提升开发效率方面起到了一定作用。事实上,我已经使用Go完成了几个小项目,主要是一些嵌入式应用。Go Toolchain的跨平台编译功能(编译后可用于其他操作系统或CPU平台)非常棒,已经遥遥领先于它的竞争对手。

\\

现在让我们来看看Go的一些比较特别的特性。入门Go其实很容易,可能只需要一个周末来了解它的基础知识。但当你开始用Go做一些更复杂的事情时,各种奇奇怪怪的事件开始浮出水面。

\\

有时候,这些特性非常奇怪,谷歌为此提供了问题解答,用于解释类似“为什么X的行为是这样或者那样的”这类问题。Go在很多方面都表现得与其他语言不太一样,感觉好像程序员在某个时候一定会被某些陷阱绊倒一样。gopher Slack频道已经证实了这种情况的存在,其中就有这样的描述:“现在你真的应该好好了解一下Go了,因为每个开发人员在他们的Go职业生涯中都会问到这个问题”。通常情况下,我们的直觉与Go的特性并不相符。例如,在谷歌的C语言变种中,公开类型、函数、常量等都以大写字母作为开头来表示它们是公开的,而标识符开头的小写字母表示它们是私有的。

\\

尽管如此,有关Go的很多决策都是在邮件列表或提案文件中经过了长时间的讨论,因此还是得到了肯定。然而,讨论所使用的用例都非常特殊,以至于很多开发人员仍然不清楚这与他们要解决的问题究竟有什么关系。

\\

我个人最喜欢的部分是Go没有提供可重入锁,即同一线程或Goroutine(Coroutine或Green Thread的变体)可递归获取的锁。如果不通过hack的方式就无法自行实现这样的功能,因为线程在Go中不可用,而Goroutine也并没有提供可用于递归识别相同Coroutine的标识符。

\\

在这篇文章中,我想介绍Go的五个特性及其语法,这些特性都很隐晦。

\\

1. 疯狂的影子

\\

让我们从最简单的事情开始:每个优秀的开发人员都听说过Shadowing,它通常会发生在变量的上下文中。下面是只包含两个作用域的简单示例:

\\
\foo(\"foo\")\func foo(var1 string) {\  for {\    var1 := \"bar\"\    fmt.Println(var1)\    break\  }\
\\

我们通过:=赋值符号创建了一个变量,并通过所赋的值(类型引用)来推断变量的类型。在这里,它是一个字符串。因此,我们在内部作用域(for循环)中创建了一个与函数参数名称相同的变量。我们覆盖(shadow)了输入参数,并输出“bar”。

\\

到现在为止还挺好。但是,在Go中,需要为其他包的属性指定包名(即结构体、方法、函数等),这个可以在提供Println函数的fmt包中看到。

\\

所以我们对之前的例子稍微做一下重构:

\\
\foo(\"foo\")\func foo(var1 string) {\  for {\    fmt := \"bar\"\    fmt.Println(var1)\    break\  }\}
\\

这一次,我们遇到了编译错误,我们试图在一个字符串上调用Println函数。但这种情况并不总是这么明显。当代码突然停止编译时,即使只有几行代码也会给我们带来“惊喜”。

\\

如果结构体发生重叠,就会很麻烦。让我们举一个奇怪的例子:

\\
\type task struct {\}\  \func main() {\  task := \u0026amp;task{}\}
\\

我们创建了一个叫作task的结构体和它的一个实例。我们有意使用小写task作为结构体的名称,因为如前所述,Go使用第一个字母来确定可见性,所以task在这里是私有的。

\\

到目前为止,它看起来很不错,Go编译了我们创建的task。但是,当我们尝试添加另一行代码时,情况突然发生了变化。

\\
\type task struct {\}\  \func main() {\  task := \u0026amp;task{}\  task = \u0026amp;task{}\}
\\

现在无法通过编译,并显示task不是一个类型。此时,Go分不清类型和变量之间的区别。也许有人会说,在JavaScript中,变量task可以是对类型的引用,但这在Go中是不可能的,因为类型不可以作为值赋给变量。

\\

现在的问题是:这算不算是悲剧?一般来说不算,但它却经常在我没有意识到的情况下发生。后面可能还会有一些代码尝试访问相同名称的结构体或包,而每次都需要花几分钟时间才能找到问题所在。

\\

说到类型问题,让我们看看另外一个例子。

\\

2.类型还是无类型,这是个问题!

\\

我们已经知道如何创建结构体和函数。有时候,我们会偶尔“重命名”一下类型,比如:

\\
\type handle int
\\

这将创建一个叫作handle的类型,它的行为类似int。通常,这个特性被称为类型别名。你可能也想到过这个特性,但不是在Go中。不过从Go 1.9开始,已经完全支持这个特性了。

\\

让我们看看可以用Go做哪些好玩的事情:

\\
\type handle int\  \func main() {\  var var1 int = 1\  var var2 handle = 2\  types(var1)\  types(var2)\}\  \func types(val interface{}) {\  switch v := val.(type) {\  case int:\    fmt.Println(fmt.Sprintf(\"I am an int: %d\
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值