《代码大全》学习笔记(5):高质量子程序特点

简介:         什么叫“子程序”?子程序是具有单一功能的可调用的函数或过程。5.1 生成子程序的原因        降低复杂性:使用子程序的最首要原因是为了降低程序的复杂性,可以使用子程序来隐含信息,从而使你不必再考虑这些信息;一个子程序需要从另一个子程序中脱离出来的原因之一是,过多重数的内部循环和条件判断。

        什么叫“子程序”?子程序是具有单一功能的可调用的函数或过程。
5.1 生成子程序的原因
        降低复杂性:使用子程序的最首要原因是为了降低程序的复杂性,可以使用子程序来隐含信息,从而使你不必再考虑这些信息;一个子程序需要从另一个子程序中脱离出来的原因之一是,过多重数的内部循环和条件判断。
        限制了改动带来的影响:由于在独立区域进行改动,因此,由此带来的影响也只限于一个或最多几个区域中。
        隐含顺序:把处理事件的非特定顺序隐含起来。
        改进性能:通过使用子程序,可以只在一个地方,而不是同时几个地方优化代码段。
        进行集中控制。
        隐含数据结构:可以把数据结构的实现细节隐含起来,这样,绝大部分程序都不必担心这种杂乱的计算机科学结构,而可以从问题域中数据是如何使用的角度来处理数据。
        隐含全局变量。
        隐含指针操作:指针操作可读性很差,而且很容易引发错误;通过把它们独立在子程序中,可以把注意力集中到操作意图而不是机械的指针操作本身。
        重新使用代码段:放进模块化子程序中的代码段重新使用。
        计划开发一个程序族:如果想改进一个程序,最好把将要改动的那部分放进子程序中,将其独立。
        提高部分代码的可读性:把一段代码放入一个精心命名的子程序,是说明其功能的最好办法。
        提高可移植性:可以使用子程序来把不可移植部分、明确性分析和将来的移植性工作分隔开来,不可移植的部分包括:非标准语言特性、硬件的依赖性和操作系统的依赖性等。
        分隔复杂操作:复杂操作包括繁杂的算法、通信协议、棘手的布尔测试、对复杂数据的操作等等。
        独立非标准语言函数的使用:绝大多数实现语言都含有一些非标准的但却方便的扩展。
        简化复杂的布尔测试:很少有必要为理解程序流程而去理解复杂的布尔测试。
5.1.1 简单而没有写入子程序的操作
        小型子程序有许多优点,其中之一是改进了可读性。
5.1.2 创建子程序的理由总结
        创建子程序的理由概述:降低复杂性、避免重复代码段、限制改动带来的影响、隐含顺序、改进性能、进行集中控制、隐含数据结构、隐含指针操作、隐含全局变量、促进重新使用代码段、计划开发一个软件族、改善某一代码段可读性、改善可移植性、分隔复杂操作、独立非标准语言函数的使用、简化复杂的布尔测试。


5.2 子程序名称恰当
        子程序有效命名的指导方针:1)对于过程的名字,可以用一个较强的动词带目标的形式;2)对于函数名字,可以使用返回值的描述;3)避免无意义或者模棱两可的动词;4)描述子程序所做的一切;5)名字的长度要符合需要;6)建立用于通用操作的约定。


5.3 强内聚性
        内聚性指的是在一个子程序中,各种操作之间互相联系的紧密程度;有些程序员喜欢用“强度”一词来代替内聚性。
5.3.1 可取的内聚性
        通常认为是可以接受的一些内聚类型:1)功能内聚性(当程序执行一项并且仅仅是一项工作时,就是这种内聚性);2)顺序内聚性(是指在子程序内包含需要按特定顺序进行的、逐步分享数据而又不形成一个完整功能的操作);3)通讯内聚性(通讯内聚性是在一个子程序中,两个操作只是使用相同数据,而不存在其它任何联系时产生的);4)临时内聚性(因为同时执行的原因才被放入同一个子程序里,这时产生临时内聚性)。
5.3.2 不可取的内聚性
        过程内聚性:当子程序中的操作是按某一特定顺序进行的,就是过程内聚性;与顺序内聚性不同,过程内聚性中的顺序操作使用的并不是相同数据。
        逻辑内聚性:当一个子程序中同时含有几个操作,而其中一个操作又被传进来的控制标志所选择时,就产生了逻辑内聚性;之所以称之为逻辑内聚性,是因为这些操作仅仅是因为控制流,或者说“逻辑”的原因才联系到一起的。
偶然内聚性:当同一个子程序中的操作之间无任何联系时,为偶然内聚性,也叫做“无内聚性”。
5.3.3 内聚性举例


5.4 松散耦合性
        所谓耦合性指的是两个子程序之间联系的紧密程度;耦合性与内聚性是不同的,内聚性是指一个子程序的内部各部分之间的联系程度,而耦合指的是子程序之间的联系程度。
        子程序之间具有良好耦合的特点是它们之间的耦合是非常松散的,任一个子程序都能很容易地被其它子程序调用;在建立一个子程序时,应尽量避免它对其它子程序有依赖性。
5.4.1 耦合标准
        几条估计子程序间耦合程度的标准:
        耦合规模:所谓耦合规模是指两个子程序之间联系的数量多少;对于耦合来说,联系的数量越少越好。
        密切性:密切性指的是两个子程序之间联系的直接程度;联系越直接越好,两个子程序之间联系最密切的是参数表中的参数。
        可见性:是指两个子程序之间联系的显著程度。
        灵活性:是指改变两个子程序之间联系的容易程度。
5.4.2 耦合层次
        简单数据耦合:如果两个子程序之间传递的数据是非结构化的,并且全部都是通过参数表进行的,这通常称作“正常耦合”,这也是一种最好的耦合。
        数据结构耦合:如果在两个程序之间传递的数据是结构化的,并且是通过参数表实现传递的,它们之间就是数据结构耦合的;这种耦合有时也称之为“邮票耦合”(stamp coupling)。
        控制耦合:如果一个子程序通过传入另一个子程序的数据通知它该作什么,那么这两个子程序就是控制耦合的。
全局数据耦合:如果两个子程序使用同一个全局数据,那它就是全局数据耦合的;这也就是通常所说的“公共耦合”或“全局耦合”。
        不合理耦合(pathological):如果一个子程序使用了另外一个子程序中代码,或者它改变了其中的局部变量,那么它们就是不合理耦合的;这种耦合也称之为“内容耦合”。
5.4.3 耦合举例


5.5 长度
       理论上,常把一个子程序的最佳长度定为一两页,即66到132行


5.6 防错性编程
        在防错性编程中,其中心思想是,即使一个子程序被传入了坏数据,它也不会被伤害,哪怕这个数据是由其它子程序错误而产生的。
5.6.1 使用断言
        断言是一个在假设不正确时会大声抗议的函数或宏指令,可以使用断言来验证在程序中做出的假设并排除意外情况。
下面是使用断言的一些指导方针:1)如果有预处理程序的话,使用预处理程序宏指令;2)在断言中应避免使用可执行代码,把可执行代码放入断言。
5.6.2 输入垃圾不一定输出垃圾
      “输入垃圾,输出垃圾”,往往是劣质程序。
       检查所有外部程序输入的数值;检查全部子程序输入参数值;决定如何处理非法参数。
5.6.3 异常情况处理
       应该预先设计好异常处理措施来注意意想不到的情况,异常处理措施应该能使意外情况的出现在开发阶段变得非常明显,而在运行阶段又是可以修复的。
5.6.4 预计改动
        越是可能的改动,越是要容易进行,把你在其中预想到的改动域隐含起来,是减少由于改动而对程序带来影响的最有力武器之一。
5.6.5 计划去掉调试帮助
        调试帮助措施包括:断言、内存检查报告、打印语句等及其它一些为方便调试而编写的代码。
避免调试信息混在程序的几种方法:1)使用版本控制;2)使用内部预处理程序;3)编写自己的预处理程序;4)保留使用调试程序。
5.6.6 尽早引入调试辅助工具
        越早引入调试辅助工具,它们所起的作用也就会越大。
5.6.7 使用“防火墙”包容错误带来的危害
       “防火墙”技术是一种包容危害策略。
        信息隐蔽可以帮助在程序中建立防火墙。
        松散的耦合也是在程序内部修建防火墙的手段之一。
        在程序中建防火墙的最好办法是把某些接口标识成“安全”区边界。
5.6.8 检查函数返回值
        如果调用了一个函数,并且可以忽略函数返回值(例如,在C语言中,甚至不需要知道函数是否返回一个值),千万不要忽略这个返回值;防错性设计的核心就是防止常错误。
5.6.9 在最终软件中保留多少防错性编程
        在最终软件中应该保留哪些防错性编程的一些原则:1)保留查找重要错误的代码;2)去掉那些无关紧要错误的代码;3)去掉那些引起程序终止的代码;4)保留那些可以使程序延缓终止的代码;5)保证留在程序中的错误提示信息是友好的;6)要对防错性编程提高警惕。


5.7 子程序参数
        程序中39%的错误都是内部接口错误,即子程序间的通信错误;以下是尽量减少这类错误的一些准则:1)确保实际参数与形式参数匹配;2)按照输入—修改—输出的顺序排列参数;3)如果几个子程序使用了相似的参数,应按照不变的顺序排列这些参数;4)使用所有的参数;5)把状态和“错误”变量放在最后;6)不要把子程序中的参数当作工作变量;7)说明参数的接口假设;8)应该把一个子程序中的参数个数限制在7个左右;9)考虑建一个关于输入、修改和输出参数的命名约定;10)仅传递子程序需要的那部分结构化变量;11)不要对参数传递作出任何设想。


5.8 使用函数
        函数是返回一个值的子程序,而过程则是不返回值的子程序。
5.8.1 什么时候使用函数,什么时候使用过程
        公用编程法是指把一个函数当作过程来使用,并返回一个状态变量。
5.8.2 由函数带来的独特危险
        使用函数产生了可能不恰当值的危险,这常常是函数有几条可能的路径,而其中一条路径又没有返回一个值时产生的;在建立一个函数时,应该在心中执行每一条路径,以确认函数在所有情况下都可以返回一个值。

5.9 宏子程序
        特殊情况下,用预处理程序宏调用生成子程序;把宏指令表达式括在括号中。
5.9.1 检查表
        高质量的子程序:总体问题、防错性编程、参数传递问题。


5.10 小结
(1) 建立子程序的最重要原因是加强可管理性(即降低复杂性),其它原因还有节省空间、改进正确性、可靠性、可修改性等等。
(2) 强调强内聚性和松散耦合的首要原因是它们提供了较高层次的抽象性,你可以认为一个具备这种特性的子程序运行是独立的,这可以使你集中精力完成其它任务。
(3) 有些情况下,放入子程序而带来巨大收益的操作可能是非常简单的。
(4) 子程序的名称表明了它的质量,如果名称不好但却是精确的,那么说明它的设计也是非常令人遗憾的;如果一个子程序的名称既不好又不精确,那它根本就无法告诉你程序作了些什么;无论哪种情况,都说明程序需要改进。
(5) 防错性编程可以使错误更容易被发现和修复,对最终软件的危害性显著减小。

 

        本章小结:

        本章介绍了高质量子程序特点,包括:名称恰当、强内聚性、松散耦合性、进行防错性编程等。

        我们编写任何代码的一个目的,都是为了确保程序的高质量。几乎所有的企业都在说:质量是企业的生命。对于IT公司来说,这就体现在了开发人员所编写的代码的质量上面。

        一般而言,产品的质量与代码的质量是成正比的。为了交付给用户可靠的产品,本章中所涉及到的知识都可以派上用场。

目录
相关文章
|
11天前
|
编译器 程序员 C语言
爱上C语言:什么C语言函数不太会,那你千万不要错过这篇(函数篇)
爱上C语言:什么C语言函数不太会,那你千万不要错过这篇(函数篇)
|
5月前
|
编译器 C语言
C语言编程陷阱:移植性
在编写可移植的C代码时,我们应考虑和应对可移植性缺陷,以确保代码能够在不同平台上正确运行。通过遵循C语言标准、了解特定平台的行为和使用标准库提供的函数等手段,可以减少可移植性问题的发生,并编写出更加健壮和可靠的C代码。同时,积极参与C语言社区和了解最新的C语言标准变化也是确保代码可移植性的重要方式。
53 0
|
5月前
|
C语言
C语言编程陷阱:预处理器 陷阱
预处理器 不能忽视宏定义中的空格: #include <stdio.h>
20 1
|
5月前
|
安全 C语言
C语言编程陷阱:库函数陷阱
会造成较高的系统负担 暂存然后以大块写入的方式 缓冲数组最好时成为静态数组,或者显示内存申请
33 1
|
8月前
|
存储 自然语言处理 编译器
c语言学习第三十五课——程序环境与预处理
c语言学习第三十五课——程序环境与预处理
108 0
|
10月前
|
算法 程序员 编译器
抽丝剥茧C语言(中阶)函数
抽丝剥茧C语言(中阶)函数
|
10月前
|
C语言
抽丝剥茧C语言(中阶)函数练习
抽丝剥茧C语言(中阶)函数练习
|
10月前
|
存储 编译器 C语言
【C】程序的编译/链接/预处理详解(C语言内功,精炼,基础)
【C】程序的编译/链接/预处理详解(C语言内功,精炼,基础)
|
12月前
|
存储 自然语言处理 算法
C语言总结(第2章算法——程序的灵魂 第3章最简单的C程序设计——顺序程序设计 第4章选择结构程序设计)
算法(algorithm)是解决一系列问题的清晰指令,也就是,能对一定规范的输入,在有限的时间内获得所要求的输出。 6是解决做什么和怎么做的问题。简单来说,算法就是解决一个问题的具体方法和步骤。算法是程序的灵魂。
739 0
|
C语言
C语言九条语句通俗易懂总结:
c语言的九条语句是学系c的基本,想要学好这一门语言,就要掌握这9条语句!
C语言九条语句通俗易懂总结: