C++的表面困境来自两方面,一是开发效率低,而是轻易犯错,维护难度大。此二者俱是表象,本质就是一个——过度复杂。或有人说C++之要害缺陷是没有统一完整的类库支撑,Bjarne Stroustrup即强调此因素。然而这其实只不过是一个结果,而不是原因。正是因为语言太复杂,才无法在有效期内开发出高质量的大一统的类库。
C++的复杂,并非是其体积庞大之必然结果。复杂是对结构混乱无序程度的描述,规模大,结构不见得必然复杂。
C++的复杂,也并不是如很多人所认为,是若干种编程范式(paradigms)的并存而至。事实上,现代实用编程语言至少有2-3种范式才能登大雅之堂。以范式数量论,Python和Ruby等新型动态语言的范式甚至多于C++,然而它们却以简单和开发效率高著称。
C++复杂的根源在于三大约束:与C的完全兼容、静态类型检查、最高性能。在三大约束下,C++未能完善对于面向对象思想的支持,未能建立强大的动态能力,从而使得C++在OO这个单项上存在本质缺陷。事实上,C++的过程、OB模型相当成熟和稳定,而泛型模型,就单项来说,除了语法丑陋之外也没有大的问题。缺陷集中体现在OO模型的实现,并因此干扰了其他几个范式的完整程度。然而,OO的缺陷绝非设计者的偏执,其原因在于三大约束。假如坚持三大约束,则即使再重新设计一次,结果也与今日相差不远。Stroustrup在多种场合表示,对C++的设计没有大的后悔之处,意思就是这个。侯捷先生早在2001年初即对我说,C++在OO上不及Java,当时体会不深,认为没有大一统的单根类库会使设计更加灵活,后来又认为凭借GP可以抵消OO的不足甚至超越之,现在看来即使不是不可能,这条道路也必然是艰辛异常,成败难以预料。
又因为上述所有因素的综合作用,C++基础类库的建设只能进行到很低的高度上就停下来,因为再往上走就面临重重困境和无穷无尽的争论。C++标准库实际上是一个距离应用相当遥远的非常基础的程序库,其主体部分只相当于Java中System和Util两个package。而C++宁可停在这样的低层次,也不愿意放弃三大约束中的任何一个。这种执着使得高层标准库设施的建立异常困难,使用也不轻易。Boost库中相当部分组件的易用性不佳。
模板的复杂语法与三大约束也有直接的关系。另一个原因是Bjarne在发明模板时目标单纯。C#和Java加入泛型机制的时候,没有继续C++最好的经验,却不约而同地继续了C++模板机制中最坏的部分——语法,短期来看,丧失了一次改革的良机。长远来看,必成累赘。
不完善的异常机制则是在木已成舟的情况下迫不得已的设计。
C++中的多种范式并行,是一些最复杂问题的表面原因。以至于Doug Lea建议在一个项目里只坚持一个范式。但是这仍然只是表象。归根结底还是因为OO的缺陷,使得与其它范式合作时困难成倍放大。故自接受Doug Lea思想以来,我的C++(乃至其他现代语言,尤其是Python等多范式语言)的开发设计思路是:
1. 首先选定一种思维方式(即范式),尽可能只用这一种思维方式解决问题;
2. 假如在局部碰到其他思维方式更得力的问题,则经慎重考虑后,可以将另一种风格包装在局部,解决局部问题。但整个系统在某一层次之上看来,应当是统一一致的。一般C++的开发,应以OB为基本风格。除非有类似MFC那样庞大而成熟的OO库支持,不应贸然在整体上使用OO风格。
3. 多种风格混用,除非有已被充分讨论并验证的方案(即成熟模式),可提供单一风格不能提供的较大优势,否则应极力避免。当然鼓励在研究中探索,但实践是另一回事。
C++完全可以在90年左右摆脱C的约束,随后简化模板语法,完善异常模型,接纳可选GC,建立完整的单根类库,付出性能小幅度下降的代价之后,实现语言整体升级。
但是C++选择了另一条路,三大约束坚持到底,坚守系统层面,以替代C为己任。是福是祸,实难判别。假如90年代初选择升级,胜则扼死Java于摇篮之中,败则寸土不保。不过以C++之高性能,胜面应稍大。如今看来,在系统面彻底取代C已无可能。
1994年为STL拖延标准立案时间长达四年,如今来看功过亦存争议。错过黄金时机不说,STL典范一立,库设计风气为之一改。然而在解决应用问题上,泛型较之OO,适应能力远逊之,且应用困难。
总之,C++的三大约束,既是其兴起之要素,也是其衰落之源头,同时,又是其今天得以屹立不倒的重要基石。其是非功过,实难一言以蔽之。
C++/CLI之对于C++的意义,其实并不在于使C++重新获得了制胜Java或者C#的机会,而在于巩固了C++作为.NET平台上系统语言的地位。由此知,C++/CLI的发展,的确如Stan Lippman所说,是C++一贯发展思路的延续。三大约束固然已经放弃,但其精神实质仍在,形攻而实守,未来将可作为.NET上唯一最强之系统语言而长命百岁。
C++/CLI决不简单,但在大多数时候,它能够比传统的C++表现的简单些。这就是Andrew Koenig说的,通过复杂实现简单。
C#和Java的繁荣期,则有赖于人们对于大一统的中层次语言的信仰有多坚持。此两种语言无论在系统开发还是在应用开发中都非最优选。目前C#出现一些迹象,引入一些动态语言特性如cmdlet,又强化系统编程能力,想上下通吃。这是一条不归路,必会使C#变得更加复杂怪异。
学习编程语言,通语法能实践,不过十分之一。真正重要的是把握其多种多样的实用的idioms或模式。这些模式才是体现了语言精神的东西。未把握各种语言中的主要应用模式,则应羞于用“会”字。常听有人说某某语言一周乃至一两天即可把握,这个把握的层次肯定是很低的。真正要“把握”语言,则我等凡人,诸事缠身,非得集中精力学习实践一两年,将该语言所擅长领域的应用问题熟悉过一遍,才有可能。若论精通,则十年也不轻易。Henry Spencer用了30年C,仍乐此不疲;Pragmatic Programmer中评价Ruby说,学上四个小时就可以用它解决实际问题,但是10年之后还为它层出不穷的新意感到惊奇。偶见有人举出自己“精通和把握”的工具和语言,动辄长达八九上十种,实为笑柄。真正把握一种,已经是难能可贵,熟练把握两种层次不同,思维不同的语言,应是有抱负的程序员的自我要求。何况如今之软件开发涉猎甚广,仅通编程层次还显不够。不过总之百招会不如一招精,做什么工作都要有自己的过人之处。