从C/C++与Java的语言设计看代码质量问题

降低犯错的机会,写出好程序代码
服务器君一共花费了151.775 ms进行了4次数据库查询,努力地为您提供了这个页面。
试试阅读模式?希望听取您的建议

历代编程语言演化,都尝试着去解决不同的附属性困难问题。从Java反省C++的诸般问题而进行改良的结果,我们可以看出它所尝试要解决的问题的困难。

为什么Java要采用垃圾收集机制?

Java中有一个很重大的特色,就是垃圾收集机制(Garbage Collection,GC)。垃圾收集机制允许程序设计者尽管放心的去配置所需的内存空间,而毋需自行处理这些内存空间的释放问题,因为垃圾收集器(Garbage Collector)会自动判断已经不被程序所使用的内存空间,并且自动释放。

惯用C/C++程序语言的程序设计者都知道,自己配置的内存必须自己妥善释放,无论是没有释放或是重复释放,都会造成程序的Bug。程序设计者自己配置内存、自己释放是一种程序设计模型,而只管配置、不管释放,又是另一种程序设计模型。

Java继承了C/C++的血统,却选择了另外一种截然不同处理内存配置与释放的程序设计模型,为什么呢?在Java问世之前,垃圾收集机制已经存在,但鲜少看到十分主流的程序语言采用,原因就是在于它的效能问题。因为自动判断每一块内存区块是否该被回收及加以回收,都需要不少计算量,因而影响到程序的效能。而Java发明者决心以效能换得这个垃圾收集机制,是一个重大的决定,为什么呢?这自然是因为让程序设计者自己配置内存、自己释放的程序设计模型,存在着影响深远的问题,容易滋生出Bug。

从指针与数组来看C/C++

除了内存配置与释放的问题之外,还有许多问题长期以来困扰着C/C++的程序设计者,例如指针的问题。

指针是C/C++中十分具有威力的语法元素,运用指针可以达到许多神奇的效果,也能写出相当优雅的程序代码。指针的运用,可以说是C/C++语言中的一门艺术。但是不幸的,水能载舟亦能覆舟,指针相较于内存配置与释放的问题,更是容易成为Bug的温床。相信有足够C/C++经验的程序设计者,都能理解指标的误用、错用,十分容易引发程序的Bug,而且相当不易察觉。指针允许你指向内存的任意位置,但是一旦指向不属于应用程序可存取的内存地址范围、或是以不正确的方式解读所指向内存中的数据,就会引发严重的错误。

而数组的越位存取也是和指针类似的问题,这一类的问题,甚至可以被刻意操作,衍生成为缓冲区溢位攻击。这类的问题,只要有过足够C/C++程序设计经验的程序设计者,应当都能体会这些在程序设计过程中所可能造成的困扰。它们不仅容易造成Bug,而且所长出来的Bug有很高的比例,都会藏在程序中最隐晦的角落,让你难以发觉。

效果强与易犯错之间如何取舍?

C/C++的程序设计者在内存处理的动作上,得到极为高度的自由,但又吃尽苦头。C/C++在内存处理的程序设计模型很有威力,却也容易让程序设计者犯错,而且一旦犯了错,程序设计者必须花费数倍、甚至数十倍撰写出该错误的心力和时间,才能揪出这个错误并加以修正。这当然是一个值得思考的问题,我们是否要在一个语言中,放入一个极有威力、却容易造成程序设计者犯错的程序模型?

显然Java的发明者选择不这么做,他宁可放弃有威力的武器,也不希望这个武器在伤敌的同时,也同样伤到自己。

而这也反映Java语言在众多面向上的设计哲学,宁可削除一些看似弹性、看似有威力的语法元素,也要换取降低程序设计者因而犯错的机会。所以,Java才会得到一个 “C++ --“ 的评语。身为一个后继、流着相同血源的程序语言,却试图削减,可以想见其中一定充满着许多的反省。

所以,我们可以看见Java中取消了指针,改用参照(reference)来指向对象,没有指针指向任意内存地址的能力。一旦程序设计者做了越界的数组操作,Java的虚拟机器就会掷出异常,并且不允许执行该操作。同样的,程序设计者也无法对指向null的空参照值进行存取,一样会受到虚拟机器的控管,掷出对应的异常对象,待应用程序处理。

从生产力来看,质量才是关键

上述这几个例子,都是传统C/C++程序设计容易犯错的地方,Java从语言的根本上尝试杜绝这几类问题的发生。这些在语言上的设计,也都反映Java的基本哲学──减少程序设计者犯错的机会。

为什么Java这么重视这件事?因为如此才能大大提升软件开发时的生产力。事实上的确如此,正如前段所提的,一旦写下了一个错误,就必须花费数倍、甚至数十倍撰写出该错误的心力和时间,才能揪出这个错误并加以修正。你可以想象,这样开发对生产力会有多大的影响。

花多少时间写下程序代码或许是生产力的关键之一,但是,总共花多少时间得到足够质量的程序代码,才是最后所展现出来的真正生产力指针。我们可以说,Java是把生产力当做最高指导原则之一的程序语言,而它想要得到生产力的重要途径之一,就是在语言层次上尽量避免程序设计者犯错。

C和C++这两个语言中,存在不少容易让程序设计者犯错的地方,这是Java之所以要C加加之后再减减的原因。

举例来说:

int a = 3; 
int b = 1; 
if( a = b ) // if( a == b ) 
{ 
	// 执行一些动作 
} 

这样的写法无论在C或C++里都是合法的,但是,你有没有注意到,在if的条件式中,程序设计者有可能少打了一个 = 号,他原先想写下的条件式可能是 a == b,但因为疏失写成了 a = b,而这仍然是可通过编译的C/C++程序代码,它的语义从原先的若a值等于b值则条件成立,变成了将b值指派给变数a,若a在指派后的值大于0,则条件成立。

相信写过C/C++的程序设计者都可能犯下这样子的错误,而且也都能理解这样子的错单纯只是因为输入程序代码的错误,却有可能花上你许多的时间才能找到。从语法上来看,这是从C时代就具备的语法,它很强大、很有弹性,可以写出很取巧的程序代码,但是也留下了让程序设计者犯下上述错误的空间。当然,这样子的语法到了Java,已经不允许了。

你可以在C++中,找到更多更有威力、更有弹性的语法,程序设计者得以凭借来达成很厉害的效果,但是,使用它们要更小心、更谨慎、甚至需要对语言更了解,也更需要技巧,才能将它们运用地淋漓尽致,而且,一不小心,就会犯下难以修正的错误。而其中并不是难在纠正本身,而是难在找到为什么而错。

像C++中的运算子重载,基本上在Java程序语言中是没有的,而像C++中的多重继承,在Java中也仅提供多重接口的实作。从这种角度来看,你可以说Java比较弱,但这是透过刻意削减容易出错的语言元素,来减少程序设计者犯错的机会,即使语言威力变弱了、看起来不那么巧妙了、或许要用比较多的程序代码才能做到一样的事,而程序代码或许看起来很笨,但因为不容易犯错,反而可以提高生产力。

这就是Java尝试提高生产力的一个重要方向。

本文地址:http://www.nowamagic.net/librarys/veda/detail/172,欢迎访问原出处。

不打个分吗?

转载随意,但请带上本文地址:

http://www.nowamagic.net/librarys/veda/detail/172

如果你认为这篇文章值得更多人阅读,欢迎使用下面的分享功能。
小提示:您可以按快捷键 Ctrl + D,或点此 加入收藏

大家都在看

阅读一百本计算机著作吧,少年

很多人觉得自己技术进步很慢,学习效率低,我觉得一个重要原因是看的书少了。多少是多呢?起码得看3、4、5、6米吧。给个具体的数量,那就100本书吧。很多人知识结构不好而且不系统,因为在特定领域有一个足够量的知识量+足够良好的知识结构,系统化以后就足以应对大量未曾遇到过的问题。

奉劝自学者:构建特定领域的知识结构体系的路径中再也没有比学习该专业的专业课程更好的了。如果我的知识结构体系足以囊括面试官的大部分甚至吞并他的知识结构体系的话,读到他言语中的一个词我们就已经知道他要表达什么,我们可以让他坐“上位”毕竟他是面试官,但是在知识结构体系以及心理上我们就居高临下。

所以,阅读一百本计算机著作吧,少年!

《设计模式:可复用面向对象软件的基础》 Erich Gamma (作者), Richard Helm (作者), Ralph Johnson (作者), John Vlissides (作者), 李英军 (译者), 等 (译者)

《设计模式:可复用面向对象软件的基础》是引导读者走出软件设计迷宫的指路明灯,凝聚了软件开发界几十年设计经验的结晶。四位顶尖的面向对象领域专家精心选取了最具价值的设计实践,加以分类整理和命名,并用简洁而易于重用的形式表达出来。本书已经成为面向对象技术人员的圣经和词典,书中定义的23个模式逐渐成为开发界技术交流所必备的基础知识和语汇。

更多计算机宝库...