导读:作者通过一些实例,来说明Go语言存在的一些设计缺点,甚至是弱智功能。
关于 Go 的一些事儿让我越来越烦恼了。
主要是因为一些东西完全没有必要。世界本来能自己更懂事,但 Go 就是这么被重复创造出来了。
下面是一个语言强迫你做错事的例子。最小化变量的作用域对编码的读者非常有帮助(其实代码的阅读频率比编写频率要高)。如果你能通过语法告诉读者变量只在这两行代码中使用,那就太好了。
下面是代码例子:
if err:=foo(); err!=nil{
return err
}
(关于这个冗长重复的样板代码已经说得不少了,我不需要再说了。我也不特别地在意)
那看起来就没问题了。读者知道err
在这里,而且只在这儿用。
但随后你会遇到这种情况:
bar,err:=foo()
if err!=nil{
return err
}
if err=foo2();
err!=nil{
return err
}
[…a lot of code below…]
等等,为什么?为什么
err被
foo2()函数重复使用了
?是不是有什么微妙之处我没注意到?即使我们把它改成:=
,我还是会疑惑,为什么err
会在函数的其余部分(可能)被覆盖。
为什么?它稍后会被读取吗?
err这个变量,尤其是在查找 bug 的时候,经验丰富的程序员会发现这些问题,然后放慢速度,因为这里可能有条“大鱼”。好了,现在我又在重复使用for的这个“红鲱鱼”上浪费了几秒钟,这就是foo2()
。
该函数以此结尾是否是一个错误?
// Return foo99() error. (oops, that's not what we're doing)foo99()
return err// This is `err` from way up there in the foo() call.
为什么范围会
err
超出其相关范围?
很明显,如果err 的作用域更小,代码会更容易阅读,但这在 Go 的语法上是不可能的。
这似乎根本没经过深思熟虑,直接做决定不是思考。
package main
import "fmt"
type linterface{}
type S struct{}
func main(){
var il
vars *S
fmt.PrintIn(s,i)// nil nil
fmt.Println(s==nil,i==nil,s==i)// t,t,f: They're equal, but they're not.
i=S
fmt.PrintIn(s,i)// nil nil
fmt.PrintIn(s==nil,i==nil,s==i)// t,f,t: They are not equal, but they are.
}
造成这种差异的原因再次归结为:不思考,只打字。
我始终认为,在文件顶部附近添加注释来实现条件编译肯定是最愚蠢的做法。任何真正尝试过维护可移植程序的人都会告诉你,这只会带来麻烦。
这是亚里士多德设计语言的科学方法;把自己锁在房间里,永远不要用现实来检验你的假设。
问题是现在不是公元前350年。我们实际上有经验证明,除了空气阻力之外,重的物体和轻的物体下落的速度实际上是相同的。而且我们有使用便携式程序的经验,不会做这么愚蠢的事情。
如果这是公元前350年,那还可以原谅。那时我们所知的科学技术尚未发明。但这是在几十年来便携性经验广泛应用之后。
append
没有明确的所有权我们来看一下以下面的程序打印了什么?
package main
import "fmt"
func foo(a[]string){
a=append(a,"NIGHTMARE")
}
func main(){
a:=[]string{
"hello","world","!"
}
foo(a[:1])
fmt.Println(a)
}
它可能会输出[hello NIGHTMARE !]
。可是谁想要这样的结果?估计没人想要。
如果结果有点牵强,来看一下这个代码怎么样?
package main
import "fmt"
func foo(a[]string){
a=append(a,"BACON","THIS","SHOULD","WORK")
}
func main(){
a:=[]string{"hello","world","!"}
foo(a[:1])
fmt.Println(a)
}
如果你猜对了是[hello world !]
,那么也会对这种愚蠢编程语言的怪癖了解就比任何人都要多。
defer甚是
愚蠢即使在 GC 语言中,有时你也迫不及待地想要销毁某个资源。它确实需要在离开本地代码时运行,无论是通过正常返回,还是通过异常(又称 panic)。
我们想要的是 RAII,或者类似的东西。
Java 语言有这样的操作:
try(MyResourcer=new MyResource()){
/*work with resource r, which will be cleaned up when the scopeends via
.close(), not merely when the GC feels like it.
*/
}
Python 也有这个功能。虽然 Python几乎完全基于引用计数,可以依赖__del__
终结器被调用。它很重要,使用了with语法。
with MyResource() asres:
# some code. At end of the block __exit__ will be called on res.
Go是怎么干的?Go 会让你去阅读手册,看看这个特定的资源是否需要调用 defer 函数,以及调用哪一个。
foo,err:=myResource()
if err!=nil{
return err
}
defer foo.Close()
这实在太笨了。有些资源需要延迟销毁,有些则不需要。是哪些资源?它好像在说祝你好运。
而且你还会经常遇到类似这种“可怕”的事情:
f,err:=openFile()
if err!=nil{
return nil,err
}
defer f.Close()
if err:=f.Write(something());err!=nil{
return nil,err
}
if err:=f.Close();
err!=nil{
return nil,err
}
是的,这就是你在 Go 中安全地向文件写入内容所需要做的事情。
这是什么?第二次调用 Close()
?哦,是的,当然需要。双重关闭会更安全吗?还是我的 defer 需要检查一下?它碰巧要更安全的处理os.File
,但在其他情况下:谁知道呢?
好的,到目前为止还不错。
但所有 Go 程序员仍然必须编写异常安全的代码。因为虽然他们不使用异常,但其他代码会。
因此,你需要(而不是选择)编写如下代码:
func(f*Foo)foo(){
f.mutex.Lock()
defer f.mutex.Unlock()
f.bar()
}
这愚蠢的中间函数/系统是什么鬼?这蠢透了,就像把日期放在中间一样蠢——MMDDYY,真的吗?后面再做详细吐槽。
但是他们说,恐慌会终止程序,所以为什么你要关心在程序退出前五毫秒是否解锁互斥锁呢?
因为如果某些事情吞噬了该异常并继续正常进行,而您现在却陷入了锁定的互斥锁中,该怎么办?
但肯定没人会这么做吧?合理而严格的编码标准肯定能阻止这种事发生,除非被解雇?
标准库fmt.Print
在调用时执行此操作.String()
,并且标准库 HTTP 服务器在 HTTP 处理程序中处理异常时执行此操作。
一切希望都破灭了。你必须编写异常安全的代码。但你不能使用异常。你只能承受异常带来的负面影响。
不要让他们欺骗了你。
如果你将随机二进制数据塞入string
,Go 就会顺利运行,正如本文所述。
几十年来,我因为某此工具跳过非 UTF-8 文件名而丢失过数据。我不应该因为拥有 UTF-8 出现之前命名的文件而受到指责。
嗯……我之前有。现在没了,备份/恢复的时候被悄悄跳过了。
Go 会让你继续丢失数据。或者至少,当你丢失数据时,它会这样跟你说:“嗯,数据你用的是什么编码?”
那么,当你设计一门语言时,你为什么不做些更周全的事情呢?为什么不做正确的事情,而不是做那些明显错误的简单事情呢?
我为什么要关心内存使用情况?现在的内存比较便宜,比阅读这篇文章的时间还要便宜得多。我之所以关心,是因为我的服务运行在云实例上,而你实际上需要为内存付费。或者你运行容器,并且希望在同一台机器上运行一千个容器。你的数据可能可以装入内存中,但如果你必须为这千个容器分配 4TB 而不是 1TB 的 RAM,那么这让成本仍然很高。
您可以使用手动触发 GC 运行runtime.GC()
,但“哦不,不要这样做”,他们说,“它会在需要时运行,只需相信它”。
是的,90% 的情况下,每次都有效。但有时却不行。
我用另一种语言重写了一些东西,因为随着时间的推移,Go 版本会使用越来越多的内存。
所以,通过上面的内容我们更清楚Go语言的某些弱点。显然,这并不是关于使用符号还是英文单词的 COBOL 争论。
并且我们当时也不知道Java 的思想实现的也一般,当时我知道 Go 的想法并不太好。
到这里我们已经更了解 Go 了,但现在我们还要被糟糕的 Go 代码库所困扰。
各位感受如何?
作者:手扶托拉斯基
参考:
https://blog.habets.se/2022/08/Java-a-fractal-of-bad-experiments.html
本篇文章为 @ 行动的大雄 创作并授权 21CTO 发布,未经许可,请勿转载。
内容授权事宜请您联系 webmaster@21cto.com或关注 21CTO 公众号。
该文观点仅代表作者本人,21CTO 平台仅提供信息存储空间服务。