编程的思考
——观《C++与90年代程序设计语言发展趋势批判》1
--本文为北京《程序员》杂志2001/08 所载文章,作者: 陈铭,此处转载,版权归作者和CSND所有
?
在CSDN上读到几篇《C++与90年代程序设计语言发展趋势批判》(C++?? A Critique of C++ and Programming and
Language Trends of the 1990s,3rd Edition,以下简称《C++批判》) 的译著,也读到不少C++支持者的争论,于是,拿E文的原文来读,顿觉感想颇多,不书不快!书与作者:
《C++批判》是其作者Ian Joyer在UNISYS公司使用C++进行系统软件开发的过程中逐步完成的。起初的第一、第二版完成于1992年,那时还只是在comp.lang.c++新闻组里发表。尔后,随着Ian Joyer在面向对象领域的深入研究,《C++批判》的第三版也于1996年面世,并且演变成一本数十页的小册子,其内容也由单纯的C++变成为多种程序设计语言的比较,其中包括了当时正如日中天的Java和Bertrand Meyer在《面向对象软件构造》一书中设计的Eiffel。到了1999年,《C++批判》的第四版更是编纂成书,名为《对象揭密:Java,Eiffel与C++》(Objects Unencapsulated: Java,Eiffel and C++??, Prentice Hall)2 ,在Amazon上颇有些成绩。
?Ian Joyer对读者的建议非常有趣:
?Buy it (Eiffel users), borrow it (Java users), or steal one and ceremonially burn it (C++ users).
?(Eiffel用户买来看,Java用户借来看,C++用户应该偷一本来烧掉!:))
?
读者是谁??
但凡熟悉Bjarne Stroustrup,读过侯捷先生文章的人都知道,多数专家是不愿虚耗精力于不同程序设计语言之间的比较,因为“主流语言之间的合理比较要耗费很大的精力,需要在广泛的应用领域有充分经验”,而且“作者们尽力表现的公正无私,但是最终都是无可救药地偏向于某一种特定的应用程序,某一种特定的编程风格,或者某一种特定的程序员文化”(C++之父访谈录)3。
而Ian Joyer却一下子将当时面向对象领域的三种最主流的语言放到一起比较,且一开场就撇下了公正无私的架子:C++只是将面向对象与传统的思维模式结合,存在很多问题,Java部分的解决了C++中存在的问题,而Eiffel则是完全经由面向对象的思维方式设计而来,与理想的编程模式最为接近。如此偏颇,不免让人猜想作者与Eiffel有亲!(Ian Joyer确实开发了一个Eiffel的编译器,并在网上变卖,但绝不至于要撰文来为自己的编译器广告)。看完《C++批判》的前言部分,答案便有了,本文的出发点在于面向对象的软件工程意念和面向对象语言的设计,而非一般意义的语言特性比较。由此,文章最贴和于从事面向对象程序设计语言研究的读者,C++的程序员也大可以把这本书当成是C++专家经验的变相体现(见本文第4,5部分)——我个人以为本书对于Java和Eiffel的使用者帮助不大。
另外,《C++批判》以一个独特的角度审视程序和程序语言的地位,发人深思。
何谓程序?何谓程序语言?
“程序即说明”,这是《C++批判》对程序的诠释,一方面是程序必须向编译器说明意欲何为(这是我们的通常认识),另一方面,程序自身也应该是其分析和设计的规格说明书(specification)。编程不仅仅指编写代码,而是分析,设计与实现的全过程!
据此,程序的特性必须由程序语言支持,《C++批判》引述了程序语言的定义:程序语言是一种规范,用于描述问题且易于改进。程序语言应该可以用来独立的完成以下工作4:
(1) 描述程序计算的目的。
(2) 将计算的过程组织成为一些具体的步骤。
(3) 在整个计算的过程中进行存储管理。
如果程序果真能够自我说明,对软件工程将会是多大的福音啊!传统语言(C++)确实十分缺乏对软件工程的支撑,在完全分析实现步骤之前,几乎无法得知程序的目的。(别跟我提注释,那不是语言强制特性!而且,你真的写了足够注释吗?)
Eiffel提供了Precondition(前提)和Postcondition(结论)的语言概念,这对于程序的可读性和编译器对程序语义的理解无疑大有帮助,有助于确保程序的正确。这也正是面向对象设计领域中“设计基于约定”(Design By Contract)在程序语言中的具体体现。5(“设计基于约定”是指所有接口(Interface)都需要显式说明其前提与结论,在采用接口继承设计组件时,组件必须在较接口松散的前提下,得出比接口更强的结论)
另外,《C++批判》认为,一种高级的编程语言应该将一些琐碎且与问题本身无关的细节移出程序员的职责范畴,而由编译器或运行支持系统代劳。而C++却仍然需要程序员完成大量的细节操作,并且其间陷阱密布,只有以来程序员的经验加以避免,比如:指针的new/delete操作;这就是为什么现代语言都不约而同的将存储回收作为不可或缺的一大特性。
传统的语言迫使我们信赖程序员,因为一个看似符合语法的语句很可能是造成错误的根源,而这种错误只能依靠程序员的经验来识别;这种不得已的依赖性或多或少的成了今天软件危机的诱因;而传统语言对软件分析设计支持的匮乏,给软件的设计与实现之间的同步造成了极大的困难,这两个缺点是《C++批判》对传统程序语言发动攻击的主战场,也是现代程序语言必须解决的主要问题。
设计理念的碰撞
设想一下当你兴冲冲的把刚买的电视机搬回家,却在使用说明上看到如下指示时的心情:
“切勿在调节色彩的过程中转换频道,否则将引起本产品严重损坏!”
即使迫不得已,为什么不是产品自身屏蔽该操作而要对用户提出要求呢?你一定这么想。
那么我们再回头看看C++的专家指南:
“切勿在数组创建过程中使用多态特性,否则你的程序将产生严重错误!”6
多么相似的描述!类似的行为还有:C++中的类型转换,非虚拟函数的隐藏等等等等。
既然某种程序语义会导致错误,为什么不从语法的角度根本杜绝这种行为呢?这就是《C++批判》倡导的Java和Eiffel的设计理念,于是,Java和Eiffel都没有指针类型,于是,Java中没有了多继承,于是与C兼容的特性更是被彻底的从这些语言中删除。程序语言应该专注于解决问题本身,而不是在与问题无关的实现细节上纠缠不休,这些细节完全可以成为编译器的职责所在,而语言中也需要融入更多分析设计成分。
在这种理念的指导下,Java和Eiffel被设计成面向对象领域的典范语言,其语法特性与C++相比显得轻盈动人。而且摒弃了传统特性的新语言对软件工程领域提供了强大的支持,各部分职责也清晰起来,只有设计人员才需要了解各种面向对象设计的要决,面向对象的编程则更多的通过语言的特性和限制来完成,程序员只需要关注于待解决的问题本身,不再需要熟记长篇累牍的程序语言专家经验。即使你从来没有听过所谓“设计基于约定”,没有深入学习过面向对象的设计,只是遵循Eiffel的基本语法,你一样可以设计出完全符合面向对象软件工程的程序!
多么完美!似乎软件工程的春天已经到来了。且慢,这一切并没有将程序效率和灵活性完全考虑在内!这种较比完美的面向对象语言设计将大量的琐碎工作移入编译器和运行支持系统中。但编译和运行终究是相对机械的过程,难以智能的做出恰到好处的取舍,在编译过程中完成的工作必须对程序语言本身加入一定的限制,而这种限制往往会使得程序的灵活性打折扣。而运行期间的附加功能无疑会对程序的效率造成一定的影响。
以前面提到的存储回收为例,如何将程序员从内存的管理中解放出来呢?一是在编译期间分析对象的产生与消亡,但动态创建的对象呢?怎样静态的判断何时释放其内存最为合适呢?不,你办不到!那么禁止对象的动态生成不就行了吗?你看,程序的灵活性受到影响了。好吧,我们让运行支持系统来管理内存,于是,我们必须在程序的运行期间对每一次对象创建过程进行一定的簿记工作,还要管理对象的引用计数,这些都给程序添加了额外的开销。对于需要使用大量对象的系统来说,这种影响将会非常明显!
另外,彻底摒弃对传统语言的兼容性将会对生活在现实中的程序员造成极大的伤害!如何去面对那些数以千计的用C语言完成的项目呢? 完全重写无疑是代价不菲,而对于一种相对过时的语言源代码的维护显然也是费力的事。
Bjarne Stroustrup总是在任何可能的场合强调:面向对象的思想虽然不错,但远非完美,C++也绝不仅仅是提供面向对象支持的! C++不是要被设计成面向对象的经典语言,而是能够造福于程序员们的通用语言。因而,Bjarne Stroustrup对C++的基本定义是:7
(1) 一种经过改进的更为优化的C。
(2) 支持面向对象编程。
(3) 支持泛型程序设计。
与C语言的兼容性居然被放在了最为重要的地位!其实Bjarne Stroustrup非常明白,“无论是语法还是语义角度,与C兼容的子集(尤其是类型转换的部分)都是C++中最欠合理优雅的部分”8。但是,对于希望取代C而成为主流语言的C++来说,与C保持兼容是一个非常关键的决策!这种特性不仅可以帮助C程序员比较顺利的过渡为C++程序员,而且极大的延长了已经使用C语言完成的项目的生命力。
C++与C语言的兼容以及它个通用特性注定它必须在灵活性和性能方面毫发无伤,这就注定了C++的语法和语义特性必然非常复杂。正如Bjarne Stroustrup所说,今天的C++标准是完全符合其设计初衷的,C++语言复杂性的缺憾,也是在其设计理念的指引之下的折衷之计。优秀的跑车应该允许车手打开箱盖,钻研其机理。不谙机车运行原理固然一样可以驾驶,而对于发动制动机制驾轻就熟更可以将跑车的性能发挥到极至。打开灵活之门,将最终决策留给程序员,这就是C++的基本设计理念!
不同的设计理念造就了C++和Java, Eiffel这些不同的语言。《C++批判》试图站在Java/Eiffel的设计角度审视批判C++,这种观察的角度终不免偏颇(如果反其道行之,由C++出发来看Java或者Eiffel,恐怕也能写出一筐的《Java,Eiffel批判》来),但是,这种偏颇并不影响《C++批判》的价值,有助于我们在深入了解C++优越性的同时,清楚的认识和对待C++设计中的不足之处(虽然这种不足与生俱来),更重要的是,《C++批判》引发的对于如何编程,如何使用程序语言的深层次的思考。
?
理想与现实——我们能做什么?
那么,有没有一种不失性能而又简洁易用的编程方式呢?随着CPU主频依循墨尔定律不断攀升,程序的性能瓶颈将会逐步淡化;而RISC技术的广泛采用又给编译技术,尤其是动态编译(JIT, Just In Time Compile)留下广阔的发展空间;加以面向对象软件工程的进一步发展,我们终将避开繁琐的细节而直面亟待解决的问题本身。
理想终究还只是理想,性能仍然是目前的大多数应用不得不考虑的问题之一,而灵活性与简洁性的权衡杠杆仍然是语言设计中悬而未决的问题。但作为语言使用者的我们,却拥有足够的自由空间来解决这些问题。
首先是工具的选择。没有完美的程序语言,“选择正确的工具完成工作,不要成为工具或语言的狂热分子!”9。C++是一种适用于多数场合的通用语言,但在某些特定的领域,确实存在很多比C++更为适用的程序语言工具,比如:Java,许多Script语言以及一些RAD工具都是针对特定领域设计的。正确的工具能够帮助我们完成很多工作,并且使得开发和维护过程事半功倍。
其次是经验的积累,泛泛的了解几种语言工具的语法无助于水平的提高。尤其是对于像C++这样复杂的语言,其蕴含的无穷能量需要通过实践的积累与专家经验的学习才能得以体现。C++给我们留下了灵活选择的余地,但我们应该深入的了解究竟应该怎样使用这种灵活性,如何避免对灵活性的滥用。比如:应该尽量避免使用的类型转换机制,以及使用标准C++库取代多数场合下的指针。《C++批判》中的指出的缺陷,也可以理解为“避免这样使用C++”。
最后,也是最重要的,是思想的修行,无论是Eiffel,Java抑或是C++,其幕后的面向对象的分析,设计和编程思想大同小异。这些思想理念的演进较语言工具的更迭更为稳定。避免在语法层面的细节处纠缠不休,直指其背后的设计理念与设计思想,才能在纷繁复杂的语言工具面前游刃有余,立于不败之地。例如:将面向对象的设计思想结合到C++的编程过程中,就能弥补C++自身对于软件工程支持的不足,使程序趋于完善。
断其章而取其义,不高明!
初次接触《C++批判》是在CSDN上读一篇关于虚函数使用的翻译段落,时有网友经典评论:不过是以错误的方式使用了C++,然后幸灾乐祸的说“你瞧,错了吧?C++不好吧?”。当时颇有同感,后来读了《C++批判》的全文,才明白,我们说的“错误方式”原本是建立在C++专家经验之上,而《C++批判》认为,程序员不需要去了解长篇累牍的所谓专家经验,对错判定应该更多的由语言本身的语法和编译器决定。以此角度出发,就不难理解作者的用意了。
《C++批判》一文的核心在其对语言设计的观点立场,撇下这个立场而孤立出文中的单章独节,往往使人产生错觉,从而更难于理解作者的观点和意图。
任何权威的作者或是经典的言论,失却了观察事物的角度,也就品不出权威,闻不见经典了。
断其章而取其义,实在不高明!
[1] C++?? A Critique of C++ and Programming and Language Trends of the 1990s, Ian Joyer
[2] Objects Unencapsulated: Java,Eiffel and C++??,?? Prentice Hall,?? Ian Joyer
[3] C++ FAQ, Bjarne Stroustrup, http://www.research.att.com/~bs/
[4] Element of Functional Programming,??? Addison-Wesley,??? Chris Reade
[5] Object Oriented Software Construction,?? Prentice Hall,?? Bertrand Meyer
[6] More Effective C++,?? Addison-Wesley,??? Scott Meyer
[7] The C++ Programming Language,? Addison-Wesley,? Bjarne Stroustrup
[8] The Design and Evaluation of C++, Addison-Wesley, Bjarne Stroustrup
[9] Exceptional C++,? Addison-Wesley,? Herb Sutter