Clojure 用后感

最近接手了一个项目,主要是 Java,但使用了 Clojure 作为规则引擎的脚本语言。简而言之就是把一些逻辑实现用 Clojure 实现,然后放到数据库里,Java 在用到时就动态加载编译执行。

之所以用 Clojure 也只是因为当年 Function Language 正是热点,所以在选型时项目负责人就把 Imperative Language,例如 JavaScript, Python 之类的排除在外了,在 FL 中的选择主要就是 Scala 和 Clojure 选谁的问题,毕竟其它的像是 LISP 或 ErLang 之类的怎么和 JVM 集成是个大问题。Clojure 因为号称相比 Scala 是更加纯粹的 FL 所以最终入选。所以现在看来,其实作所谓的战略决策时基本上是凭运气,在这个所谓的历史关口作出的选择成了现在所有接手人员的噩梦。没有人懂 Clojure,原先的人都已经跑路了,新人也没有人愿意学 Clojure,没办法,程序员其实也是很功利的,如果学英语大家都花钱去学,要是学拉丁语,很可能就是倒贴钱也没人学。如果当年选的是 Scala,或者是 Python,那现在的人可就会对当年的高瞻远瞩佩服到五体投地。其实都是赌,有人赌对了,有人赌错了而已。现在一个项目正在大肆使用 Python,说实话我挺担心。如果是纯粹的 Python 项目也还好,坏就坏在它也是用 Python 作会脚本引擎。天知道选错了又是啥后果。以我接手的这个项目为例,已经超过10年的历史,里面除了 Java 和 Clojure,还混杂着 Scala 和其它一些语言写的模块,尤其是其中一个模块用的语言居然是公司自己开发的,然而这个语言的项目组已经解散不再维护了,更奇葩是的这个语言居然跟 JDK 的某些版本不兼容,导致在升级 JDK 这种无害操作时心惊胆颤。

 在这里我忍不住吐槽下 Java,大家可以看到以前面的选项中,从来没有出现过 Java,从没试图直接用 Java 作为脚本引擎。其实作为一个 Java 项目,就用自己作脚本引擎不是最好吗?但 Java 不知是不是脑子进水了,自废武功。以前 Java 还真可以,在 JDK 1.4 的时候,我还曾经在项目中使用过,即时编译 Java 脚本执行。当然 Java 仍然有个缺陷是这个脚本是不支持热部署的,也就是说你改了你的脚本,只能重启应用才能让它生效。因为 Java 并不支持手工 Unload 指定的类。但无论如何,有的用总比没的用强,而且这些规则脚本并不会频繁修改。但到了某个版本后,可能是 1.6,这个动态编译的功能被从 JRE 中去掉了,改放到了 JDK 的一个附加包中,这可就要命了,因为很多服务器上是不安装完整 JDK 的,再后来好像直接从 JDK 中拿掉了,你想动态加载得自己用 java 命令去编译……真是谢谢你全家。

好了,吐槽结束,回归 Clojure 本身,其实还是吐槽。

 1. Clojure 很容易写成摊大饼式的代码,简单来讲就是函数执行的结果直接当成下一个函数的输入,而且嵌套至无穷 (圆环套圆环?),如果有人在 Java 里这样写一定会被人骂死。就如同一个笑语说的,某间谍偷到了某核心系统源代码的最后一页——整整一页的右括号,因为系统是 Clojure 写的。其实 Clojure 写的代码也可以更好看一些,但不知道为啥,似乎大部分写 Clojure 的人都恨不得把全部功能都在一行里实现,即所谓的简练,能用一行代码实现 Java 的一大片代码。其实代码简练与否不是靠行数的,而是靠逻辑行数,如果你把 Clojure 的每个函数调用都新起一行,左右括号也新起一行,那行数就与 Java 差不多。否则 Java 也大可以把换行符都去掉宣称自己也只有一行。

2. 代码摊大饼也罢了,执行顺序还是由里向外,如果有断行的话再加上从下往上, 这个就要了命了。正常的阅读习惯是从左向右,从上向下,你搞个从里向外实在是不知所谓。有人说那是你不习惯,习惯了就好。但问题是为啥你的习惯与大家都不一样。就像别人的书都是左右排版,你不但搞个上下排版,而且还是从下往上排,你说别人难不难过。

3. 哦,对了,有人说 FL 对编译器友好,问题是我是做应用的,不是做编译器的,我只知道它对我很不友好。

4. 有人说这对程序员也很友好,例如 2 + 5 * 3 + 4 * 6, 你如果不用 FL 就得熟知各种操作符优先级。但问题是常用的操作符优先级我在小学就学过了,而你倒是不用记操作符优先级了,(+ 2 (+ (* 5 3) (* 4 6))) 这种写法看起来很舒服?你要看懂,是不是还得先在纸上翻译成正常的顺序才能明白?知道大家输入中文为啥用拼音不用五笔不?真不是拼音比五笔好学,而是拼音大家已经都学会了。

5. Clojure 程序里充斥着各种特殊符号,例如 #() 表示快速函数声明, #{} 表示快速 hash-set 声明,-> 表示参数首位顺序执行, ->> 表示参数末位顺序执行……哦,对了,这又被看做 Clojure 的优点,让程序简练优美。就算大家没读过信息论,能不能想想为啥大多数编程语言都不这么干?因为冗余信息的一大用处就是提高辩识度,也就是可读性。如果真追求简练,那应该填接用机器码编程,只用 0 和 1 就够了。

6. 号称一切函数都要有返回值,然而就像常言说的,凡事无绝对,我就是想打个 log 需要啥返回值。所以返回 nil 也算有返回值?

7. 宣称一切函数都没有副作用,因为所有的 Clojure 对象都是不可变对象,这意味着一个对象在处理过程中会不停地生成它的硬拷贝来保证这一点,当然它又宣称它使用了很高明的技术来保证只复制需要复制的部分以避免把内存撑爆……好吧,我除了选择相信还能说啥呢。

8. 报错信息极为晦涩,通常就是 XXX 不能转成 IFn 类型,真实原因则千奇百怪,至于发生在哪全靠猜。这也是把所有逻辑写在一行上的报应。

9. 方法参数没有类型签名。如果调用链比较长,而且参数命名不那么规范(那是一定的),在阅读别人代码时为了搞清楚这个参数是什么类型得一直跟踪到源头,这时杀人的心都有了。

原文地址:https://www.cnblogs.com/lldwolf/p/Clojure.html