《代码大全》第二版--第二部分

第二部分:建立高质量代码

    第五章:软件构建中的设计

     5.1 设计

        在编码前进行,好比画图,画xml,想好逻辑怎么作,新增哪些数据结构,命名;算法

        设计可能会考虑不周,而且设计过程是很是艰难的,会犯一些错误 ,可是在设计阶段犯错的代价远低于编码阶段;数据库

        设计是易变的;编程

    5.2 设计的重要目标:管理复杂度

        复杂度是设计的重要指标之一,现代软件一般比较庞大,一我的的脑力远远不能装下这么多逻辑和设计。好的设计会将整个系统按照逻辑关系划分为子系统,在将子系统分解,最终划分为一个个小的在咱们大脑记忆范围内的功能点,咱们可以较容易的理解这些功能点,再将功能点联系起来,由小到大的理解整个系统。下降系统复杂度,每一个最小集复杂度达到最小。设计模式

        目标:最小复杂度,易于维护扩展、耦合性, 重用,移植,层次性。数据结构

    5.3 分层设计

        横向和纵向上的设计,纵向上分层设计,从系统->子系统->模块->类->子程序。纵向上的设计有自上而下和自下而上,自上而下的好处是相对比较容易,从大局逐步剖析到细节,很差的地方是局部变化因素对上层影响较大,由于是固定好上层,若是下层变化须要影响到上层,会发生连锁反应,自上而下设计,一般自下而上的开发,先搭建基础组件。  自下而上的设计好处是先把影响最大而且最基础的部分设计好,在设计高层,高层能够随意更改而不至于影响下层,这样设计的难点在于要时刻和最终目标对齐,和最终总体目标对齐,不然容易走偏或者设计了一些多余的用不上的底层接口。ide

        横向模块之间的交互必定要注意接口之间的通讯限制,若是没有限制,逻辑上其实就是一个接口,并无分开,模块间要作到功能解耦,只经过小部分暴露的接口作交互,并且某一模块不能和太多模块打交道,好比数据库访问模块就只和数据库打交道,而且向外暴露接口,在数据库变化的时候只须要适配接口就好了。函数

    5.4 设计来源(启发式)

        将现实生活中的问题抽象成设计中的元素工具

        将生活中的类似实体抽象出共同的抽象特征性能

        采用信息隐藏、测试

        区分系统易变和不易变部分。找出一个最小的不易变部分,逐步扩大这个部分并检测这个部分的易变性。直到易变性突破了某个可接受范围,那么外边的更大范围就是易变的。

        适当采用现有的设计方法:常见的设计模式

    

        补充:几个代码设计的原则:

            单一职责原则SRP: 就一个类而言,应该仅有一个引发它变化的缘由

            开放封闭原则ASD:类,模块,函数等等应该是能够扩展的,可是不可修改的

            里式替换原则LSP:全部引用基类的地方必须透明的使用其子类的对象

            依赖倒置原则DIP:高层模块不该该依赖低层模块,两个都应该依赖于抽象。抽象不该该依赖于细节,细节应该依赖于抽象    

            迪米特原理LOD:一个软件实体应当尽量少地与其余实体发生相互做用

            接口隔离原则ISP:一个类对于另外一个类的依赖应该创建在最小的接口上。

    

        总结:设计是个很重要的过程,而且须要大量的工做经验,不只须要有全局观,还有关注具体模块功能的实现可行性,须要拆分目标系统,下降复杂度,须要关注的点不少,评估冲突妥协。这块后面有相关经验了再回来补充笔记。(普通开发人员在编码以前也要作se给出的设计进行思考,看有没有更优秀的方案,实现过程的逻辑伪代码也最好写一写,画一画逻辑流程图)

 

    第六章:能够工做的类

        6.1 类的基础:抽象数据类型ADTs

            抽象数据类型是指一些数据以及对这些数据所进行的操做的集合。

            类 = ADTs + 继承 + 多态

        6.2 良好的接口类型

            类的接口应该展示一致的抽象层次:一个类仅实现一个ADT(单一职责);建立类时明确要实现的抽象是什么,类中的服务也就是方法一般是成对的。

            类中的数据应该作好封装,只须要暴露部分以表达这个类的属性特征的接口(信息隐藏), 让调用方针对接口编程而不是去看接口中的具体实现来编程。

             《抽象单一,职责单一,服务单一》

        6.3 有关设计和实现的问题

            1. 包含:has a的关系, 类中的成员数据,能够声明也能够继承得来,最好不要超过7个成员变量

            2. 继承: is a 的关系;继承能够重用父类的数据和成员,继承体系不能过多,过多意味着复杂度太高,最好不超过6层。注意继承的权限:private,protected,public; 避免菱形继承。

                继承规则: 若是须要共享数据,则将这部分数据提取出来造成一个数据结构; 若是须要共享行为,将这些行为提取出来造成一个基类; 若是都要共享,提取基类并将数据结构填充到基类中去。

                基类控制接口:继承 

                本身控制接口:包含(包含字段,包含方法,包含对象)

            3.构造函数: 最好在构造函数里进行成员初始化动做,构造函数要注意深浅拷贝

        6.4 建立类的缘由

            1. 为现实世界中的对象建模

            2. 为抽象的对象建模(好比物体的形状能够创建一个基类)

            3. 为了下降复杂度,创建多个类,类之间有相互交互; 类中能够隐藏信息,好比一个具体的复杂算法或者一个协议,隐藏在类中,对类外暴露接口便可。

            4. 减小参数的传递:对于多参数的函数,若是将这些参数封装成一个类,直接能够传递一个类的对象。

            5. 代码可重用,将重用的部分代码封装成一个类,其余个性化的类能够继承这个父类来获取那些公用的方法; 

            6. 首先作好系统分析工做,将动态的和非动态的数据分别放到不一样的类中,动态的数据组成的类能够随时变化。

            应该避免的类:万能类(上帝类), 可有可无的类(将这些类降级或者合并到其余类中), 类的命名不要使用动词(若是类中只包含动做而没有具体数据对象,这个类一般为一个工具类,名字必定要使用名词)

        

        补充总结:对类的思考,首先类的做用要单一,和函数同样,职责要单一,类中应该保存的是某一个ADT,对外暴露的接口也应该和这个ADT的抽象等级相同,对接口的暴露也要三思,最后类的名字要想好,用一个名字来表达。

 

    第七章:高质量的子程序

        子程序:routine, 方法:method, 过程:procedure, 宏:macro

        子程序是为实现一个特定目标而编写的一个可被调用的方法或者过程。函数有返回值,过程无返回值。

        7.1 建立子过程的正当理由: 下降系统的复杂度,避免代码重复, 封装一个复杂的算法或者不易懂的协议之类的过程, 简化复杂的布尔判断, 改善性能,确保每一个子程序都比较小

        7.2 在子程序上设计: 着重内聚性,子程序内部操做紧密,子程序对外变现的功能单一;

        7.3 好的子程序名字:描述子程序所作的功能, 不要使用数字如part1, part2 ; 使用动宾短语。

        7.4 子程序能够写多长: 50-100 行, 最好在50行之内、函数越短小,功能越单一,可重用性越高

        7.5 如何使用子程序的参数:

                入参在前,出参在后; 使用宏定义来代表结构体中的出入参;参数最好不要超过5个,参数过多的时候能够考虑封装成数据结构或者对象的方式传入,参数命名也很重要; 形参名和实参名尽可能保持一致。

 
#define  IN
#define  OUT    
struct {
     IN  int putIn;
     OUT  int putOut
}  
 

        7.6 使用函数时要特别注意的事情:何时用函数,何时用过程,函数有返回值,过程无返回值,有的时候表示过程的一个函数能够返回一个值用来表示这个过程是否执行成功。

        7.7 宏子函数与内联子函数:

            宏子函数中的参数和计算过程必定要用括号包起来,由于宏展开的时候各类符号的优先级可能会出现非定义状况,

            宏中多条语句的时候要用大括号包起来,防止在if后面紧跟宏时,宏展开只会有一条语句在for, if循环做用范围内;

            取代宏的手段:const , inline , template, enum, typedef

 
#define club(a)   ((a)*(a)*(a))   // a为x+1  -->   ((x+1)*(x+1)*(x+1))    
 

        总结:子程序,目的在于下降复杂度,提升可读性,可靠性,可修复性,可重用性,封装隐藏信息,对于子程序,要注意内部的内聚性,一个子程序只作一件事情,注意子程序行数尽可能保持在50行内,最后,要给子程序取个好名字,达到从名字就能看出这个子程序完成的功能是啥。

 

    第八章:防护式编程

        子程序不因传入的错误数据而被破坏

    8.1 保护程序免遭非法输入数据的破坏:检查全部来源于外部的数据, 决定如何处理错误的输入数据。

    8.2 断言:开发和维护阶段使用,生产代码不要编译进去。断言检查的是不应发生的状况,错误码用来检查不太可能发生的异常场景。

    8.3 错误处理技术:返回中立值,换用下一个正确的数据, 返回与前一次相同的数据, 记录日志文件,返回一个错误码; 应该具体场景具体分析,对于正确性要求较高的场景应确保正确性,对于健壮性要求较高的时候应着重健壮性。

    8.4 异常:把代码中的错误或者异常事件传递给调用方代码的一种特殊手段。提出异常,交出控制权,或者本身处理异常。高健壮性要求对异常处理要全面。避免在构造函数和析构函数中抛出异常,好比在构造函数中抛出异常,异常以前分配的资源就得不到释放,这个时候就有内存泄漏问题; 了解库函数的异常状况。

    8.5 隔离程序,使之包容由错误形成的损害: 调用子程序前,先作preCheck动做

    8.6 辅助调试代码:目的是快速的检测错误

        进攻式编程:在开发阶段让异常显示出来,而在产品代码运行时让它可以自我恢复。

        对于辅助的调试代码,在正式产品中能够经过工具或者编译选项将他们剔除于生产代码中。

    8.7 肯定在产品代码中保留多少防护式代码:若是每一个子程序在正式代码以前都先运行防护代码,那么系统将会变得臃肿,首先第一点,去掉一些细微错误的检查代码,去掉硬件因素引发的异常代码,保留让程序稳妥的崩溃的代码; 第二,制定个产品规则,对于大多数的子程序,能够经过调用方来保证数据的正确性,有的子程序内部调用了几个子程序,这几个子程序的防护代码都是一致的,在这些子程序外部进行一次数据校验后就能够给多个子程序使用,若是每一个子程序都去作一遍有点多余,我的以为大多数子程序的数据正确性由调用方负责比较灵活。

    8.8 对防护式编程的态度:过多的防护式代码会让系统变得臃肿和缓慢,增长系统复杂度,可适当根据防护等级决定代码的编写。

    总结: 能够根据具体的项目要求来,有的项目要求调用方负责数据的正确性,有的要求被调用者负责数据的正确性,仍是应该按需来。    

 

    第九章:伪代码编程过程

        9.1 关于伪代码:与具体语言无关,能够当成注释来写,尽可能保持与逻辑一致

        9.2 经过伪代码建立子程序:检查先决条件;定义子程序要解决的问题:输入,输出,功能; 为子程序命名; 决定如何测试子程序; 搜索重用的代码; 代码效率性能考虑; 算法与数据结构的安排;编写伪代码;评审伪代码;编写代码;

        总结: 伪代码能够当作是具体代码的稍上层表现,包含了大概的实现过程,又经过简易的预览来描述实现过程,在写代码以前进行伪代码编写能够提早预估到困难点以及应对方法。

相关文章
相关标签/搜索