剑客
关注科技互联网

从 Java 到 Go,再到 Java

介绍: 合适的地形, Cassandra, 使事情更糟糕

在Lisp中, 你并不仅仅针对语言编程,你同时针对编程建造语言. 当你编码时,你可能会想“我想要Lisp 有这样那样的操作.”所以你写下代码. 等到后面你意识到,使用新的操作可以简化代码其它部分的设计 , 等等. 语言和编程同时发展…最终你的编码看起来好像语言就是为了它而设计出来一样. 当语言和编程能 很好互相配合的时候
,你的代码更干净,小巧,有效.
– Paul Graham, Programming Bottom-Up

在进化的生物中, fitness landscape
是预见基因组和生殖成功之间关系的一种方式。每个可能的基因组演化展现在地图上的某个位置,这个位置的地势高度代表相关联的演化的生殖成功。然后我们可以把一系列的基因变异想象成在这个地图上行走。是否按照合适的地形行走的规则是——你只可以以小的步伐在地图上行走——不可以突然从一个位置跳到其他位置——或者在
错误 ” 
的方向上行走,等等。那些降低你的生殖成功率的行为, 会受到相应的惩罚,使你 得到更少的机会在地图上行走
。出现在地图山峰附近的人将会沿着上坡路移动到山峰, 寻找任何地方更高的山顶,而不是向下走下峡谷。

这种使遗传算法的行为可视化的方式,使我们能够看到这样的算法将如何趋向于“局部最优”解决方案,而不是找到“全局最优”。
同时,它强调了使用渐进的方法以探索一系列可能的设计:如果每一步增值必须得到可量化的改进, 并且禁止向地图上其他位置的进行突然跳跃, 我们就会陷入局部最优设计而不会到达其他地方的峰值。如果我们已经位于局部最优解决方案的顶部,我们有可能哪儿也去不了。有时候, 事情必须先经过涅槃,才能变得更好。

OpenCredo’s October Cassandra Webinar
中,  为了得到系统达不到的性能和伸缩性,我把分布式键值存储Cassandra描绘成尚未解决的问题。这一问题已经被关系型数据管理系统所解决。Cassanda 移除了RDBMS的几乎全部的数据管理特性,用一个简单但强大的数据地址模型来代替。这一模型基于二维键。如果我们理解了这个模型怎样工作,以及怎样最好地工作,我们就能在Cassandra之外得到独特的性能;但是,如果我们尝试像使用关系型数据管理系统一样使用Cassandra(  try to use Cassandra like an RDBMS
),我们很快会发现,无论以哪种方式,Cassandra都是更糟糕的关系型数据管理系统.

如果我们把数据管理系统的空间 想象成一个适应景观,我们可以看到Cassandra和一个RDBMS坐在(或者附近)不同的景观的峰顶:通过与非常松散的进化类比,它们占据不同的生态链,开始为不同事情进行优化。当然,Cassandra并不会从RDBMS小的改进中升级:它的开发者没有从RDBMS开始,在情况变得更加糟糕之前,Cassandra数据模型进入视野。相反,Cassandra是在设计空间中选择一个不同出发点的结果。

一个月前,当我第一次学习谷歌的Go语言,我的第一印象是,它是一个故意在多种方式变得更糟糕的语言。我已经听了一些开发者激烈地抱怨:它的设计是多么逆行的,十分丑陋,加上种种限制。对于他们,使用Go语言犹如在适应景观中滚到低谷。他们迫不及待地想用回高雅的 极具创造力的Java语言。但是,
即使我在一些方面上背驰而去,我不知道什么取代优化。如果Go语言没有比Java糟糕,反而比Java更好呢?我决定坚持使用这门语言,看看它到底能让我在编程语言设计以前没有接触过的 领域中能
从中学到什么。

Go: 有什么好的呢?

程序与所需程序不相关的时候,其编程语言是低级别的。

– Alan Perlis, Epigrams on Programming

尽管许多人不看好Go语言,但喜欢Go的人也很多。喜欢Go的人认为Go的代码编写产量高,适宜阅读。假定他们不是简单的被骗,这些开发者可以用Go来做什么呢?

Go开发者普遍认为其他人用Go编写的代码易于阅读和理解 。当我被分配任务要去实现Terraform提供程序时,我会去Terraform的代码库通读其他提供程序的代码实现,
我几乎

可以 马上
按照他们的方式来工作:设计一个自己的工作原型并且当天就可运行。虽然“可读性”是一个非常主观的概念,但Go语言的代码有着特殊的属性,仿佛能帮助理解。

主要的说,Go牺牲了一致性的表现力。我喜欢将编程视作富有“创造性”的活动,原因之一是我喜欢在每个系统里开发“ 合乎我对领域模型理解的语言
”的代码。Paul Graham在多年以前就对其进行了描述,其中一点是:你先选择你想用于 构建的
项目的语言,然后再去构建项目,如此一来后者从你所创建的领域抽象来看就失去了自然性与表达性。 对此,
Go提供的支持非常有限,它不支持宏、泛型、lambda表达式、错误处理等。多年来我
用Java、Ruby、Clojure创建的 几乎所有
的DSL(数据领域语言)

技巧都没法在Go中使用。 用Go时,
你基本上别无选择,只能或多或少地写“ 平庸”

代码。

在另一方面,我经常从我的开发同事那里听到抱怨说,他们根本无法看明白我写的代码,除非我把其中的逻辑分析出来使之更易于表达。一旦他们看明白了这些代码,他们心里会很舒坦。(有一两次,因为没有其他人能领会它的要点,我不得不将内容撤回;回想一下,其实它们都是正确的)。 Je ne regrette rien(我不后悔)
。但你必须努力让其他人克服初始障碍,这意味着 他们
也必须工作。这在一个融洽的小团队里是行得通的,因为你可以慢慢走到其他人的桌子边,跟他们一起探讨一些陌生的工作内容。但如果是另一种情况——你的代码库要在好几十年时间内,被成千上万的来自世界各地、各个时区的人阅读与应用时,这将会充满挑战。这是Google的真实情况,而Go就是其中的最佳代表。

举一个具体的例子。假设我们要在集合中的每个字符串后面添加一个感叹号来创建新的集合。在Kotlin中,会是这样

val plain = listOf("foo", "bar", "baz")
val exclaimed = strings.map { it + "!" }

在Go中,可以写为:

func mapStringToString(input []string, mapper func(string) string) []string {
output := make([]string, len(input))
for index, item := range input {
output[index] = mapper(item)
}
return output
} 
func main() {
plain := []string{"foo", "bar", "baz"}
exclaimed := mapStringToString(plain, func(s string) string {
            return s + "!"
        })
fmt.Println(exclaimed)
}

但这样非常笨拙。Go中缺少范型,这意味着人们必须使用mapStringToString作为两种不同类型的集合之间的映射,而不能像在绝大多数强类型的现代语言中那样使用通用类map将数组T映射到数组R。缺少lambda函数意味着,人们不得不完整的写出这个函数为了能使字符串的修改通过映射。几乎不值得这么做,你也可以只写成:

func main() {
	plain := []string{"foo", "bar", "baz"}
	exclaimed := make([]string, len(plain))
	for index, item := range plain {
		exclaimed[index] = item + "!"
	}
	fmt.Println(exclaimed)
}

当Go语言支持传递函数(这个偶尔派上用场),它并不是真正支持函数用来构建组合的抽象类,让人感觉是语言的扩展。它倾向于具体,内联逻辑表达式,函数调用作为一种结构化编程机制。“具体与内联”意味着你可以预见未知,他所发生的正确位置——这使得程序员 更加容易找到
不熟悉的代码块 位置。

同样的哲理是工作在Go语言关联的库里面,可以静态编译与链接所有东西。它意味着无论你何时使用手中的库源代码,Go可以定位外部依赖于一个可预知的路径位置。Ctrl-click在一个IDE环境将会帮助你直接声明你所调用的函数。这通用规则有利于透明,去除远程的以及不透明的,具体与文学超过抽象与魔幻。它像是一门没有隐喻的语言。

你一旦接收“canilla”Go语言是你的所有,最好把它想象成一种用于执行磁盘和网络I/O的DSL工具(标准库是容易理解的,而且拿来即用),丰富和转换数据(Go语言的结构类型是非常有利于这个的),并且并行高效工作(概念是一种经过深思熟虑的低级抽象且并行执行)。这些特征体现出它在系统编程的优越性,在你平常关心离散领域,链式执行——在text文件读取,解析它的内容成CSV格式,在第三列中发散值的总和——一个具体,命令式编程自然适合问题空间。它额更少原则适合应用的发展,你需要一个更广泛的隐喻来表达应用领域的多角度观点。我并不会这样,例如,想要使用Go来编写高度依赖演员的软件——基于方法—— 你可以做到
,但是,它与Scala提供的 Akka
案例与模式匹配 的表现对比
,是 相对笨拙的

Java开发者可以从Go语言中学到什么

如果一门语言不会改变你对编程的看法, 就不值得去学习.

– Alan Perlis, Epigrams on Programming

使用Go语言开发了几个月后,再次回到Java (和Kotlin)的开发.此时我更倾向于认为,对于一些编程逻辑而言,更直接具体的快速实现可能要好过绞尽脑汁地设计出更通用的版本,更何况这种更抽象的版本在实践中有可能永远不会得到复用。 在代码中,我发现了某种 “普通表达”的价值。

另外,对于搞清楚Spring Boot框架如何运行,以及背后的实现原理是什么这样的问题,我发现自己逐步失去了耐心。我希望使用更轻量级以及更透明的框架. 当你使用一种语言来工作,这种语言对于实现不同的功能会权衡利弊,做出最好取舍,然后在你熟悉的、舒服的环境下这种权衡取舍会变得直接明了: 你会看到为了得到你想要的结果,你牺牲了什么。

学习Go语言不会教会你任何令人兴奋的计算机科学理论,或者介绍给你一个完全信的软件开发规范(如果是为了这个目的,你可以尝试 Idris
)。但是在编程语言设计空间的广度和多样性上,它将带给你更好的理解,同时主流的语言往往看起来大同小异(Kotlin相当类似Swift,也类似Typescript等语言)。有时候用来区分一门语言的不是创造性的新特性,而是它是否画地为牢,束缚了自己。

分享到:更多 ()

评论 抢沙发

  • 昵称 (必填)
  • 邮箱 (必填)
  • 网址