返回信息流王垠:为Java说句公道话
转自:http://www.yinwang.org/blog-cn/2016/01/18/java/
有些人问我,在现有的语言里面,有什么好的推荐?我说:Java。他们很惊讶,想不到像我这样的人也会推荐Java,所以我现在来解释一下。
Java超越了所有咒骂它的“动态语言”
也许是因为年轻人的逆反心理,人们都不把自己的入门语言当回事。很早的时候,计算机系的学生用Scheme或者Pascal入门,现在大部分学校用Java。这也许就是为什么很多人恨Java,瞧不起用Java的人。他们说,Java庞大,复杂,臃肿。然而他们所谓的“复杂”之处,其实大部分都是有用的。特别是像静态类型系统,generics等特性,对于大型的工程项目,是很重要的。
某些Python程序员,在论坛里跟初学者讲解Python有什么好,其中一个原因竟然是:“因为Python不是Java!” Python一般不用来写很大的工程,所以很多Python程序员误以为静态类型是臃肿的累赘。他们喜欢这样宣传:“看Python多简单清晰啊,都不需要写类型……” 对于Java的无缘无故的恨,盲目的否认,导致了他们看不到Java很重要的优点,以至于迷失自己的方向。虽然气势上占上风,然而其实Python作为一个编程语言,是完全无法和Java抗衡的。
在性能上,Python比Java慢几十倍。由于缺乏静态类型等重要设施,Python代码有bug很不容易察觉,察觉了也不容易debug,所以Python无法用于构造大规模的,复杂的系统。你也许发现某些startup公司的主要代码是Python写的,然而这些公司的软件,质量其实相当的低。在成熟的公司里,Python最多只用来写工具性质的东西,或者小型的,不会影响系统可靠性的脚本。
静态类型的缺乏,也导致了Python不可能有很好的IDE支持,你不能完全可靠地重构(refactor)Python代码。PyCharm对于早期的Python编程环境,是一个很大的改进,然而理论决定了,它不可能完全可靠地达到“变量换名”等基本的refactor操作。
在设计上,Python,Ruby比起Java,其实复杂很多。缺少了很多重要的特性,有毛病的“强大特性”倒是多了一堆。由于盲目的推崇所谓“正宗的面向对象”方式(所谓late-binding),这些语言里面有太多可以“重载”语义的地方,这导致了代码具有很大的复杂性和不确定性。Python和Ruby代码容易被滥用,不容易理解,容易出问题,就是这个原因。
很多JavaScript程序员也盲目地鄙视Java,而JavaScript其实比Python和Ruby都要差。不但具有它们的所有缺点,而且缺乏很多必要的,方便的特性。JavaScript的各种“WEB框架”,层出不穷,似乎一直在推陈出新,而其实呢,全都是在黑暗里瞎蒙乱撞。JavaScript的社区以幼稚著称。你经常发现一些非常基本的常识,被JavaScript“专家”们当成了不起的发现似的,在大会上宣讲。我看不出来JavaScript社区开那些会议,到底有什么意义,仿佛只是为了拉关系找工作。
Python凑合可以用在不重要的地方,Ruby是垃圾,JavaScript是垃圾中的垃圾。原因很简单,因为Ruby和JavaScript的设计者,其实都只是民科。
Java的“继承人”没能超越它
最近一段时间,很多人热衷于Scala,Clojure等新兴的语言,他们以为这些是对Java的改进,是比Java更现代,更先进的语言。然而最后这些狂热分子们逐渐发现,Scala和Clojure其实并没有解决它们声称能解决的问题,反而带来了它们自己的毛病。这些毛病很多是Java没有的。
我认识一些人,开头很推崇Scala,我建议他们老老实实用Java,没听我的,结果到后来成天都在骂Scala的各种毛病。但是项目上了贼船,不得不继续用下去。我不喜欢进行人身攻击,然而我发现一个语言的好坏,往往取决于它的设计者的水平,人品,和动机。很多时候我看人的直觉是异常的准,以至于依据对语言设计者的第一印象,我就能预测到这个语言将来会怎么发展。所以在这里,我想谈一下对Scala和Clojure的设计者的看法。
Scala的设计者Martin Odersky,虽然在程序语言领域有所建树,发表了不少看似高深的学术论文( 其实很多是扯淡的),然而他对于语言的“设计”,其实并不是特别在行。所以我很惊讶的发现,有些非常基本的问题,Scala都会搞错。由于Odersky是大学教授,名声在外,很多人想找他拿个PhD,所以东拉西扯的,喜欢往Scala里面加入一些无需有的,有潜在问题的“特性”,其目的就是发paper,混毕业。这导致Scala过度繁复,加入的特性很多后来被证明没有很大用处,反而带来了问题。学生把代码实现加入到Scala的编译器,毕业就走人不管了,所以Scala编译器里,就留下一堆堆的历史遗留垃圾。
再来说一下Clojure。当Clojure最初“横空面世”的时候,我就看了一下它的设计者Rich Hickey做的宣传讲座视频。我当时就对他一知半解拍胸脯的本事,印象非常的深刻。那种气势,仿佛其他的语言设计者什么都不懂,只有他看到了真理似的。也只有这样的人,才能创造一个“宗教”吧?然而,Clojure大力宣传的“特性”(什么lazy啊,pure啊,transactional memory啊),都是从别的语言道听途说抄过来,却又没能深刻理解其精髓。有些“函数式语言”的特性,本来就是有问题的,却不问青红皂白,为了“主义正确”,抄过来。所以最后你发现这语言是挂着羊头卖狗肉,说得头头是道,用起来怎么就那么蹩脚。
Clojure的社区,一直忙着从Scheme和Racket的社区抄袭思想,却又想标榜是自己的发明。比如Typed Clojure,就是原封不动抄袭Typed Racket。有些本来同样的概念,恁是要改个不一样的名字。甚至有人把SICP,The Little Schemer等名著里的代码全都用Clojure来改写,结果完全失去了原作的简单性和精华性。最后你发现,Clojure里面好的地方,全都是Scheme已经有的,Clojure里面新的特性,几乎全都有问题。我参加过一些Clojure的meetup,可是后来发现,里面竟是各种喊着大口号的初学者,各种趾高气昂的民科,愚昧之至。
盲目推崇Scala和Clojure的人们,很多最后都发现,这些语言里面的“新特性”,几乎都有毛病。它们里面最重要最有用的特性,其实早就已经在Java里了。有些人跟我说:“你看,Java做不了这件事情!” 然后跟我解释。结果经我分析,他们以为Java做不到的原因是,他们潜意识里已经认定,非得用某种最新最酷的特性,才能达到目的。Java没有这些特性,他们就以为非得用另外一种语言。然而我发现,如果你换一个角度来看问题,不要钻牛角尖,把问题化简到最基本的状态,不要去追求最酷的写法,Java就能有效的解决它,而且解决的干净利落。
如果现在要做一个系统,真的宁可用Java,也不要浪费时间去折腾什么Scala或者Clojure。错误的人,设计了错误的语言,所以希望非常渺茫。
Java没有特别讨厌的地方
Java也许缺少一些方便的特性,然而长久以来用Java进行教学,用Java工作,用Java开发PySonar,RubySonar,Yin语言,…… 我发现Java其实并不像很多人传说的那么可恶。我发现自己想要的95%以上的功能,在Java里面都能找到比较直接的用法。剩下的5%,用稍微笨一点的办法,一样可以解决问题。
很多人讨厌Java,其实是因为早期的GoF Design Patterns,试图提出千篇一律的模板,给程序带来了不必要的复杂性。然而Java语言本身,其实跟Design Patterns并不是等价的。Java的设计者跟Design Pattern的设计者,完全是不同的人。你完全可以使用Java写出非常简单的代码,而不使用Design Patterns。
Java有优秀的IDE支持
我平时都用IntelliJ来写Java代码。我发现IntelliJ里面,有一些非常好的设计思想。其中很多功能,其实超越了所有的文本编辑器(Emacs,VIM……)。IntelliJ让Java如虎添翼,开发起来感觉是在飞一样。
用IntelliJ的时候,你不需要为“给变量起名字”之类的事情焦虑。因为IntelliJ有非常强大而友好的refactor功能,你可以非常迅速的换掉变量的名字。所以在第一次创造变量的时候,你不需要花心思去起一个完美的名字。用一个还算凑合的名字,把代码很快写出来,实验成功。然后再返回去看代码,把名字换成一个更合适的就可以。
IntelliJ还可以进行非常迅速的结构变换,这让你就像艺术家在构造一个雕塑作品。最开头我可以大刀阔斧,把代码劈成大致的形状,然后再把它仔细推敲,揉捏成更好,更容易理解,更具魅力的形状。
结论
综上所述,我实在不忍心看着有些人被Scala和Clojure忽悠。如果没有超级高的性能和资源需求(可能要用C这样的低级语言),目前我建议大家就老老实实用Java吧。虽然不如一些新的语言炫酷,然而实际的系统,还真没有什么是Java写不出来的。有少数地方可能需要绕过一些问题,然而这样的情况不是很多。
编程使用什么工具是重要的,然而工具终究不如自己的技术重要。很多人花了太多时间,折腾各种新的语言,希望它们会奇迹一般的改善代码质量,最后他们发现这都是在浪费时间和精力。选择语言最重要的条件,应该是“够好用”就可以,因为项目的成功最终是靠人,而不是靠语言。
这是一条镜像帖。来源:北邮人论坛 / java / #47463同步于 2016/1/20
该镜像源已超过 30 天没有更新,可能在源站已被删除。
Java机器人发帖
王垠:为Java说句公道话
dss886
2016/1/20镜像同步85 回复
订阅后,新回复会通过你的通知中心匿名送达。
9 条回复
这篇文章感觉有种对Java的宗教式的崇拜。而且很不具体,说了很多“新特性”不好,也并没有说“新特性”具体指的是什么(如果说Scala不应该从Java继承Object.equals方法,我倒是赞成),更多的篇幅都在说脏话骂人。
标题说了是王垠啊。。好吧。。我加上
【 在 icybee 的大作中提到: 】
: 摸摸头,标题是不是括号一下转载比较好,不然一看第一句因为年轻人的逆反心理,我还以为楼主老了
【 在 dss886 的大作中提到: 】
: 虽然对中间黑Python的部分不是特别认同,但是我想听听暖神对Scala的意见-。-
: @nuanyangyang
想听Scala啊,好吧。
我觉得,Scala和Java的关系类似于C++和C的关系。Java属于最小化的语言,用最少的特性却能完整地实现一个有垃圾回收的语言,而Scala在很多方面更为实际需求所想。
基本的语言级别的修正(我认为是Java本来应该做,但没有做的):
+ 类型推导:早就不是新技术了,Java推出的时候(1995),ML已经支持类型推导20年
了。(1973)。C#也已经支持类型推导了。
+ 不再区分“基本”类型和“对象”类型(但也有AnyVal和AnyRef),范型系统可以用基本类型作为参数:其实真正的问题是JVM不具备范型系统。这是Java1.5设计的时候的一大失误:2005年左右,Java设计者想推出范型系统,但又不想弄坏已经平静地为世界服务了10年(1995)的JVM,于是做了让步,类型参数只在语言级别有意义,翻译到JVM会被擦除(erasure),变成Object。这也就使得范型容器只能容纳Object。因此ArrayList<int>在Java里是非法的,但ArrayList<Integer>却合法。Scala没有这个限制。Seq[Int]是可以的。其实,.NET虚拟机真正合理地实现了虚拟机层次的范型。C#也没有Java的问题。
然后就是很多基本用法。比如:
+ Scala里,==运算符总是值相等的比较,即算数类型比较值,对象类型相当于调用.equals,但Scala里即使等号左边是null也没关系。
+ 在Java里需要用getter和setter,把域的访问用函数封装,以便将来想改实现以保证兼容性;但Scala本身的val和var就可以直接生成getter和setter。 (去看看笑话吧: http://bbs.byr.cn/#!article/Joke/696463 )。C#也对生成property有特殊语法。
+ 在Java里使用Singleton模式(有人说是反模式,我也同意),实现很麻烦,需要考虑类里嵌套一个容器类,还有原子操作什么的。但Scala可以直接声明object,就是单例了。
+ 在Java里需要使用Visitor模式,来处理固定的数据类型和可扩展的操作。但Scala的case class,和大多数函数式语言一样,直接解决了Visitor模式的需求。看: http://bbs.byr.cn/#!article/Java/37009
+ 在Java里需要使用Abstract Factory模式,但Scala里面,最常见的用法就是在“伴随object”里添加工厂方法。而“伴随object”也是object,可以实现抽象的工厂接口的。
+ 在Java里曾经需要手动关闭对象(比如文件),Scala里可以用回调函数来保证一个块结束时文件被关闭。后来Java1.7学到了try-with-resource
+ 在Java里需要手动实现lazy initialization,这个非常难以实现,必须正确使用锁、volatile或者原子类型,以保证多线程的正确初始化。但Scala有lazy关键字。
+ Scala几乎不使用null,而使用Option[T]表示对象的有无(这是ML语言的传统)。后来Java 1.8学会了Optional类型。
还有很多Scala特有的特性,如AnyVal类型,lambda,trait,for-comprehension,by-name参数,模式匹配等。后来Java1.8也学去了不少特性,如value-based类型,lambda等。
Scala里,所有的东西都是表达式,而不是语句,更像函数式语言。我个人认为scala的一致性比Java好得多。很多时候可以写出比Java更简练、更规则的程序。比如:
// 算除法,但如果除数为0就以0作为返回值
val a = try {
1 / 0
} catch {
case e: ArithmeticException => 0
}
// 获得列表首元素,但如果没有就抛异常。这个在Java里要判断null,然后拆箱,很麻烦。
val b = someList.get(0).getOrElse {
throw new IllegalArgumentException("List is empty.")
}
所以,从以上各个角度看,Scala都更像是一个真正为程序员着想的编程语言。如果说Scala太函数式了的话,C#应该是一个不错的折衷,但C#跑在.NET上,但我承认.NET设计得比JVM好。
如果说Scala哪里不好,主要是Scala实现在了JVM上吧。Scala明明有非常强的类型系统,但偏偏任何两个类型的AnyRef都可以比较equals。JVM并没有在运行时类型信息方面很好地帮助Scala,Scala以某种方式在JVM的class里编码了额外的类型信息。看看Scala的反射就知道了:Scala的反射API和JVM的反射完全不同,却又在各种地方纠缠(比如class loader什么的)。其实Scala的反射API不是专门为JVM设计的,看他们的文档就考虑到Scala的设计者们只把JVM当成一个后端,而不是唯一的后端。他们很早就有把Scala实现在.NET上的计划,但似乎至今没有实现。前一些日子,EPFL(做Scala的那帮人)中有学生联系了我们,似乎有把Scala从JVM上搬走,移植到别的虚拟机上的意愿,但我这里没有更详细的信息。
另一个麻烦是二进制兼容性。毕竟JVM不是为Scala设计的。一个Java程序如何翻译成JVM bytecode,有很固定的方法,所以即使JVM变化了,Java变化了,程序依然是兼容的;但是,Scala没有一个标准的往JVM bytecode上翻译的策略,所以,不同的Scala版本(如2.9, 2.10, 2.11等)生成的.class文件是互相不兼容的。所以,发布Scala的Jar包时,需要为每个Scala版本打一个包,这就是为什么Maven仓库上会同时有akka-actor_2.12.0-M3、akka-actor_2.11、akka-actor_2.10。Scala的策略是,x.y.z版本中,y的改变会破坏二进制兼容性,但z不会。即使y的二进制兼容性破坏了,源代码基本上还是兼容的。
凑巧的是,C++也有类似的二进制兼容性问题,libstdc++版本的更新会导致用旧的编译器编译出来的C++程序无法运行。某些Linux用户应该记得类似compat-libstdc++-3.x之类的包。原因嘛……可以说ELF可执行文件格式是为C语言而不是C++设计的吧,ELF可没有“函数名重载”的概念,C++编译起需要自己mangle。而且不仅仅是C++,连rust语言编译出来的东西都需要mangle,而且rust即使编译出来的是“库”,也不是.so的,而是自己的格式,需要记录额外的类型信息,那是ELF记录不了的,可以说Rust和Scala同病相怜。
JVM本身缺少很多功能,比如coroutine(类似Ruby的fiber的轻量级的上下文切换),没有这个,像Akka这样的暴力的大规模多线程效率会很低(JRuby的fiber也不得不用JVM Thread实现)。而Erlang的虚拟机,尽管本身线性执行的性能并不好,但多线程相当棒。
Scala本身语言比较复杂(肥硕?),导致工具支持也不如Java好,比如Eclipse上的ScalaIDE质量还比较粗糙,耗内存,有时候很慢,bug也很多。Scala编译器速度还比较慢。SBT工程管理工具太复杂,我觉得我的智力不足以理解它,但Java的Maven就很好理解。这些都是工程问题,只能靠有心人(主要是开发Scala的那些人吧)来维护了。
还有Scala提供的数据结构比起Java,更像函数式语言。像List(单向链表)作为基本的线性类型,这不总是最好的选择,有时候效率会受影响。如果是Scala库本身的开发人员,也许会纠结于作为函数式程序员的自尊,或者对纯粹性的追求吧,使用函数式的数据结构。有时候性能确实很好,但有时候,就需要放弃一些“自尊”使用效率更高的命令式结构了。Scala并不拒绝命令式编程。如果性能需要,可以用Scala的一个类似Java的子集写出形式和性能都类似Java的程序,也可以细心一点,选择适当的Scala数据类型。
总之,Scala并不是完美的,但我更喜欢Scala。