《Unix 编程艺术》 第十四章 语言
#14.1 Unix 下语言的丰饶
Unix 上运行的语言种类完全可能超过计算史上其它所有操作系统的总和,至少有两个充分的理由造成了这种巨大的多样性:
- Unix 广泛用于研究和教学平台。
- Unix 传统鼓励专门领域语言的设计,和现在一般称为“脚本语言”的东西——为了把其它应用程序和工具胶合起来而专门设计的语言。
“脚本语言”是个有点整脚的术语。很多通常这么称呼的主要语言 (PerlTcl、Python 等)都已经超越了最初意义的脚本用途,已经成为威力相当强大的独立通用编程语言。
把所有这些语言都纳入“脚本语言”的部分原因在于,这些语言都具有非常一致的发展历程。在运行期完成解释使得动态存储管理的自动化相对容易,而这几平要求采用引用(存储地址不透明且无法进行运算)而不是传值或显式指针。使用引用可使下一步更容易实现运行期的多态性和 OO。
#14.2 为什么不是 C
C 和 C++以增加实现时间和调试时间为代价来优化效率。尽管以 C 或 C++编写对时间要求极高的系统程序或应用程序内核似乎还有意义,然而自从这些语言在 1980 年代崛起,世界已经发生了很大变化。在 2003 年,用几平同样的价钱能够买到的处理器快了 1000 倍,内存大了 1000 倍,,磁盘容量也大了 10000 倍。
急剧下降的成本从根本上改变了编程的经济含义。大多数情况下,和 C 一样节约机器资源已经不再有任何意义。相反地,经济方面的最优选择已经变成尽可能减少调试时间、尽可能延长人类对代码的长期可维护性。
C 和 C++的中心问题在于它们要求程序员自己完成内存管理—声明变量、显式管理链表、设置缓冲大小、检测或防止缓冲溢出,以及分配和回收动态存储。…尽管缺乏确凿坚实的数据,但很多经验丰富的程序员都相信,内存管理产生的 bug 时真实代码持续产生错误的最大单体来源。
#14.3 解释型语言和混合策略
避免手工管理内存的语言通过在运行期可执行体中嵌入一个内存管理器来完成内存管理。
通常情况下,这些语言的运行环境分成程序部分(运行脚本本身)和解释器部分解释器管理动态存储。在 Unix(以及其它现代操作系统)中解释器内核由多个程序部分共享,减少了各个程序的实际开销。
#14.4 语言评估
混合语言是一种知识密集型(而不是编码密集型)的编程。要让它能够工作,我们不仅应该具备相当数量的多种语言应用知识,并且还必须具备能够判断这些语言在什么地方最适合、以及怎样把它们组合在一起的潜经验。
#14.4.1 C
尽管存在内存管理问题,但 C 语言还有一些仍然可以在其中称王称霸的生态环境要求速度最快并且具有实时需求的程序,或者与 OS 内核紧密联系的程序非常适合用 C 编写。
即使更高级的语言能够满足编程的要求,我们仍然要学习 C,其中一个充分理由就是 C 能帮助我们学会在硬件体系层次上考虑问题。
#14.4.2 C++
当 C++在 1980 年代中期首次公布于世时,面向对象 (OO)语言正被大肆吹捧为解决软件复杂度问题的“银弹”。
C++面向对象的特性对于其前辈 C 而言是个压倒性的优点其拥期望 C++能够迅速废掉更古老的 C 语言。
这种情况当然并没有发生。部分可归因于 C++本身的问题:对向后兼容 C 的要求迫使 C++在设计中做出了许多妥协。而且这个要求阻碍了 C++完全自动化动态内存管理从而无法解决 C 语言最严重的问题。后来,薄弱、不成熟的标准化努力,并不能限制各个不同编译器实现者之间的功能特征竞赛,C++变得过分精微复杂了。
对 C 的向上兼容;面向对象的平台;STL 和泛型等最前沿的技术工具——C++ 试图满足所有人的所有要求,但代价是 C++ 比任何一个程序员所能处理的复杂度都要高。C++ 的主要设让者已经承认他不指望任何一个程序员能够完全掌握 C++。
Unix 黑客对此并没有很好的反应:一段匿名但非常著名的评论这样描述“C++:狗被钉上软肢而变成的章鱼”。
C++ 最根本的问题还是在于它根本上只是另外一种传统语言。在标准模板库发明后,C++的内存管理控制有所改善,比 C 好得多,但仍然十分脆弱:除非代码仅使用对象,否则控制仍旧无用。对许多类型的应用而言,C++的 OO 特性并不重要,没有带来多少优势,陡增复杂度而已。
总结:C++的最佳之处是编译效率以及面向对象和泛型编程的结合。最糟之处是它非常怪异复杂,往往鼓励过分复杂的设计。
#14.4.3 Shell
简单 shell 程序的编写极其容易和自然。Unix 使用解释型语言的快速原型设计传统就始于 shell。
然而,随着程序规模越来越大,这些程序往往变得非常专用。部分 shell 语法(特别是其引用和声明语法规则)变得十分混乱。为了 shell 作为交互式命令行解释器的实用性而在设计中对语言部分做了折中,这些缺点就是这么来的。
现在 shell 只是为最简单的包装器(那些语言使用在包装器上是大材小用)和系统启动时的初始化脚本而保留。复杂的 shell 脚本经常产生可移植性问题,主要原因并不在于 shell 本身而在于 shell 使用了某些它假定存在的程序。
总结:shell 的最佳之处在于书写小型脚本非常自然快捷。最糟之处在于大型 shell 脚本必须依靠大量辅助命令,而这些辅助命今不一定在所有日标机器上都表现一致甚至不一定存在。要在大型 shell 脚本中分析依赖关系并不容易。
#14.4.6 Python
Python 的语法介于 C 语言和 Modula 系列语言之间,但有个非常罕见的特征,即代码块结构实际上用缩进来控制。
Python 语言的设计是非常于净优雅,具有非常出色的模块化特性。它提供了设计者用面向对象风格编码的可能,但并不把这个选择强加于设计者(可以用更加经典的类 C 方式编码)。
Python 在纯执行速度方面无法同 C 或 C++竞争(尽管在现今快速处理器上应用混合语言策略使这点已经相对不那么重要)。实际上,人们通常认为 Python 是主要脚本语言中效率最低、速度最慢的语言,这是它为运行期类型多态付出的代价。
然而当心,别因为这些原因而拒绝 Python,Python 能够提供的性能实际上已经满足人多数应用程序,即使那些似乎需要更好性能的程序通常也受到网络等待或磁盘等待等外部延时的限制,从而完全抵消了 Python 解释型开销产生的影响。
总结:Python 的最佳之处在于它鼓励清晰、易读的代码,易学易用,又能够扩展到大型项目。最糟之处在于,不仅相对于编译语言,而且相对于其它脚本语言,它也是效率低下、速度缓慢的。
#14.4.7 Java
Java 编程语言的设计目标是“write once, run anywhere(一次编写,到处运行)”并且支持网页中嵌入交互程序(即 applets),可在任一个浏览器中运行。由于其所有者 Sun Microsystems 的一系列技术和战略失误,Java 没有实现这两个最初的设计目标。但它在系统编程和应用编程方面仍然十分强大,足以挑战 C 和 C++。
尽管远比 C++ 小巧简单,但 Java 设计非常聪明地抓住了自动管理内存的巨大优势也抓住了支持 OO 设计这一虽小却并非不重要的优点。Java 保留了大量的类 C 语法,大多数程序员对此感觉非常舒服。它也包括支持动态载入的 C 调用并支持在 C 中把 Java 作为嵌入语言调用。Sun 公司在网上提供良好 Java 文档的工作完成得非常出色,这一点作用也不可小觑。