Streamlet 阅读(57) 评论(0)

(原发于 GitHub Pages,2019-01-01 23:22:43)

2019 年,我回来了。

不知不觉中,我入 PHP 的坑已经 3 年有余,入 Go 的坑也大半年了。作为不评论不舒服斯基星人,自然要对 Go 品头论足一番的。

总的一句话,Go 的一些特性确实恰到好处,然而更多的地方却是平庸、繁琐、束缚,以至于我想不到它是适合哪些场景的。

静态语言里,C、C++ 有着明显的适用领域:你要想老老实实写程序,不玩任何花招,就用 C 吧,至少你能掌控一切,实在想玩你还有宏这个大杀器;你要想玩点花招,那就用 C++,代价就是需要自身水平更高,能掌控到多大层次就写多大层次,不懂的不要不懂装懂去用,总体来说还是安全的。

脚本语言里,如果要随便写点什么工具,python 啥的挺方便的;写点网络的,就用世界上最好的语言 PHP 吧。什么?你说 Java?实在没办法,体量太大,公司要你用你就用吧。不过就其本质而言,其实 Java 和今天的主角——Go 是同一类的。不是说他们语法像,是指应用场景(不过这个领域拿 PHP 写显然会更爽)。

嗯——为了表达出真实的意思,我想用词稍微犀利点,请先做一下心理建设。

我不怎么懂 Java,就我浅薄的了解而言,如果你的公司、团队有很多傻逼,甚至你自己也是,业务上又正好可以用 Java 界的一些现成的框架、组件,那么用 Java 肯定是没错的啦。它确实有一种魔力,让你无论多傻逼,也绝对写不出错得多么隐蔽、精妙无比的代码;同时让你无论多牛逼,也写不出多么精彩绝伦、言简意赅的代码。Go 也有这种特质,甚至有些地方比起 Java 更有过之而不及。不信请看:

别人家的写法:

r = f(p1, p2 != null ? p2 : p3)
$r = f($p1, $p2 ?? $p3) 

Go 家的写法:

var r someType
if p2 != nil {
	r = f(p1, p2)
} else {
	r = f(p1, p3)
}

为什么要设计成这样?Go 爸爸说:你们有些人啊,会嵌套很多层 ?:,导致代码可读性太差啦,于是禁止你们使用 ?:,这是家法。在这里,作为熊孩子的代表,我来告诉大家怎样写出让 Go 爸爸无语的代码:

	foo := 1
	bar := 2
	var foobar int
	if foo > bar { if bar > 1 { foobar = 1 } else if bar < 0 { foobar = 2 } else { foobar = 3 } } else { if foo >= 3 { foobar = 4 } else { foobar = 5 } }

怎么样?可读性差不差?

看到了吧,这种傻逼是防不住的,他愿意把 ?: 嵌套好多层,它同样可能会把 if else 嵌套好多层。有素质的人会怎么做?遇到 ?: 嵌套太多立马拆成 if else。所以结论是,即使去掉 ?:,傻逼还是傻逼,但是正常的人写代码就会很啰嗦;支持 ?:,傻逼还是傻逼,正常人用起来爽。要知道一层 ?: 的场景占所有 ?: 场景的比例还是很高的吧。我觉得可以这样,Go 爸爸可以统计一些工业级代码库的 ?: 嵌套层数,作为数据支撑(比如自家 Chrome 里一层 ?: 占 95%,两层占 4.9),然后再在语言层面只支持一层 ?:,编译参数可选打开两层,不支持两层以上。这就功在千秋了。

除了 不支持 ?:,Go 爸爸还有很多这样的设计,随便举几个例子:

  1. 不支持默认参数
  2. 不支持运算符重载
  3. 不提供 goroutine id(以及 gls、可重入锁)

特别是第三点,也是这种思路,因为你们可能会滥用,所以我不提供。类似这种“爸爸思路”,是我今天要喷的最大喷点。前两点也许是抄 Java 的,不怪 Go 爸爸。

综上,Go 爸爸通过扼杀一些基本语法或者一些基础信息,来防止傻逼干坏事,同时让正常人用起来很啰嗦,同时还可能防止不了傻逼干坏事。这跟 Java 通过不提供高级语法来防止傻逼干坏事是师出同门啊,而且他们正好都能写网络服务程序,你说它们像不像?

然而,Go 爸爸也有精分的时候,它居然发明语法糖了耶!比如 if 可以执行一个句句。单就这个语法而言,我的态度是中立偏赞。赞是赞 Go 爸爸确实用过心了,某些时候挺方便,还能缩小变量作用域;不过这个总归是可有可无的,毕竟换一行写也不会死,要变量作用域加个大括号就行。

小语法方面倒是有个亮点,那就是 switch 的隐式 break、显式 fallthrough 处理。不多展开了。

除了防傻逼,Go 爸爸还有一个思路,就是只许州官放火,不许百姓点灯。有两个语法点——

  1. 泛型
  2. 逗号ok断言

先说泛型吧。不支持泛型其实我挺能理解的,因为他确实比 ?: 复杂多了,傻逼用不起,?: 都没有,怎么可能会有泛型呢。然而 Go 爸爸有特权呀,它的 map、chan 可都是泛型的哦。但是你要写一个泛型的语法结构的话,对不起没有。

再说逗号ok断言,同样 Go 爸爸要得起,我们要不起。其实我更想要一个这样的语法:当返回值是 xxx, ok 或者 xxx, err 的时候,我如果用一个返回值接,那么就返回第一个,以便链式调用,同时 !ok 或者 err != nil 的时候 panic。

以上两小点虽然是在喷,不过还好啦。无所谓的。下面讲几个大的方面。

代码组织

我特别赞赏 Go 对于 package 级严格的循环依赖检查。然而如果加上其他规则:

  • 一个目录一个 package
  • 不同路径下的同名目录也是不同 package
  • go 代码无法拆成 .h、.cpp

实际可操作性就会非常差。要拆 package,就要把依赖关系梳理得特别严格,半点不得马虎。这对工一般的程代码来说是个极大的挑战。

我更倾向于做成函数级循环依赖检查,或者不限制(毕竟递归函数也是要支持的嘛)。

这部分,中立偏喷,偏喷是因为 Go 爸爸用了我的小名 internal。

OOP

我特别赞赏 Go 对于 OOP 泛滥成灾的思考与探索,以及对于终结这阵 Java 带来的不正之风的决心。怎么可能万物都是 class 呢。但同时,Go 还是有点矫枉过正的,如果我需要利用传统的 OOP 来搞事情,就非常麻烦,你甚至都无法写出一个框架来。你只能写库让别人用。虽然我也不喜欢框架,但有的时候是需要框架的。

defer 非常切中痛点,特别解决一堆错误 return 外加资源释放的问题。相比之下C 里只能用 goto,C++ 本身不支持但可以玩出 LOKI_ON_BLOCK_EXIT 或者 BOOST_SCOPE_EXIT。defer 一举解决问题。(要是能再增加命名 defer 以及撤销 defer 的功能就更好了。没错,你也许看出来了,我觉得 LOKI_ON_BLOCK_EXIT 是最完美的方案。)

虽然 defer 很好,但不意味着析构就没用武之地了。理想的情况是,析构、多态、defer 都要有……

这部分我的态度是中立。

错误处理

终于要点赞了。错误处理是在我看来 go 完胜的地方,恰到好处地处理问题,又防止滥用。也矫正 Java 带来的歪风邪气。

Java 的设计,让人不得不用异常来处理业务。甚至 Java 自己还帮我们分好类了:一种是不是异常的异常,用来处理业务;另一种是真的异常。一些用惯 Java 的傻逼跑到 C++、PHP、Python 里乱拉屎,到处是 try catch。

Go 爸爸一声令下,万籁俱寂。

goroutine

最后不得不说说 gorouthine,毕竟是卖点嘛。我的态度中立偏赞。赞是因为这是一种太有创意的方案,居然想在在语言层面解决多线程、并发问题;不过我还是觉得这更多的是应用层面的问题,做到官方库里会更好,而不是做成语法。

总结

刚开始用 Go 的时候,特别亮眼,简直处处是亮点,然后越接触越讨厌,一点也不耐看……看得出来,设计者糅合了 C、python、Java 的一些特性,并融入了自己的独特的理解。Go的设计者真的特别特立独行且坚持己见,一些我喜欢的特性因为他们的坚持而存在下来,一些我讨厌的特性也因为他们的坚(Gu)持(Zhi)而不能有所改观。就这样的 Go,想代替 C 作为系统语言,是没戏的;想在网络服务有一番作为,抢 Java 的份额,或许是有机会的,不过最多只能抢 Java 的,连 C# 的都抢不了,C++、PHP 更抢不了。

嗯,除了特定的不得不用的场合,反正我是不会特意用 Go 的。

2019,新年快乐!