第一部分 高质量子程序设计
本文为《代码大全2》的读书笔记,版权归代码大全所有。^_^
本文基址: http://blog.youkuaiyun.com/cugxueyu/archive/2007/12/10/1926703.aspx
· 创建子程序的正当理由
· 在子程序层上的设计
· 好的子程序名字
· 子程序可以写多长
· 如何使用子程序参数
· 使用函数时要特别考虑的问题
· 宏子程序和内联子程序
※ 子程序定义:为实现一个特定的目的而编写的一个可被调用的方法或过程。
※ 低质量的子程序:
> 差劲的子程序名字
> 没有文档
> 代码布局不好
> 子程序没有一个单一的目的
> 没有错误处理
> 使用神秘数据(没有意义的单纯数字)
> 参数过多
> 参数顺序混乱且未经注释
※ 子程序也是迄今为止用以节约空间和提高性能的最重要手段。
※ 创建一个子程序可以有很多合理的原因,但完成它的方法却有对错之分。
1、 创建子程序的正当理由
> 降低复杂度
隐藏代码细节、缩小代码规模、改善可维护性、提高正确性。
※如果没有子程序的抽象能力,我们的智力水平将无法管理复杂的程序
如:当内部循环或条件判断的嵌套层次很深时,就需要把嵌套的部分提取出来形成一个独立的子程序,可以降低外围子程序的复杂度。
> 引入中间、易懂的抽象
把一段代码放入一个命名恰当的子程序内,是说明这段代码用意最好的方法之一。
※ 提供了更高层次的抽象,从而使代码更具可读性,也更容易理解,同时也降低了调用代码的复杂度。
> 避免代码重复
※ 创建子程序最主要的原因:避免代码重复。
※ 如果两个子程序内编写了相同或相似的代码,就意味着代码分解失败了。
※ 节约存储空间、改动起来方便可靠、有利于验证代码正确。
> 支持子类化
※ 覆盖简短而规整的子程序所需新代码的数量,要比覆盖冗长而邋遢的子程序更少。
※ 如果能让可覆盖的子程序保持简单,那你在实现派生类的时候也会减小出错的几率。
> 隐藏顺序
※ 隐藏处理事件的顺序。
※ 如:pop_statck() à 先读取栈顶的数据,然后减少stackTop变量的值。
> 隐藏指针操作
※ 把指针操作隔离在子程序内部,可以把精力集中于业务本身,而不是指针操作机制的细节。
※ 益处:集中指针操作(提高正确性)。
> 提高可移植性
※ 可以使用子程序来隔离不可移植的部分。
※ 不可移植:语言提供的非标准特型、对硬件和操作系统的依赖。
> 简化复杂的布尔判断
※ 布尔判断的逻辑放入单独的函数中,也强调了它的重要性,这样也会激励程序员在函数内部做出更多的努力,提高代码的可读性。
> 改善性能
※ 采用子程序,可以只在一个地方优化代码。
※ 可以更方便的查找出效能低下的代码、可以用高效的算法和快速语言来容易得替换代码。
> 确保所有的子程都很小
※ 使代码的结构更容易掌握。
2、 在子程序层上的设计
※内聚性:指子程序中,各种操作之间联系的紧密程度。(抽象和封装基本已经取代内聚性)
※目标:让每一个子程序指把一件事情做好,不再做任何其他事情。
※理解一些概念要比记住一些特定的术语更重要(出发原则)
> 功能的内聚性
※ 是最强也是最好的一种内聚性,也就是说,让一个子程序仅仅执行一项操作。
※ 评估内聚性:前提是子程序所执行的操作与其名字相符。
> 顺序上的内聚性
※ 指在子程序内,包含有需要按特定顺序执行的操作,这些步骤需要共享数据,而且只有在全部执行完毕后才完成一项完整的功能。
> 通信上的内聚性
※ 指一个子程序的不同操作使用了同样的数据(通信媒介),但不存在任何其他的联系。
> 临时的内聚性
※ 指含有一些因为需要同时执行才放到一起的操作的子程序。
例如:start_up()子程序可能需要读取配置文件、初始化临时文件、设置内存管理器等。
这些术语没有哪个是神秘的或者神圣不可侵犯的,需要理解的是其中的想法,而不是那些术语(把注意力集中于功能上的内聚性上)。
3、 好的子程序名字
※ 好的子程序的名字能清晰地描述子程序所做的一切。
指导规则:
> 描述子程序所做的所有事情
> 避免使用无意义的,模糊或表述不清的动词
> 不要仅通过数字来形成不同的子程序名字
> 根据需要确定子程序名字的长度(视该名字是否清晰易懂而定)
> 给函数命名时要对返回值有所描述
> 给过程起名时使用语气强烈的动词加宾语的形式
> 准确使用对仗语(add/remove等)
> 为常用操作确立命名规则(用于区分不同类别的操作)
4、 子程序可以写多长
※ 理论上认为:子程序最佳最大长度通常是一屏代码或打印出来一到两页的代码,大约50-150行代码。(IBM: 50行之内。 TRW: 两页纸之内。)
※ 与其对子程序的长度强加限制,还不如让下面这些因素来决定子程序的长度。
(子程序的内聚性、嵌套的层次、变量的数量、决策点的数量、解释子程序用意的注释数量以及跟复杂度相关的考虑事项)。
5、 如何使用子程序参数
※ 子程序之间的接口是程序中最易出错的部分之一。
减少接口间错误的指导原则:
> 按照输入-修改-输出的顺序排列参数
※ Wrong:随机、按字母顺序
※ 顺序参考:仅作为输入参数 à 既是输入又是输出的参数 à 仅作为输出的参数
> 考虑自己创建IN和OUT关键字
※ 预处理定义IN和OUT(起说明性作用)
※ 注意:1、确保一致的使用该定义方法。2、不要混淆IN和const的作用。
> 如果几个子程序都用了类似的一些参数,应该让这些参数的排列顺序保持一致
例:fprintf()/printf、fputs/puts、strncpy/memcpy。
> 使用所有的参数
往一个子程序中传递一个参数,就一定要使用这个参数。如果你不要它,请尽快把它从接口中删除。
> 把状态和出错变量放在参数表最后
※ 它们只是附属于程序的主要功能,而且它们是仅用于输出的变量。
> 不要把子程序的参数用做工作变量
※ 请明确的引入工作变量。
> 在接口中对参数的假定加以说明
※ 如果假定了传递给子程序的参数具有某种特征,那就要对这种假定加以说明。
※ 一种比用注释还好的方法 —> 在代码中使用断言。
应该对哪些接口参数的假定说明:
※ 参数是仅用于输入的、要被修改的、还是仅用于输出的。
※ 表示数量的参数的单位(英寸、英尺、米等)。
※ 状态代码和错误值的含义。
※ 接受的数值的范围。
※ 不该出现的特定数值。
> 把子程序的参数个数限制在大约7个以内
> 考虑对参数采用某种表示输入、修改、输出的命名规则
※ 认为区分输入、修改、输出参数很重要的话,就建立一种命名规则来进行区分。
> 为子程序传递用以维护其接口抽象的变量或对象
※ 如果要表达的抽象是:子程序期望3项特定数据,这3项数据碰巧由同一个对象提供,那就应单独传递3项数据。
※ 如果要表达的抽象是:想一直拥有某个特定的对象,且要对这个对象执行很多操作,就应该传递整个对象。
> 使用具名参数
※Visual Basic:可以显式地把形式参数和实际参数对应起来。
> 确保实际参数和形式参数相匹配
好习惯:总要检查参数表中参数的类型,同时留意编译器给出的关于参数类型不匹配的警告。
6、 使用函数时要特别考虑的问题
函数(有返回值的子程序)和过程(无返回值的子程序)的区别更多的是语义的区被,而不是语法的区别。
> 什么时候使用函数,什么时候使用过程
> 设置函数的返回值
回避返回错误的返回值的原则:
※ 检查所有可能的返回路径(尽可能初始化返回值)
※ 不要返回值向局部数据的饮用或指针。
7、 宏子程序和内联子程序
预处理器的宏语言编写的指导原则:
※ 把宏表达式整个包含在括号内
(最终要展开到代码中:#define CUBE(a) ((a) * (a) * (a)))。
※ 把含有多条语句的宏用大括号括起来
使用宏函数具有风险、且不易理解,应该尽量避免使用。
※ 用给子程序命名的方式来个宏函数命名,以便在需要时可以用子程序来替换它
※ 宏子程序在使用上的限制(C++语言替代宏的方案)
1、const定义常量
2、inline定义内嵌代码函数
3、template用于以类型安全的方式定义各种标准操作,min、max等
4、typedef可以定义简单的类型替换
※ 内联子程序
因为避免了子程序调用的开销,因此inline机制可以产生高效的代码。
※ 节制使用inline子程序
inline子程序违反了封装原则,实现代码写在头文件中,从而暴露了程序的实现细节。