Featured image of post 读《代码大全(第二版)》笔记(二)

读《代码大全(第二版)》笔记(二)

本周读了第二部分-创建高质量的代码

说实话,真的好后悔没有早点读这本书,觉得自己以前写的代码就是一坨Shǐ。看书的过程中,一边看就觉得后脊梁发凉,一阵阵凉风从后背吹过……

本部分分为五章,基本上还是从顶层设计角度探讨了如何更好的编写代码。分别从软件的设计、类的封装、子程序的设计以及防御式编程和使用伪代码进行开发方法方面进行了探讨。感觉书中需要学习点太多了,好多方面看完之后都有感触。想了想,如果都列出来感觉可以写一本小册子了,所以在这里还是随意挑几个标题大(Y( ^ _ ^ )Y)的记录一下吧,太细的点就不列了。不过虽然有的点不是大标题,但并不代表这些点不重要,有时间的话,我想我还会经常回过头来当手册来翻一翻的。

第五章-软件创建中的设计

本章的一个核心思想是如何更合理的控制复杂度。

(Page.77)软件的首要技术使命:管理复杂度

书中首先阐述了管理复杂度的重要性,并指出产生高代价、低效率的设计源于下面三种根源:

  • 用复杂的方法解决简单的问题;
  • 用简单但错误的方法解决复杂的问题;
  • 用不恰当的复杂方法解决复杂的问题。

关于这一点,书中描述了两种方法来解决这个问题:

  • 把任何人在同一时间需要处理的本质(essential)复杂度的量减到最少;
  • 不要让偶然性(accidental)的复杂度无谓地快速增长。

关于如何控制复杂度这个问题,看了下整本书的目录,不管是从顶层对软件的设计,还是深入到底层的代码编写,貌似都是为了更好的控制复杂度,不过我整本书还没看完,只是感觉而已。

回过头来想了想,在我曾经做过的项目中。虽然有的项目外表上看起来算是完成了,不过我感觉,在做项目的过程中,多少都冒犯了上面指出的三种根源。只不过上面总结的很抽象,具体实施起来会涉及到很多方面。呵呵,这当然是一个复杂的问题。

随后,书中说了理想的设计特征包括哪些范畴:

  • 最小的复杂度(Minimal complexity)
  • 易于维护(Ease of maintenance)
  • 松散耦合(loose coupling)
  • 可扩展性(extensibility)
  • 高扇入(high fan-in)
  • 低扇出(low fan-out)
  • 可移植性(portability)
  • 精简性(leanness)
  • 层次性(stratification)
  • 标准技术(Standard techniques)

其中,关于最小的复杂度部分,我觉得书中有一句话说的很形象。

(Page.80)如果你的设计方案不能让你在专注于程序的一部分时安心地忽视其他部分地话,这一设计就没有什么作用了。

精简性方面,书中指出

(Page.81)精简性意味着设计出的系统没有多余的部分(Wirth 1995,McConnell 1997)。

除此之外,对于高扇入和低扇出,其实之前貌似重视不够,尤其是低扇出,以后要注意这一点了,至于其他方面,平时看的也比较多,应该还算是不陌生吧。

书中后面讲到隐藏秘密(信息隐藏)。这其中主要分为了两大类:

  • 隐藏复杂度
  • 隐藏变化源

老实说,看过这部分之后,回想了下之前写过的代码,我觉得做的很不够,和书中所描述的点相差甚远,真是觉得汗颜。也感觉到设计好的接口是如此之难,能够设计好的接口真不是简单吹吹牛就能做好的,需要多方面的综合考虑才有可能更合理的对接口进行设计。

对于设计模式的用处,书中列出了设计模式的几个益处,我想根据这几个点,多少也可以指导我们平时如何更合理的使用设计模式吧。

  • 设计模式通过提供现成的抽象来减少复杂度
  • 设计模式通过把常见解决方案的细节予以制度化来减少出错
  • 设计模式通过提供多种设计方案而带来启发性的价值
  • 设计模式通过把设计对话提升到一个更高的层次上来简化交流

对于这几个益处,针对设计模式,我觉得,设计模式仅仅是一种框架,可以用来降低复杂度,减少错误,可以依赖这些模式做一些变化来适应需要解决的问题,最后是利于码农之间更好的交流。所以从这几个方面看,学习设计模式一定不要死记硬背,更好的掌握使用这些模式的场景更重要,所谓手中无剑,心中也无剑,此为高手。

关于这章的其他部分提了一些具体实践的方法,说了一些方法论,和具体实践结合很紧密。关于保持松散耦合这方面,书中也提了哪些方面可能会产生耦合,以及耦合常见的形式。

本章后面讨论了一下自上而下和自下而上的设计方法。作者的结论是,两种方式各有利弊,并非排斥关系。自上而下从一般性的问题出发,把该问题分解成可控的部分。而自下而上从可控的部分出发,去构造一个适用的方案。这两种方法都有各自的强项和弱项。设计过程中,你会受益于二者的相互协作。

第六章-可以工作的类

本章讨论了设计类的接口。书中给出了几个 C++的代码例子,以说明良好的接口应该如何设计。书中有几个点很有启发性,其中有一句是

(Page.138)关注类的接口所表现出来的抽象,比关注类的内聚性更有助于深入地理解类的设计

对于类进行良好的封装,作者给出了几条建议:

(Page.139)尽可能地限制类和成员的可访问性

关于这条建议,书中引用了 Meyers 的一句话,

(Page.139)应该采用最严格且可行的访问级别(Meyers 1998,Block 2001)。

意思很明显,就是说,如果你不确定,那么多隐藏通常比少隐藏要好。

(Page.139)不要公开暴露成员数据

这一点很明显,暴露成员数据会破坏封装性。这一点对于 C++ 来说更应该注意,因为对于 C#Java 来说都有对应的访问器来隐藏成员数据,而 Objective C,则会对每个向外暴露的成员同样也会生成对应 SetGet 方法。呃,对于语言的讨论就此打住吧……

(Page.139)避免把私用的实现细节放入类的接口中

对于这一点,原来一起工作的同事有讨论过相关的问题。对于这一点《Effective C++》书中也有详细讨论,具体的可以翻一翻这本书。另外,这一点感觉也是C++的一个坑,其他语言应该可以比较容易的规避这个问题。

(Page.141)不要对类的使用者做出任何假设

关于这一点,我想没什么好解释的,很显然的事情。

(Page.141)避免使用友元类(friend class)

书中说到,

(Page.141)一般情况下友元类会破坏封装,因为它让你在同一时刻需要考虑更多的代码量,从而增加了复杂度。

(Page.141)不要因为一个子程序里仅使用公用子程序,就把它归入公开接口

这一点考虑的问题是,如果把某个子程序暴露给外界后,接口所展示的抽象是否还能保持一致。

(Page.141)让阅读代码比编写代码更方便

关于这一点,书中强调还是代码的可读性。时刻要防止接口设计的一致性问题,一旦出现接口定义不规范的问题后,不但开始影响代码的可读性,而且还有可能会出现《程序员修炼之道》中所说的破窗户问题。

(Page.141)要格外警惕从语意破坏封装性

对于语意上对于封装性的破坏,书中列出了五种情况,这里有不一一列出了。其实中心思想就是不要让类的内部实现影响外部调用,其中包括外部对类的调用顺序的限制。如果你发现封装的类,从外部调用的时候需要考虑内部实现,这时候其实就是破坏了类的封装性。

(Page.142)留意过于紧密的耦合关系

对于耦合的关注,我觉得再怎么强调也不为过。

对于继承的使用,看完书之后,我的感受是,一定要小心谨慎!书中给出了什么情况下应该使用继承,什么情况下应该使用包含(Page.149):

  • 如果多个类共享数据而非行为,应该创建这些类可以包含的共用对象。
  • 如果多个类共享行为而非数据,应该让它们从共同的基类继承而来,并在基类里定义共用的子程序。
  • 如果多个类既共享数据也共享行为,应该让它们从一个共同的基类继承而来,并在基类里定义共用的数据和子程序。
  • 当你想由基类控制接口时,使用继承;当你想自己控制接口时,使用包含。

对于上面的规则,我的总结了一下,只要涉及到控制接口的就选择继承,只涉及数据的就用包含。

本章后面说了下创建类的原因,基本上都是一些原则性的指导。并在随后给出了三类应该避免的类(Page.155):

  • 避免创建万能类(god class)
  • 消除无关紧要的类
  • 避免用动词命名的类

第七章-高质量的子程序

这一章整体在讲如何设计好的子程序。比如本章的第一节讨论了创建子程序的正当理由,说了几点为什么要创建子程序,目的是什么,创建子程序解决了什么问题。随后书中描述了如何设计合理的子程序。不过,我觉得对于书中讨论的关于子程序可以写多长觉得蛮好玩的,作者的观点是,如果编写了一段超过 200 行代码的子程序,那你就应该小心了。说到这里,我想起在我刚开始写程序的时候,隐约记得写过一个远比 200 行长的多的函数,具体几行忘记了,只记得很长很长,而且方法的参数有输入也有输出,最蛋疼的是没有给出哪几个参数是输出,哪几个参数是输入。现在想想,基本上这个方法只有我会调用,( ̄ ▽  ̄) 。

对于子程序的参数,书中也做了讨论,并明确指出把子程序的参数个数限制在大约 7 个以内,并要求采用某种表示输入、修改、输出的命名规则。

书中对于函数和过程给出简单的区分。

(Page.181)函数是指有返回值的子程序;过程是指没有返回值的子程序

不过在 C++ 中,通常把所有子程序都称为“函数”;返回值类型为 void 的函数在语义上其实就是过程。关于宏的使用,很明确,尽量少用,尤其是宏子程序。至于内联子程序,作者的建议是节制使用,因为内联子程序违反了封装原则。

第八章-防御式编程

对于防御式编程来说,重点还是讨论了断言。

(Page.189)断言为真,则表明程序运行正常,而断言为假,则意味着它已经在代码中发现了意料之外的错误。

至于断言和错误处理的使用的指导建议是,

(Page.191)用错误处理代码来处理预期会发生的状况,用断言来处理绝不应该发生的状况

对于错误处理技术的使用,根据不同的使用场景给出了不同的建议。例如医疗设备控制软件,如果发生了医疗故障,为了保证患者的安全,应该立刻关闭程序等。防御式编程全部的重点在于防御那些未曾预料到的错误。

本章随后讨论了异常。对于异常的使用给出了几条建议。例如,

(Page.199)不能用异常来推卸责任

其实就是说,哪里出现了问题就在哪里解决,不要让规避问题的存在,或者说是遮掩问题。

不要在构造函数和析构函数中使用异常,除非你在同一地方把它们捕获

在构造函数中处理异常会让问题变的很麻烦,其中最重要的问题就是资源的泄漏问题。

书中提到了隔栏(barricade)这个词。并定义说

(Page.203)是一种容损策略(damage-cotainment strategy)。

我觉得目的就是为了把错误引起的损失控制在一定范围内。

书中其它部分谈到了几点关于产品发布和开发过程中,调试代码如何进行权衡。

第九章-伪代码编程过程

其实,我觉得本章没什么特别要说的,因为我觉得伪代码编程属于开发方法学,而且这种方法是作者比较认可的方法,但是不同的项目可能需要不同的方法作开发,不一定所有项目都要用伪代码编程。例如,重构。

不过,通过了解伪代码编程,然后再跳出整个软件开发的框框,我觉得写代码真成为了软件开发中一个不应该占用很多时间的一个环节。通过编写伪代码,可以把你的思路完全展现出来,作者的观点也很明确,当你什么时候发现伪代码已经无法更改的时候,再开始写真正的代码。由此可见,写真正的代码,在这个开发模式中,占用的比例就非常的小。当然,这么做可以很明显的感觉到,真正写实现代码的时候会比较容易,其实思路和框架都有了,只剩下了代码实现。至此,又让我想到关于复杂度的控制。通过这个环节,可以把代码出错的几率压缩到最低,从这一点来看,其实编写伪代码是一个非常重要的环节,它能有效的控制代码的复杂度。

至此,第二部分基本上就大概总结了下,其实很多点需要不断的在实践中去尝试,总结写下来只是学的第一步,真正能够融会贯通才算是学到手了,你说是吧