阅读《代码大全》(第二版)体会小结
这一次阅读了著名的《代码大全》(第二版)。全书虽然章节众多,但是主要就是几个大部分,这些部分我大多有一些浅显的了解但还未深入,当然也有一些全新的体验,例如表驱动法。全书内容丰富而详细,我在阅读的其中问题并不是太多,只不过很多的内容都觉得了解的还太浅,需要更多的实践以及阅读去体会。在这里记录下的也就是一些自己的体会,主要是对书中一些论断的不同看法与讨论,大部分是关于面向对象和结构化设计的内容;以及对于全新接触的表驱动法的学习体会。
Question 1:
“7.1 创建子程序的正当理由”中,提到创建子程序有助于降低复杂度、改善性能,并能够避免代码重复以及提高可读性。对这一论断我一直以来都有疑问。以创建函数为例。首先,我认为创建函数,也就是类似模块化设计,对于性能和复杂度是有增加的。调用函数时,存放传入参数需要进行栈操作,同时也需要保存程序上下文。由于我接触过mips汇编编程,根据我的体验,在mips汇编中进行函数切换时需要进行多个寄存器的值保存,并且进行栈操作,主要表现在存值入栈、保存函数返回地址、从栈中或者其他地方一次取出或者经计算后跳转到相应地址。在mips汇编中这些保存的工作一来一回可以占据好几十行,如果是大型程序,其中大量运用了模块化设计和代码重用原则,那么这些保存工作的开销可想而知,对于复杂度和性能都是增加。其次,保存上下文次数太多的话容易出现异常甚至错误,加重程序运行的负担。再者,我认为代码重用和可读性的提高的重要性有待商榷。所谓代码重用与可读性,无非是 为了便于阅读与维护,而为此增加的一系列函数的书写,虽然看起来是整洁明了了,但是运行效率未必更高,换句话说,这些并不是必要的。试想如果是在Ken Thompson的那个时代,或者说是几十年前计算机内存还是以MB计算的时代,如果进行如此大规模的函数调用,代码重用,对性能的冲击势必更加不容忽视。代码大全中甚至还说创建子程序能够降低复杂度,理由是可以通过创建子程序来隐藏信息,这样就不必再去考虑它们了。这无疑是自欺欺人。编写子程序隐藏信息细节充其量能说是提高抽象层次,只暴露接口,体现封装性与抽象的思想。但是既然代码都还在那里,那么无论是否编写子程序隐藏信息,时间复杂度都是那么多,编写子程序并不能够降低时间复杂度,空间复杂度更不用说了。如果这里说到的是指程序的复杂度的话,那倒是无可非议,不过对于提高性能并无意义。因此,在我接触面向对象建模技术以及软件工程开发的基本思想之后,我认为这一系列规范为的是让人们更加系统而有条理地规范地编写软件,而不是为了提高软件的性能。
Answer 1:
上网查看网友们对于子程序代码重用的讨论以及函数调用开销的讨论,大部分都说在现代大容量高速度计算机的条件下,函数调用的开销可以忽略不计,而与之带来的良好的程序可读性以及易维护性看起来更加受人重视。诚然,利用面向对象的抽象机制以及封装机制,能够使程序开发层次更加分明,便于先设计之后开发,封装保证了程序的安全性,避免他人随意操纵类的成员变量。代码重用虽然看起来我觉得没什么用,但是的确便于修改,如果多处利用重复代码的话,一旦需要修改,那么代码重用将提供便利,这就跟宏的设计思想是类似的。而子程序设计,只对用户暴露出接口,而不需用户关心内部实现,符合当代软件开发以及使用的一般准则。尽管看到这些讨论,我依然认为代码重用需要适度,盲目的把一个函数的功能拆分成很多的小函数在我看来没有太大意义。可能只有在软件项目的开发中我才会考虑使用代码重用以利于结对编程以及团队编程以及程序测试。
Question 2:
关于全局变量的使用,在代码大全的“全局数据”中列出了全局变量的优点和缺点,但是书中仍然提到不到万不得已不使用全局变量,以及类的设计思想本身就保证了类的私有变量能够被类的所有成员方法所共用的情况。说的很有道理,但是并不是所有语言都是面向对象的。面向对象式语言,例如java和c#,确实可以在类中定义私有变量,然后类的方法都可以公用这些变量,同时实现了全局变量(类全局)的功能以及类的封装原则。但是在c语言中就难以实现这一点,毕竟在c语言中最接近面向对象的结构体也无法对变量加上访问控制符。
Answer 2:
其实关于全局变量,我吃过亏也得过宜。之前编写c语言程序时因为乱改全局变量而导致程序出现隐含的错误不易察觉耗费了我大量的调试时间,然而,在一次编写多层递归程序时,没有想到使用全局变量,而是使用函数的局部变量,导致函数一层一层退出嵌套是标志变量的值并不像我所想的那样,导致没能在规定时间内完成程序。应该说在java等面向对象语言中,全局变量显得不那么必要,因为类本身的封装性和综合性,但是在c语言中全局变量还是很重要的,有时候全局变量的使用能够极大地便利数据的使用以及标识的使用。如果不使用全局变量,统计递归函数的运行次数都不太容易,虽然使用函数的静态变量可以解决问题,由于递归函数是递归地调用自己,静态变量可以起到一定的作用,并且使用静态变量,由于作用域是在函数内,其他的函数过程无法影响该函数的静态变量的值,不失为一个好对策。另外,解决递归问题的话可以通过将递归转化为非递归来进行,例如动态规划算法的使用。同时在代码大全中也提出,可以用锁来控制对全局变量的访问,虽然耗费资源但是却可以很好地控制全局变量的使用,避免数据不一致性。
Question 3 & Answer 3:
关于封装的问题。这也是老生常谈了。其实我不认为这个问题能够真正一方完全说服另外一方,所以就把问题和回答合起来了。代码大全中强调类的基础就是ADT,而良好的封装被称为是抽象的必要条件。据该书所说,要么就是封装与抽象两者皆有,要么就是两者皆失,除此之外没有其他可能。该书也提倡避免c++中友元类的使用。从我接触面向对象技术开始,我一直对封装性没有好感。封装性虽然一定程度上能够保证程序的安全性,但是却大大限制了程序编写的自由性。那些所谓业内顶尖大师们对于封装性的“权威论断”,无疑给程序的编写,尤其是面向对象语言的编写铐上了枷锁,不过实际上ADT早已给oo程序加上了禁锢。所谓限制类成员的可访问性,将它们声明为私有级别。既然是面向对象的程序,那么对象与对象之间一定要有交流,虽然松散耦合并不是坏事。如果说所有的类成员都声明为私有,那么其他的类如何访问这些类变量?如果说是利用类的成员方法,那实际上还是破坏了封装性吧?毕竟子程序还没有声明为私有。直到数据和子程序全部都被声明为私有,外界无法窥探到类的任何内部实现细节,然后其他类也无法与该类进行交流了吧?这在我看来是非常荒谬的。为了内部细节不被窥探,而大大降低模块之间的耦合度,给编写程序增添了很多麻烦。编写程序时,比如需要对某一个类的变量进行查看值或者更改值的操作,那么一句封装性的原则,是无法进行存取的。就算提供公有的子程序来存取类的私有变量,试想如果成员方法能够存取类的私有变量,那私有还有什么意义?不过随后我又在代码大全中看到,该书认为,比如有一个类有三个float型私有变量x、y、z,类将这三个变量封装起来,然后暴露出这三个变量分别的get以及set方法,这样并没有破坏封装性,因为外人并不知道你的内在细节到底是什么,不了解你的类的底层实现到底有哪些变量。这样倒是解决了其他类与该类的沟通交流问题。虽然实现了细节的隐蔽性,但看起来是很荒谬的。一堆私有变量,居然定义set和get方法,我只觉得这很悲哀,尽管看起来对外封装了数据细节。
Question 4 & Answer 4:
在类的设计与实现中,组合和继承都经常被提及,尤其是继承,甚至被奉为面向对象三大特性之一。但是在我看来,组合比继承要方便的多。不知是否是因为才疏学浅,我始终没太理解继承到底有什么意义,除了多态的使用。多态确实是一个看起来非常有用而且华丽的技术,这里先抛开多态不谈,来看看继承。以前在进行面向对象建模课程训练时,老师规定必须在程序中建立继承层次,接口、抽象父类或者非抽象父类都必须尝试。经过尝试后,我感觉到继承给程序编写带来的桎梏甚至比封装还要大。或者说,继承的用途并不见得那么广泛,尤其是在不那么大型的程序中。本身由于类的封装性以及松散耦合的原则,类与类之间的联系就不是太紧密,使用组合就能较好地利用各种类的资源。当然在大型的程序中,例如说一个公司的管理系统,有各种类型的员工,而这些员工类可以统一继承而来,这是很好的代码重用实例。这样看来也不能一味否定继承。在代码大全对于继承的讨论中,有几句话让我感触很深。第一句是“要么使用继承并进行详细说明,要么就不要用它”。勉勉强强用上继承,结果又没明确要继承什么,继承层次如何,就容易产生无效代码或者把自己弄糊涂。在写继承之前应该考虑是否有足够的子类,它们之间的共用关系值得写一个父类来实现代码重用,因为很多时候其实两个类之间根本不需要再建一个父类,是不必要的代码。同时书中所说的“避免让继承体系过深”也是很有道理的。继承本身就是比较高级的技术,容易出现意外情况,而多层继承的逻辑已经复杂到大学课堂上都拿多层继承的实例来进行分析以明确学生对继承的理解。且不说多层继承,多重继承更加可怕,以致java中都取消了这个c++的特性。当一项技术容易让人产生迷茫的时候,人们需要谨慎使用,或者这项技术应该引起人们的讨论和思考。
Question 5 & Answer 5:
对于表驱动法,我表示非常惊讶,因为长期的if else的使用让我觉得实在是有些厌烦,而看到了表驱动法的思想,利用查表来完成 逻辑部分的判断,这使得逻辑判断更加结构化了。当然,既然涉及到了表,那么数据的存储以及查询一定会成为最大的问题。如果查询速度快,那么存储结构一定较为复杂,例如为了使查找更加快捷更加便利而在存储数据的同时存储一些其他的信息便于查找,增加了空间的占用。在表驱动方法中,直接访问表就像哈希表一样可以进行直接存取,虽然隐藏了复杂的内部实现而表现给用户的是便利的查询使用,但是存储的问题依然值得考虑。索引访问表能够一定程度上降低空间的使用,建立起庞大的索引表来代替建立庞大的主查询表,便于查询也便于维护,与哈希表有些类似。令我惊讶的是,在代码大全中还提出了阶梯访问表,它进一步解决了索引访问表可能遇到的一些问题,不得不说这些大师想法很巧妙。阶梯访问表通过判断确定范围来确定数据是否有效,这种判断范围而不是单纯的点的想法看起来很一般,实际上我却没有想到。在评定成绩等级的时候,阶梯访问无疑比索引访问要有效,当然在阶梯访问中查找并不一定要按照顺序查找范围,由于范围是按照一定顺序排列的,利用二分查找可以降低时间复杂度。在书中还谈到,索引访问未必不能够实现阶梯访问的功能,只不过需要具有非连续的键值来进行索引,如果是连续值那还是应该使用阶梯访问。