高质量的子程序
1. 创建子程序的正当理由
1.1. 降低复杂度
可以通过子程序来隐藏一些信息,主程序中就不再考虑这些信息。如果内部循环层次太深,就意味着需要从子程序中提取新的子程序,把嵌套的部分提取出来形成一个独立的子程序,可以降低外网子程序的复杂度。
1.2. 引入中间易懂的抽象
把一段代码放入一个命名恰当的子程序内,是说明这段代码用意最好的方法之一。
If(node <> null) then
While( node.next<>null) do
Node =node.next
Leafname=node.name
End while
Else
Leafname=””
End if
修改成如下语句更易懂:
Leafname=getleafname(node)
1.3. 避免代码重复
1.4. 支持子类化
覆盖简短而规整的子程序所需的新代码的数量要比覆盖冗长而邋遢的子程序更少,如果你能让覆盖的子程序保持简单,那你在实现派生类的时候也会减少犯错的几率。
1.5. 隐藏顺序
比如用户登录需要先判断验证码是否正确,再判断用户名密码是否正确,这两个操作应该放在两个子程序中,从何保证一个子程序存在是否依赖于另一个子程序是否已经执行。
1.6. 提高可移植性
可用子程序来隔离程序中不可移植的部分从而明确识别和隔离未来的移植工作,不可移植的部分包括对硬件依赖,对操作系统的依赖的等。
1.7. 简化复杂的布尔判断
为了理解程序的流程通常并没有必要去研究那些复杂的布尔判断的细节。应该把这些判断放在函数中以提高代码的可读性,同时也强调了它的重要性。
1.8. 改善性能
通过使用子程序你可以在一个地方优化代码。把代码集中在一处可以更加方便的查出哪些代码的运行效率低。
其他理由
Ø 隔离复杂度
Ø 隐藏实现细节
Ø 限制变化所带来的影响
Ø 隐藏全局数据
Ø 形成中央控制点
Ø 促成可重用的代码
Ø 达到特定的重构目的
似乎过于简单而没有必要写成子程序的操作
编写有效的子程序时一个最大的心理障碍是不情愿为一个简单的目的而编写一个简单的子程序,写一个只有两三行的子程序看起来有点大材小用,但是经验可以表明一个很好而又小巧的子程序会多有用。
Ø 提高可读性
Ø 简单的操作常常会变成复杂的操作
2. 在子程序上设计
内聚性是指子程序中各种程序之间的联系紧密程度
功能的内聚性是最强也是最好的一种内聚性,也就是说让一个子程序仅执行一项操作。例如:sin()、getcustomername()这样的子程序时高度内聚的。当然以这种方式来评估内聚性前提是子程序所执行的操作与其名字相符
除此之外其他种类的内聚性通常认为是不够理想的
顺序上的内聚性是指在子程序内包含有需要按特定程序进行的操作,这些数据需要共享数据,而且只在全部执行完则完成了一项完整的功能。例如:子程序需要按照给的出生日期来进算出员工的年龄和退休时间。如果子程序先计算员工年龄再根据年龄计算退休时间那么他就具有顺序的内聚性。这时你可以创建两个不同的子程序,他们能根据给定的日期分别计算出员工的年龄和退休时间,其中计算退休时间的子程序可以调用计算年龄的子程序,这样两者都具有功能上的内聚性了。
通信上的内聚性是指一个子程序中不同操作使用了同样的数据,但不存在其他任何的联系,入刚才那个例子中传入日期先计算年龄再计算退休时间,两次计算之间只是碰巧使用了相同的出生日期,那么这个子程序就只具有通信上的内聚性。
临时的内聚性是指有一些因为需要同时执行才放到一起的操作的子程序,比如说startup()shutdown()等。startup()子程序可能需要读取配置文件、初始化临时文件、设置内存管理器。要想使他最有效,应该那原来那个具有临时内聚性的子程序去调用其他子程序,由这些字程序来完成特定的操作,而不是直接由他执行所有的操作。
逻辑上的内聚性是指若干操作被放入同一个子程序中,通过传入的执行标志选择执行其中的一项操作。
巧合的内聚性是指子程序各个操作之间没有任何可以看见的关联。
3. 指导原则
描述子程序所作的所有事情
避免使用无意义模糊或表达不清的动词,有些动词的含义非常灵活,可以延伸到涵盖几乎任何含义。像performservices()、processinput()这样的子程序根本不能说明程序在做什么,他们只能说明跟服务、输入有关
不要仅通过数字来形成不同的子程序名,例如把所有的代码都写成一个大函数,然后为每十五行代码创建一个大函数,并把他们分别命名为part1、part2等。
根据需要确定子程序名字的长度,变量名的最佳长度是9到15个字符
给函数命名时要对返回字有所描述,比如说customerid.next()都是不错的函数名
给过程起名时使用语气强烈的动词加宾语形式,例如printdocument()、check order infor()都是很不错的过程名,在面向对象语言中不用在过程名中加入对象的名字(宾语),如document()、orderinfo.check()。
准确使用对仗词,例如add\remove increment\decrement open\close
Begin\end insert\delete show\hide
Creste\destroy lock\unlock source\target
First\last min\max start\stop
Get\put next\previous up\down
Get\set old\new
4. 子程序可以写多长
Ø 保证功能上的内聚性,尽量短小
Ø 不超过200行,IBM研究表明:超过200行的子程序,可维护性和出错率都会增加
5. 如何使用子程序参数
按照输入-修改-输出的顺序排列参数 修改:及时既作为输入又作为输出用途的参数,如引用对象
如果几个子程序都用了类似的一些参数让这些参数的排列顺序保持一致。如:stringbuilder的 Append(array<Char>[]()[], Int32, Int32) 和 Insert(array<Char>[]()[], Int32, Int32)
使用所有的参数
把状态或出错变量放在后边。状态变量一般都是枚举类型,如Regex. Match(String, String, RegexOptions)
不把子程序的参数用做工作变量。
错误
Int Multiply(int multiplier)
{
multiplier *= 2;
。。。。。。。。。。
Return multiplier
}
在上面的最后一行代码中,multiplier的值已经发生改变,如果日后要修改这段程序,要在其他地方使用原有输入值,你可能会想当然的以为multiplier 是含有原始输入值的参数并使用它,而事实上并非如此
正确
Int Multiply(int multiplier)
{
Int product=0;
product = multiplier * 2;
。。。。。。。。。
Return product;
}
在接口中对参数的假定加以说明 ,通过注释,或者断言,后期会讨论如何编写自说明代码
参数是用于输入的、要被修改、用于输出
表示数量的参数的单位
所能接受的数值范围
不该出现的特定数值
参数限制在7个以内 跟人的记忆特性有关,人很难同时记住超过7个单位的信息
为子程序传递用以维持其接口抽象的参数或对象 如一个程序有10 个属性,被调用的子程序只需要其中的3项数据就能进行操作。我们是只传递三项数据还是整个对象都进行传递?子程序的接口要表达何种抽象,是其处理问题的关键。如果你是发现自己是先创建对象,把被调用子程序的3项数据填入该对象,在调用过子程序后,有从对象中取出3项数据的值,这就说明你应该只传递那3项数据,而不是对象。
6. 使用函数时要特别考虑的问题
函数是指有返回值的子程序;过程是值没有返回值的子程序
差:
If(report.FormatOutput(formattedReport) = sussess) then
好
Report.FormatOutput(formattedReport,out oupputStatus)
If(oupputStatus= success) then
Status = report.FormatOutput(formattedReport)
If(status = successe)
(
)
专注 + 保持追求精益求精的精神 + 完美的心态 == 天才