哪些语言特性,有助于开发大型系统?
在大型项目上,Python 是个烂语言吗? 因为这个问题,我想到了另外一个可能有些建设性的问题,希望大家一起来探讨一下。
正好在看「深入浅出 NodeJS」,谈到 Node 的模块机制与包管理,于是联想到大型系统开发时需要的语言特性。
正好在看「深入浅出 NodeJS」,谈到 Node 的模块机制与包管理,于是联想到大型系统开发时需要的语言特性。
按票数排序
按票数排序
按时间排序
14 个回答
首先我认为,大型系统指的是,团队里面的人你认识和沟通不超过5%、没有文档就无法理解大多数人当初写那几行代码的意图、代码具有或者接近8位数行、而且需要长期维护、具有完善的自动化测试,所以制度本身和(面试、考核或certification等的)高门槛才是保证项目本身可以健康的生存下去的最重要的因素的系统。也不要以为这种代码不多,windows、office、maya、cad软件、战斗机控制程序、还有出了bug就可以吧杨利伟杀死的这些要命的程序——这些通常都复杂。完成这种系统,需要这个团队在根需求无关的、跟开发有关的几乎所有方面都实行独裁统治。
所以这种系统需要语言提供什么样的功能呢?我们知道,因为这几千万行都是同一个软件的代码,所以里面必然具有无数的互相依赖的模块,一万多人开发十几年下来,什么是什么都基本上理不清楚了。所以这个时候你做一个修改,最重要的是要保证不要把别人的代码搞烂。就算你跑完了所有自动化测试,如果存在一些东西是自动化测试所无法覆盖的,那你觉得你可以靠人肉排除来保证所有已知的、修过的bug不再出现吗?
这当然是不可能的,尽管不能100%解决问题,但是一个强大的静态分析软件是可以帮我们解决95%的问题(就是那些不能被自动化测试所覆盖的问题里面的95%)。于是这就要求这个语言必须要有一个强大的静态分析软件,或者potentially你可以写一个强大的静态分析软件。
写一个静态分析软件面临的最大的困难有两个,一个是name resolving,另一个是reference resolving。name resolving的意思就是,你得到了这个名字,你知不知道他究竟代表的是你源代码里面的哪一个函数或者变量。reference resolving的意思就是,你得到了两个指针,你知不知道运行到某一行确定的代码的时候,这两个指针是否指向同一个对象?
于是为了让静态分析软件更容易的做出来,我们需要语言本身可以让我们很轻松的分析出这两个resolving的内容。很难分析name resolving的一般是动态类型语言,譬如python、javascript、ruby这些。很难分析reference resolving的一般是带指针和引用的静态类型语言语言,譬如C/C++、java、C#这些。
我们知道,两个都容易的一般指的是那些在语法上要求你明确副作用的语言,譬如Haskell。如果世界上大部分人都是从lisp/scheme/ocaml/haskell开始学习的话,其实学习Haskell并没有那么难(一旦你习惯了C语言那一套你就晚了)。只要你招得到足够多的人,并且让那些人满足你制作出来的规章制度的话,Haskell是可以用来做大型系统的。
只不过这个事情在现实里面一般比较难满足,那name resolving和reference resolving取舍哪个好呢?因为解决reference resolving是在name resolving解决了的基础上才可以解决的,所以你只能保留name resolving。如果一个语言没有name resolving的困难,那你的静态分析软件的难度瞬间就下降了许多。根据上面的描述,这方面的代表自然是静态类型语言。
==========================================================
参考了@庄表伟 的评论,我觉得只要一个具有programming language背景的人,大概都知道什么语言的特性是在分析的时候跟name resolving或者reference resolving相关的。我可以狙击个简单的例子。
prototype:难以做name resolving
指针:难以做reference resolving,但是还好,因为那种超大的系统一般你不敢滥用指针,所以静态分析软件就算不能完全解决这个问题(本来也是无法完全解决的),也只会让他们成为corner case,落进5%里面。
dynamic dispatch:跟reference resolving有关
static dispatch:跟两者无关
pure:跟两者无关
等等。feature的列表实在是太多了,不想一个一个列出来。
============================================================
下面还有一个匿名用户提到了communication cost的问题。的确沟通成本是一个问题,但是语言根本不会对沟通成本有什么影响,因为你一个组有一万个人,就算你什么都不干,你能沟通多少东西。
还有编译时间。sqlserver这么大的程序也只需要10分钟,因此这个故事告诉我们,提高编译时间,不能靠语言,而要靠砸钱呀。你买得起一个大集群,我们微软的MSBuild就可以帮你把剩下的事情做完。
至于说C++的string和TMP这种变态,STL是不是半吊子类库我就不评论了,至于TMP——如果你不喜欢,只要你强制大家不要用,review的时候肯定会让所有用了TMP的人无法checkin,如果他们坚持使用,他们就会没有delivery,年底就会没有成绩,就会被末位淘汰,然后你就没有这种烦恼了。你想通过降低语言的自由度来起到什么帮助基本没什么用的,只要你把制度写清楚了就行了。
所以这种系统需要语言提供什么样的功能呢?我们知道,因为这几千万行都是同一个软件的代码,所以里面必然具有无数的互相依赖的模块,一万多人开发十几年下来,什么是什么都基本上理不清楚了。所以这个时候你做一个修改,最重要的是要保证不要把别人的代码搞烂。就算你跑完了所有自动化测试,如果存在一些东西是自动化测试所无法覆盖的,那你觉得你可以靠人肉排除来保证所有已知的、修过的bug不再出现吗?
这当然是不可能的,尽管不能100%解决问题,但是一个强大的静态分析软件是可以帮我们解决95%的问题(就是那些不能被自动化测试所覆盖的问题里面的95%)。于是这就要求这个语言必须要有一个强大的静态分析软件,或者potentially你可以写一个强大的静态分析软件。
写一个静态分析软件面临的最大的困难有两个,一个是name resolving,另一个是reference resolving。name resolving的意思就是,你得到了这个名字,你知不知道他究竟代表的是你源代码里面的哪一个函数或者变量。reference resolving的意思就是,你得到了两个指针,你知不知道运行到某一行确定的代码的时候,这两个指针是否指向同一个对象?
于是为了让静态分析软件更容易的做出来,我们需要语言本身可以让我们很轻松的分析出这两个resolving的内容。很难分析name resolving的一般是动态类型语言,譬如python、javascript、ruby这些。很难分析reference resolving的一般是带指针和引用的静态类型语言语言,譬如C/C++、java、C#这些。
我们知道,两个都容易的一般指的是那些在语法上要求你明确副作用的语言,譬如Haskell。如果世界上大部分人都是从lisp/scheme/ocaml/haskell开始学习的话,其实学习Haskell并没有那么难(一旦你习惯了C语言那一套你就晚了)。只要你招得到足够多的人,并且让那些人满足你制作出来的规章制度的话,Haskell是可以用来做大型系统的。
只不过这个事情在现实里面一般比较难满足,那name resolving和reference resolving取舍哪个好呢?因为解决reference resolving是在name resolving解决了的基础上才可以解决的,所以你只能保留name resolving。如果一个语言没有name resolving的困难,那你的静态分析软件的难度瞬间就下降了许多。根据上面的描述,这方面的代表自然是静态类型语言。
==========================================================
参考了@庄表伟 的评论,我觉得只要一个具有programming language背景的人,大概都知道什么语言的特性是在分析的时候跟name resolving或者reference resolving相关的。我可以狙击个简单的例子。
prototype:难以做name resolving
指针:难以做reference resolving,但是还好,因为那种超大的系统一般你不敢滥用指针,所以静态分析软件就算不能完全解决这个问题(本来也是无法完全解决的),也只会让他们成为corner case,落进5%里面。
dynamic dispatch:跟reference resolving有关
static dispatch:跟两者无关
pure:跟两者无关
等等。feature的列表实在是太多了,不想一个一个列出来。
============================================================
下面还有一个匿名用户提到了communication cost的问题。的确沟通成本是一个问题,但是语言根本不会对沟通成本有什么影响,因为你一个组有一万个人,就算你什么都不干,你能沟通多少东西。
还有编译时间。sqlserver这么大的程序也只需要10分钟,因此这个故事告诉我们,提高编译时间,不能靠语言,而要靠砸钱呀。你买得起一个大集群,我们微软的MSBuild就可以帮你把剩下的事情做完。
至于说C++的string和TMP这种变态,STL是不是半吊子类库我就不评论了,至于TMP——如果你不喜欢,只要你强制大家不要用,review的时候肯定会让所有用了TMP的人无法checkin,如果他们坚持使用,他们就会没有delivery,年底就会没有成绩,就会被末位淘汰,然后你就没有这种烦恼了。你想通过降低语言的自由度来起到什么帮助基本没什么用的,只要你把制度写清楚了就行了。
关于“大型系统设计”,每个人都应该看看这篇,Clojure 设计者 Rich Hickey 的 Keynote "The Language of the System" http://www.youtube.com/watch?v=ROor6_NGIWU 讨论了如何设计大型系统中的各种问题、组件特性和要求;其中捎带提到了 Clojure 一些特点,但并不针对 Clojure,非常值得一听。
------
任何降低 book keeping、logistics 和 communication cost 的特性。
降低 book keeping cost 的特性包括:GC、自动类型推导、内置模块化支持,甚至更极端的并行支持、分布式支持(Erlang 是最极端的例子)等。为繁琐的 book keeping 工作提供可靠而普适的内置支持是任何一门有节操的语言减少程序员出错机会的最基本表现形式。至少要有一个足够通用好用的内置字符串类型而不是拿半吊子类库来忽悠人啊 C++!
降低 logistics cost:提高(或完全去除)编译时间。对,除了 C++,你很少能找到 Build All 需要 1h+ 的项目。Linux Kernel 都基本不需要这个数。(@vczh 我应该是听 AutoDesk 的兄弟们说的,具体项目忘记了。虽然比不上贵司,他们至少应该不是缺钱的主……)
降低 communication cost:功能集正交:用同一种方法解决一个问题,一个技术专门用来解决一个问题。当你允许,甚至鼓励(通过内置类库)程序员用任何可能的方法解决问题的时候,这些代码都属于 write only 的类型。来 C++ 我们讨论一下为什么会有 TMP 这种变态东西,为什么会有 new vs malloc 。你给用户不必要的自由越多,他们就越不受控制——不受控制的结果就是它们再也没法相互沟通了。而失去沟通能力是项目一大必死原因的第一名。
吐槽了一篇 C++,补充一些其它的吧。
尽早发现错误的能力:静态类型、编译语言在这个方面有相当的好处;内置的前后条件、函数式语言的不变量,都是。Java / C++ 可以在编译时直接发现参数类型传错,Python 脚本说不定跑很久了才碰到。
工具支持。Go 直接提供了 gofmt 之类的工具,Java 有 Eclipse / IntelliJ 这些对重构有巨大帮助的辅助工具,更不说 JVM 的 JMX 。Python 的 pip 也算勉强在“工具”方面作了一点什么。没有标准、优秀的工具(包括环境、库)支持,很多时候是逼迫程序员自己拿石头凿轮子。

The Language of the System - Rich Hickey
http://www.tudou.com/programs/view/Ma6S2UEZh2k/
------
任何降低 book keeping、logistics 和 communication cost 的特性。
降低 book keeping cost 的特性包括:GC、自动类型推导、内置模块化支持,甚至更极端的并行支持、分布式支持(Erlang 是最极端的例子)等。为繁琐的 book keeping 工作提供可靠而普适的内置支持是任何一门有节操的语言减少程序员出错机会的最基本表现形式。至少要有一个足够通用好用的内置字符串类型而不是拿半吊子类库来忽悠人啊 C++!
降低 logistics cost:提高(或完全去除)编译时间。对,除了 C++,你很少能找到 Build All 需要 1h+ 的项目。Linux Kernel 都基本不需要这个数。(@vczh 我应该是听 AutoDesk 的兄弟们说的,具体项目忘记了。虽然比不上贵司,他们至少应该不是缺钱的主……)
降低 communication cost:功能集正交:用同一种方法解决一个问题,一个技术专门用来解决一个问题。当你允许,甚至鼓励(通过内置类库)程序员用任何可能的方法解决问题的时候,这些代码都属于 write only 的类型。来 C++ 我们讨论一下为什么会有 TMP 这种变态东西,为什么会有 new vs malloc 。你给用户不必要的自由越多,他们就越不受控制——不受控制的结果就是它们再也没法相互沟通了。而失去沟通能力是项目一大必死原因的第一名。
吐槽了一篇 C++,补充一些其它的吧。
尽早发现错误的能力:静态类型、编译语言在这个方面有相当的好处;内置的前后条件、函数式语言的不变量,都是。Java / C++ 可以在编译时直接发现参数类型传错,Python 脚本说不定跑很久了才碰到。
工具支持。Go 直接提供了 gofmt 之类的工具,Java 有 Eclipse / IntelliJ 这些对重构有巨大帮助的辅助工具,更不说 JVM 的 JMX 。Python 的 pip 也算勉强在“工具”方面作了一点什么。没有标准、优秀的工具(包括环境、库)支持,很多时候是逼迫程序员自己拿石头凿轮子。
说实话,多大算大这个问题很让我困扰。你说豆瓣那规模大不大?其实没用什么高大上的语言特性吧,Python这么憨厚的玩意儿,不也跑的挺好?
当然我知道同行们有些是看不上这种规模的项目,我也理解,也认同。但是战斗机,卫星,航空母舰,外星探险车,或者一个维护几十年,规模超过千万行的操作系统,这些项目对这个世界上多大比例的开发人员有参考和学习意义?但是如果我们无视这些极端场景下积累的知识和经验,又会错过什么?
我个人的经验来讲,“大型系统”,并不能对等于“优秀的产品”或者能让我心动的知识。做一个工程,需要各种各样的妥协,最理想的工具未必就是最终能帮助我们做成这件事的。但是我还是可以尽量的给出我自己心目中一些对项目开发有正面意义的语言特性。
一时能想到的,比较具体的点就这些了。至于那些形而上的理论,其实我很难说服别人,也很难被人说服。就不提了。
===========
忘了说,是否方便调试其实应该算在内,虽然抬杠的说可以说这个东西不是语言应该操心的问题,但现实是某种语言是不是方便调试除错还是可以比较的。
当然我知道同行们有些是看不上这种规模的项目,我也理解,也认同。但是战斗机,卫星,航空母舰,外星探险车,或者一个维护几十年,规模超过千万行的操作系统,这些项目对这个世界上多大比例的开发人员有参考和学习意义?但是如果我们无视这些极端场景下积累的知识和经验,又会错过什么?
我个人的经验来讲,“大型系统”,并不能对等于“优秀的产品”或者能让我心动的知识。做一个工程,需要各种各样的妥协,最理想的工具未必就是最终能帮助我们做成这件事的。但是我还是可以尽量的给出我自己心目中一些对项目开发有正面意义的语言特性。
- 一个安全方便的资源管理方案。主要是内存和IO资源。行内有个笑话,一群人说内存管理太重要了,不能交给程序员;一群人说内存管理太重要了,必须交给程序员。该给谁,具体的项目说了算,一个既有的质量达标的运行时,对于普遍的应用项目是有意义的。大到可以用来自夸的“大型项目”,小到手机app。GC、ARC、智能指针、内存池,都可以看作对资源管理自动化的方案。Java 虽然语法笨拙,但是JVM的质量确保它得到足够的拥护。C说不上有什么内置的自动化资源管理方案,所有优质的C项目都会自己实现一套,CPP理论上有非常好的方案,但是很多优质的CPP项目也是自己实现一套。
- 一个功能丰富好用的文本类型。这个东西在理论上根本不应该是个事儿,字符串,那玩意儿不就是个特化的线性容器么,好多专家都能说的头头是道。但是在应用领域,文本类型的支持程度,完全可以决定一个语言是否能流行开。基本上比CPP文本功能更弱的编程语言,现在是很难被广泛接受的。
- 模块和命名空间隔离能力。这个东西有些语言是放在编译和编码阶段,有的是跟运行时尽可能相关的。但是运行时的模块管理策略其实影响资源载入机制,也就一定程度上影响了效率。效率这个问题么,又是一个吵不完的罗圈架。很多人都觉得自己有过人的深刻见解。这个我无意参与争执,总之语言级别提供助记方案,通常总是有帮助的。
- 开发工具支持。我个人近几年除了iOS/Mac OS的GUI相关的开发,基本上是纯编辑器工作,这并不表示我完全否认开发工具的作用,相反好的IDE肯定对工作是有帮助的。我对IDE的负面观点主要集中在几点,一个是“好”语言不应该依赖于某一种特定的IDE,一个是语言应该对开发工具是透明的,工具能做到的,应该允许人手工也能做到,这样才能方便的发展团队自己的自动化过程。
- 类型约束。老实说我没觉得这个东西必不可少。它确实对开发有帮助,对错误容忍越少的项目,越需要类型约束帮助我们提高代码质量。但是也确实有很多项目就是用动态类型的语言哗啦哗啦写出来就最好了。程序没有面对那么极端的场景,我就用1/10的开发成本写个几万行的项目出来,再用1/10的成本写个万把行的测试,再用2/10的成本去测试行不行?很多项目是可以这么做的,而且我看这么做出来的项目很多并不小。
- 内置类型,特别是基础容器和算法库。质量高不高,好不好用,跟语言的结合程度,至少影响了使用上的舒适感。个人观点上CPP对C的优点,相当大的基于STL和BOOST,它们可以让我几乎忘了我在用一个“C” Plus Plus语言。Python 受到欢迎,很大程度也是因为[]和{}内置类型非常好用。类似的,现在对并发和异步的语言级支持,也是越来越受到重视,但是还没有内置类型这么重。很多时候我们还把它看做是需要程序员明确构造的东西。但是大概再过几年,异步和并发的细节也都移交给C和CPP这个层面了。
一时能想到的,比较具体的点就这些了。至于那些形而上的理论,其实我很难说服别人,也很难被人说服。就不提了。
===========
忘了说,是否方便调试其实应该算在内,虽然抬杠的说可以说这个东西不是语言应该操心的问题,但现实是某种语言是不是方便调试除错还是可以比较的。
我使用C/C++,Java多年,最近一年也学习了Scala, Clojure和Go语言。程序语言设计方面,我向@连城 请教的最多,他应该比我更有实力说一说,他写过一个笔记,理想的编程语言。
我理想的语言特性如下,条目很杂,有大有小:
我理想的语言特性如下,条目很杂,有大有小:
- 强静态类型。绝对不允许有隐式类型转换,例如从int32 到 int64,从int64到int32更不允许。C是一个弱类型语言,C++比C略强,但也不能算是强静态类型。Java也不算,Scala比Java略强。最强的是Hashkell。
- 除了静态类型,还需要有很强的自动类型推导,写代码跟动态语言一样,很简洁。例如C++11里面的auto, Scala里的val, var,目前,它们的自动类型推导还没有强到我心目中完美的样子。
- 要有闭包,函数作为一等公民。函数式编程在并发方面有很强的表达能力,例如Spark和Storm。也可以让代码写的很短,且在概念上很统一。C++11有了lambda, Java 8也即将有,Scala, Clojure更不用说了,Go语言也有。
- 要有面向对象。面向对象是一个很好的抽象工具,现在的很多程序员都接受了这个工具,所以要做成语言特性。但是不能做成像Java, C++这种,要做的像Smalltalk那样,基于消息传递的。Go语言的那种隐式接口,跟C++, Java也很不同,不过我还没有理解透彻,目前只对消息传递有好感,从Scala的Actor里感受到的。
- 定长的基本数据类型。例如int32,uint32(Java,go语言就是这么做的,C99也有了stdint.h),实在不喜欢C/C++的标准,没有定死数据类型的长度,很多时候容易造成bug。其实很多C/C++项目都会重新 typedef int int32, typedef long long int64 定义一套定长的基本类型
- 字符串使用UTF8编码,源码文件也使用UTF8编码。国际化是必须的,UTF8已经是事实上的标准,何不采纳呢。Go语言是这么做的!
- 要有GC. 对于一个静态类型语言来说,每个对象的变量和生命周期是非常清楚的,应该让机器自动管理内存。这个时候有两种方案,智能指针(例如c++11里shared_ptr或unique_ptr)或GC(例如Java, Scala, Clojure, Go)。GC可以做得很薄,就成了智能指针了,所以GC不仅仅可以做到智能指针的事,还能做更多。
- 既然有了gc,就没有“指针”,只有引用了。指针这个东西,只是以前编译器技术不发达的时候出现的,现代编程语言应该一律用引用这个概念。
- 不区分栈变量和堆变量。Java就是这么做的,现代编译器技术,可以做到不损耗性能,又降低程序员负担。
- 没有分号。分号散布在代码里,充满了小麻点,太干扰视线了。现代编译技术完全可以做到不不需要分号,例如Scala, Go就是这么做的。
- 千万不要使用缩进语义(如Python, Hashkell那样很不好)。容易出BUG,也不容易自动格式化。一个大型工程中,一个程序员无意中添加了一个TAB,程序依然编译通过,但是意义不一样,从而添加了一个BUG,但是静态分析很难检查出这类错误。我很同意王垠这篇文章的观点,语法最漂亮的编程语言是哪种?
- 要有支持单机并行的标准库或语言特性。现在大部分机器都是多核了。不要用老旧的线程和进程模型,这种模型编程复杂,很容易死锁。Java 7里的Fork/Join 也不太好用。C++11加进了一个thread库,太落后了,现在谁还用线程这种抽象工具呢?像Scala里就有Actor,Erlang有轻量级process,Go语言有gorutine和channel,这几个接近我心目中的样子。
- 要有支持分布式的标准库。现在的服务端编程,多机集群是家常便饭,标准库应该有一个方便易用的工具,方便程序员编写分布式程序。例如Erlang OTP, Scala有AKKA(还是比不上OTP完整)。
- 类型放在变量名后。我个人也认为这样可读性要好一些。Go语言是这么做的,为什么 Go 语言把类型放在后面?,Scala也是,ActionScript也是。
首先我特别赞同 @vczh 关于大型系统的定义,而在这个定义下,他的答案似乎已经没有太多可以补充的了。但遗憾的是发现还是有很多人把这个问题看成了有哪些语言特性有助于让程序员更爽。我想说的是,其实让程序员更爽的特性,有助于开发大型系统的特性,几乎是两码事儿。
@vczh 的答案很抽象,抽象的东西总是很NX的,也就是几句话能涵盖一篇十万字文章所说的东西,我没打算把这篇文章展开来。在他讨论的范畴之外,我来补充一点偏门些的东西。
1、C语系或者类C语言风格。
vczh也谈到了这一点:
甚至于即使你选择了一个类C风格的语言,仍然可能招不到足够的人员,事实上你的选择可能只有:C、C++、C#、Java,,,,,
2、尽可能多的编译器检查。
强类型检查是最基本的,可见性(也就是封装),只读性(const、readonly、final)以及等等等等,在人和编译器之间,你应当无条件的相信后者,因为后者出错的可能性和前者完全不是一个量级的。
一个合格的程序员和菜鸟的区别就在于合格的程序员永远不会怀疑编译器错了,而是怀疑自己错了。这是无数次对编译器的误解之后得到的宝贵经验。
3、足够大官方文档和类库。
足够大的官方文档可以避免两个程序员对某个函数所实现的功能和限制理解的完全不一致,足够大的类库可以避免当你制定了一个规范的时候需要去给程序员解释为什么使用这个开源项目而不是另一个。
这些,都可以大幅降低隐性的communication cost。
4、单元测试和IDE支持。
据我的个人研究统计,人犯错的概率比机器至少高出一百倍,事实上即使写程序超过十几年,在各种语言之间转悠一会儿之后,每年总有一次犯诸如把相等运算符写成赋值运算符的错误。即使戴了合适的眼镜,偶尔也会分不清分号和冒号,以及逗号和句点。
而机器即使运行一百年,也绝对不会把赋值运算符当作相等运算符。
@vczh 的答案很抽象,抽象的东西总是很NX的,也就是几句话能涵盖一篇十万字文章所说的东西,我没打算把这篇文章展开来。在他讨论的范畴之外,我来补充一点偏门些的东西。
1、C语系或者类C语言风格。
vczh也谈到了这一点:
如果世界上大部分人都是从lisp/scheme/ocaml/haskell开始学习的话,其实学习Haskell并没有那么难(一旦你习惯了C语言那一套你就晚了)而事实上遗憾的是世界上大部分人都是从C语言或者很像C的语言(Java啥的)开始学习的。所以选择一个非C风格的语言,那么首先,你可能根本就招不到做这个项目所需要的人手,,,,,,
甚至于即使你选择了一个类C风格的语言,仍然可能招不到足够的人员,事实上你的选择可能只有:C、C++、C#、Java,,,,,
2、尽可能多的编译器检查。
强类型检查是最基本的,可见性(也就是封装),只读性(const、readonly、final)以及等等等等,在人和编译器之间,你应当无条件的相信后者,因为后者出错的可能性和前者完全不是一个量级的。
一个合格的程序员和菜鸟的区别就在于合格的程序员永远不会怀疑编译器错了,而是怀疑自己错了。这是无数次对编译器的误解之后得到的宝贵经验。
3、足够大官方文档和类库。
足够大的官方文档可以避免两个程序员对某个函数所实现的功能和限制理解的完全不一致,足够大的类库可以避免当你制定了一个规范的时候需要去给程序员解释为什么使用这个开源项目而不是另一个。
这些,都可以大幅降低隐性的communication cost。
4、单元测试和IDE支持。
据我的个人研究统计,人犯错的概率比机器至少高出一百倍,事实上即使写程序超过十几年,在各种语言之间转悠一会儿之后,每年总有一次犯诸如把相等运算符写成赋值运算符的错误。即使戴了合适的眼镜,偶尔也会分不清分号和冒号,以及逗号和句点。
而机器即使运行一百年,也绝对不会把赋值运算符当作相等运算符。
我觉得 @徐辰 说得不错
从这个意义上说,根本不可能有一种语言(或者说一组互不矛盾的语言特性)可以解决真正大型系统的开发问题。
这也是为什么世界上会有那么多语言,却没有一个能够一统江湖。
大型项目的根本问题还在于管理,或者说是人的问题,而不是语言或工具的问题。
不过我在 在大型项目上,Python 是个烂语言吗? 这个问题的回答里也说过了,仍然有一些语言是对人的管理是有帮助的。
JAVA就是其一。
单就语言的技术特性来说,JAVA的优点并不算特别突出,缺点倒是不少。
但是在对开发人员的管理方面,它有一个极大的优点——优秀的JAVA程序员与差的JAVA程序员之间的差距相对较小,而且JAVA程序员好找……
这也是为什么很多“大型”项目都喜欢用它——不一定是最好的,但基本可以保证不会差到哪里去。
之所以有这样的优点,在于它的语言特性在很多方面作出了限制,尽量从技术上避免人犯错。比如GC,静态类型,引用,包,接口……
对了,还有很多现成的库等资源,加上强大的IDE。
而其它大部分语言虽然都可能做出更好的东西,但存在两个根本的问题:
第一:有更大的可能做出完全失败的东西,比如C++
第二:没有足够的人手,比如Haskell
最后,就个人而言,我非常不喜欢JAVA,因为它对于程序员来说太束手束脚了,而且写起来太啰嗦。
没有什么像样的“大型系统”是用单一语言开发出来的以SAP为例,底层是基于C/C++开发的Kernel,数据库层面用了SQL,应用层使用SAP自己的ABAP语言,GUI有两个版本,Windows版是用VC,跨平台版是用JAVA,还有Web前端用了JS,移动端用了Obj-C
从这个意义上说,根本不可能有一种语言(或者说一组互不矛盾的语言特性)可以解决真正大型系统的开发问题。
这也是为什么世界上会有那么多语言,却没有一个能够一统江湖。
大型项目的根本问题还在于管理,或者说是人的问题,而不是语言或工具的问题。
不过我在 在大型项目上,Python 是个烂语言吗? 这个问题的回答里也说过了,仍然有一些语言是对人的管理是有帮助的。
JAVA就是其一。
单就语言的技术特性来说,JAVA的优点并不算特别突出,缺点倒是不少。
但是在对开发人员的管理方面,它有一个极大的优点——优秀的JAVA程序员与差的JAVA程序员之间的差距相对较小,而且JAVA程序员好找……
这也是为什么很多“大型”项目都喜欢用它——不一定是最好的,但基本可以保证不会差到哪里去。
之所以有这样的优点,在于它的语言特性在很多方面作出了限制,尽量从技术上避免人犯错。比如GC,静态类型,引用,包,接口……
对了,还有很多现成的库等资源,加上强大的IDE。
而其它大部分语言虽然都可能做出更好的东西,但存在两个根本的问题:
第一:有更大的可能做出完全失败的东西,比如C++
第二:没有足够的人手,比如Haskell
最后,就个人而言,我非常不喜欢JAVA,因为它对于程序员来说太束手束脚了,而且写起来太啰嗦。
和语言无关,能够有效驾驭复杂度的方式是把系统分解成
原因在于,规则系统可以使得测试空间大大下降(正交了)。一旦规则系统被“proven”,剩下的就是检验驱动数据,而那是个很多人(比如业务经理)都能做的事,可以发动人海战术还不用担心沟通和协作瓶颈,没有coding style、personal style问题,也容易发现问题(可以把数据可视化、使用分析工具交叉检验数据)
- 用来驱动系统的数据
- 处理数据的规则系统
原因在于,规则系统可以使得测试空间大大下降(正交了)。一旦规则系统被“proven”,剩下的就是检验驱动数据,而那是个很多人(比如业务经理)都能做的事,可以发动人海战术还不用担心沟通和协作瓶颈,没有coding style、personal style问题,也容易发现问题(可以把数据可视化、使用分析工具交叉检验数据)
知乎用户、刘炜、匿名用户
赞同
说说我的个人理解
1. 很快的编译速度
c++ 没有想象的那么糟糕 -- 如果编译不是那么慢的话。 比如我们公司的c++ 分布式编译需要一个小时 -- 现在流行的scala 也存在这个问题。添加一个很小的特性 都需要很长的时间。
2. 比较好的 module system
接口和实现分离 -- 目前我熟悉的语言里只有 OCaml 实现了真正意义上的分离编译
3. REPL
对于一些测试 和探索大型的library 非常有用
4. 代数数据类型 和模式匹配
我最大的take away (from functional programming) 不是类型系统 而是ADT 和pattern match -- Mozilla 的Rust 恰好都有
1. 很快的编译速度
c++ 没有想象的那么糟糕 -- 如果编译不是那么慢的话。 比如我们公司的c++ 分布式编译需要一个小时 -- 现在流行的scala 也存在这个问题。添加一个很小的特性 都需要很长的时间。
2. 比较好的 module system
接口和实现分离 -- 目前我熟悉的语言里只有 OCaml 实现了真正意义上的分离编译
3. REPL
对于一些测试 和探索大型的library 非常有用
4. 代数数据类型 和模式匹配
我最大的take away (from functional programming) 不是类型系统 而是ADT 和pattern match -- Mozilla 的Rust 恰好都有
知乎用户
赞同
从编程语言本身来讲,标准库丰富、规范化、工程化的编程语言更适合开发大型系统。从编程语言的生态环境来看,这个问题非常复杂。这可能会涉及到人力成本、技术环境支持和系统的业务领域等几个问题。
尽量不做第一个吃螃蟹的,有大型系统做先例的我觉得都可以
erlang够小众 资料也不多 ide更是渣的很
但就是可以用在生产环境,因为爱立信都用了你还怕个鸟啊
当然国内游戏服务器也有用的
BTW:京东一直嚷嚷着要从ASP 换到j2ee 说是这不行那不行,难道asp就这么不堪吗?
微软中国、当当网没法活了?
erlang够小众 资料也不多 ide更是渣的很
但就是可以用在生产环境,因为爱立信都用了你还怕个鸟啊
当然国内游戏服务器也有用的
BTW:京东一直嚷嚷着要从ASP 换到j2ee 说是这不行那不行,难道asp就这么不堪吗?
微软中国、当当网没法活了?
能想到的唯一有助于开发大型系统的“语言特性”就是——慢,慢到令人发指,慢到让所有人对这个语言开发出来的系统性能不报任何期望,慢到PRD中没法加入任何性能指标,然后你就会发现大型系统无非就是放大镜下的小型系统了(任何对这一段有疑问的同学请参见所有的ERP/CRM等“大型系统”)。
除此之外,其它的语言特性和系统规模关系不大,因为没有什么像样的“大型系统”是用单一语言开发出来的(表举反例,所有的反例都不够大)。
除此之外,其它的语言特性和系统规模关系不大,因为没有什么像样的“大型系统”是用单一语言开发出来的(表举反例,所有的反例都不够大)。
这个问题是我提的,主要的原因当然是因为自己也有所思考,想与各位朋友有所交流。不过,这篇回答却拖了很多天,主要是腹稿没有打好的缘故。
一、首先需要定义,何谓大型系统?
有一个答案是@jifei 写的,虽然他的答案被折叠了,但是我认为其实他提出的问题非常重要,必须首先解答。
在我看来,在软件编程这个领域几十年的发展过程中,对于大型系统的定义,是一直在扩大着的。最近我翻一本介绍编程语言的老书,说到当年 FORTRAN 语言刚刚面世的时候,尚且不能支持分别编译,导致程序超过400~500行,编译器就会死机。那个时候的大型系统,也许就是超过500行代码的程序吧。
随着软硬件各方面的发展,现在的程序已经远远超过这个规模了,按照@vczh 的定义,得超过8位数才行......
这就引出第二个需要讨论的问题,系统的大小,是由问题域的复杂度决定的?还是由解答域的复杂度决定的?
打个比方,如果在 UNIX 刚刚诞生的1969年,我们只有PDP-7这样的计算机,编程语言也只能选择 C 语言。如果想要写一个知乎这样的网站,得花多少代码量呢?得付出多少的人力、物力呢?这样的一个网站,算不算大型系统呢?
假设一个系统,用 A 方法、A 语言实现,需要100万行代码;用 B 方法、B 语言实现,只需要10万行代码。这个系统的算大型?算中型?还是算小型呢?
所以,在这里首先抛出两个观点:
如果泛泛而谈,绝大多数的语言特性,在最初设计的时候,都是为了提高开发效率,简化开发劳动,从而帮助开发者开发更加大型的系统的。
汇编替代机器码是如此,高级语言替代汇编也是如此。举一个极端的例子:
最初的 FORTRAN 只允许 IJKLMN 六个变量作为循环中的变量。据说大多数编程只需要用到IJK 三个,FORTRAN 的设计者已经大发慈悲、一口气给了六个了。
到后来,变量名、函数名、方法名、类名都允许任意命名、任意长度,这就是一个巨大的进步,因为有意义的变量名,能够帮助写出有意义的代码。
再到后来,ruby 有一个小小的改进,方法名中允许出现?号和!号,虽然只增加了一点点,但是也不无价值。
三、早期的语言特性,大多数是为了方便程序员而设计的
早期的语言发展,可以说是在一片蛮荒之中,披荆斩棘地前进。在没有浮点运算单元之前,只能模拟浮点运算,这是当年程序员的一大噩梦,但是到后来,这都不算一回事。
函数定义,是为了能够少些重复的代码。等到有高级语言,支持函数的递归调用以后,程序员就能够写更加简单和自然的代码了。
绝大多数程序员,都最痛恨重复劳动,所以 DRY 原则是人人信奉的最高准则。众多的语言特性,也为了更好的代码重用,而被设计出来。比如模板、比如继承之类。
四、意识到程序员需要被限制,是语言发展历程中的一大转折
程序员这个职业,经历了一个逐步走下神坛的过程。最初的程序员,都是天才、都是神级的家伙,他们不会犯错,唯一限制他们的创造力的,是烂机器、烂工具与烂语言。
渐渐的,程序员越来越多,那些不是天才的家伙,也混入了程序员的队伍。天才们非常烦恼,却毫无办法。
终于,有一些大神受不了了:「我要设计一种语言,让那些家伙去用,通过限制,让他们没法犯错!」
(以上为演绎,请勿对号入座)
Java 这样的语言,在历史上出现,并且广为流行,是有其必然性的。虽然,其中的有些强制(比如 Exception),突显了语言设计者的傲慢与自以为是。
五、类库与框架的出现,是一大进步
常年的劳动,使程序员们逐步的意识到,大型系统之间,是存在相似之处的,是可以归类的。他们需要解决的问题,是可以逐步提炼出通用的解决方案的。
最终,开发团队的工作分为两个阶段:挑选成熟的、适合系统需要的框架与类库,编写自己必须开发的代码。
这时,对于语言特性的需求,就新增了对于框架与类库编写的支持能力。
RTTI、反射、Interface、元编程、DSL 之类的技巧,被发展出来。
插播、TDD 与单元测试思想的兴起
TDD 是个好东西,它从根本上分担了质量保证的责任,而不少语言,开始提供「assert」这样的关键字,也是一种语言层面的响应,个人认为是有进步意义的。
六、开源时代与远程包管理
最为典型的例子是 ruby 的 gem 和 nodejs 的 npm,通过这两个工具,软件开发中自造轮子的现象大大减少,程序员们越来越习惯于首先搜索可能适用的 package,然后 net install 进来。
这虽然不全然是语言特性,但确实是语言支持特性的最新的发展方向。
仅仅比较 ruby 和 nodejs 来说,nodejs 的包管理比 ruby 更加完善合理,有兴趣的朋友,可以去了解一下。
七、高负载、高并发带来的需求
这个值得专题讨论,此处不再赘述。个人认为 Go 的出现,的确是针对这一类问题,有了更加深入的思考。
八、结论
一、首先需要定义,何谓大型系统?
有一个答案是@jifei 写的,虽然他的答案被折叠了,但是我认为其实他提出的问题非常重要,必须首先解答。
在我看来,在软件编程这个领域几十年的发展过程中,对于大型系统的定义,是一直在扩大着的。最近我翻一本介绍编程语言的老书,说到当年 FORTRAN 语言刚刚面世的时候,尚且不能支持分别编译,导致程序超过400~500行,编译器就会死机。那个时候的大型系统,也许就是超过500行代码的程序吧。
随着软硬件各方面的发展,现在的程序已经远远超过这个规模了,按照@vczh 的定义,得超过8位数才行......
这就引出第二个需要讨论的问题,系统的大小,是由问题域的复杂度决定的?还是由解答域的复杂度决定的?
打个比方,如果在 UNIX 刚刚诞生的1969年,我们只有PDP-7这样的计算机,编程语言也只能选择 C 语言。如果想要写一个知乎这样的网站,得花多少代码量呢?得付出多少的人力、物力呢?这样的一个网站,算不算大型系统呢?
假设一个系统,用 A 方法、A 语言实现,需要100万行代码;用 B 方法、B 语言实现,只需要10万行代码。这个系统的算大型?算中型?还是算小型呢?
所以,在这里首先抛出两个观点:
- 大型系统的定义,一直在不断增大
- 大型系统的规模,取决于问题域,而非解答域
如果泛泛而谈,绝大多数的语言特性,在最初设计的时候,都是为了提高开发效率,简化开发劳动,从而帮助开发者开发更加大型的系统的。
汇编替代机器码是如此,高级语言替代汇编也是如此。举一个极端的例子:
最初的 FORTRAN 只允许 IJKLMN 六个变量作为循环中的变量。据说大多数编程只需要用到IJK 三个,FORTRAN 的设计者已经大发慈悲、一口气给了六个了。
到后来,变量名、函数名、方法名、类名都允许任意命名、任意长度,这就是一个巨大的进步,因为有意义的变量名,能够帮助写出有意义的代码。
再到后来,ruby 有一个小小的改进,方法名中允许出现?号和!号,虽然只增加了一点点,但是也不无价值。
三、早期的语言特性,大多数是为了方便程序员而设计的
早期的语言发展,可以说是在一片蛮荒之中,披荆斩棘地前进。在没有浮点运算单元之前,只能模拟浮点运算,这是当年程序员的一大噩梦,但是到后来,这都不算一回事。
函数定义,是为了能够少些重复的代码。等到有高级语言,支持函数的递归调用以后,程序员就能够写更加简单和自然的代码了。
绝大多数程序员,都最痛恨重复劳动,所以 DRY 原则是人人信奉的最高准则。众多的语言特性,也为了更好的代码重用,而被设计出来。比如模板、比如继承之类。
四、意识到程序员需要被限制,是语言发展历程中的一大转折
程序员这个职业,经历了一个逐步走下神坛的过程。最初的程序员,都是天才、都是神级的家伙,他们不会犯错,唯一限制他们的创造力的,是烂机器、烂工具与烂语言。
渐渐的,程序员越来越多,那些不是天才的家伙,也混入了程序员的队伍。天才们非常烦恼,却毫无办法。
终于,有一些大神受不了了:「我要设计一种语言,让那些家伙去用,通过限制,让他们没法犯错!」
(以上为演绎,请勿对号入座)
Java 这样的语言,在历史上出现,并且广为流行,是有其必然性的。虽然,其中的有些强制(比如 Exception),突显了语言设计者的傲慢与自以为是。
五、类库与框架的出现,是一大进步
常年的劳动,使程序员们逐步的意识到,大型系统之间,是存在相似之处的,是可以归类的。他们需要解决的问题,是可以逐步提炼出通用的解决方案的。
最终,开发团队的工作分为两个阶段:挑选成熟的、适合系统需要的框架与类库,编写自己必须开发的代码。
这时,对于语言特性的需求,就新增了对于框架与类库编写的支持能力。
RTTI、反射、Interface、元编程、DSL 之类的技巧,被发展出来。
插播、TDD 与单元测试思想的兴起
TDD 是个好东西,它从根本上分担了质量保证的责任,而不少语言,开始提供「assert」这样的关键字,也是一种语言层面的响应,个人认为是有进步意义的。
六、开源时代与远程包管理
最为典型的例子是 ruby 的 gem 和 nodejs 的 npm,通过这两个工具,软件开发中自造轮子的现象大大减少,程序员们越来越习惯于首先搜索可能适用的 package,然后 net install 进来。
这虽然不全然是语言特性,但确实是语言支持特性的最新的发展方向。
仅仅比较 ruby 和 nodejs 来说,nodejs 的包管理比 ruby 更加完善合理,有兴趣的朋友,可以去了解一下。
七、高负载、高并发带来的需求
这个值得专题讨论,此处不再赘述。个人认为 Go 的出现,的确是针对这一类问题,有了更加深入的思考。
八、结论
- 语言特性,是一个历史发展的过程,最好从历史的角度来考察
- 设计者的初衷往往都是好的,但是实际上有很多语言特性,事与愿违
- 通过不断的分解,很多开发大型系统的困难,已经不再需要在语言层面解决
- 编程语言仍然年轻,还在不断进步,值得不断的关注与学习