一、何为子程序?
子程序是为实现某一特定目的而编写的方法或过程。
二、为何写子程序?
1、可以降低复杂度。如果面临一个复杂逻辑,通过子程序可以有效分解逻辑,比如将一些列操作封装起来,通过函数名来传递某种逻辑。
2、提高可移植性。如果想要某个程序有很高的可移植性,可以通过子程序将与硬件或非语言标准的东西隔离开,达到更高的可移植性。
3、避免重复代码。重复的代码在维护上带来麻烦,同时也占据了不必要的空间,如果两段程序中出现了类似的代码,则说明程序的分解上出现问题,并且改动起来会加大麻烦。
4、隐藏操作。对于使用指针、全局变量或者复杂布尔判断的地方,可以用子程序进行封装,这样可以更专注于本身要解决的问题。
三、如何设计子程序?
一般来说,一个子程序只实现某一特定目的,所以子程序的设计主要原则为增强子程序的内聚性。
1、如果一个子程序只实现单一功能,例如sin(),则可认为其具有很强的内聚性。
2、如果一个子程序中出现两种功能,并且两种功能只是用了相同的数据,并没有其他联系,则称之为通信内聚性。例如,将一份报表中的值打印后又将报表中的值进行重置。这个例子中,两个操作--打印和重置只是用了相同的数据,而没有进一步的联系,所以这种子程序的内聚性是不如功能内聚性强的。如果将打印和重置分别放到不同子程序,再由更高层次的子程序来调用两个子程序,则其内聚性提高。
3、如果一个子程序中存在只是需要同时执行才放到一起的操作,则称之为临时内聚性。比如说系统的启动需要读取不同配置文件,初始化临时文件,设置管理器等。对此可用临时内聚性的子程序分别调用各个每个操作所构成的子程序而不是由它直接执行所有的操作。
四、如何命名子程序?
1、描述子程序所做的所有事情。好的子程序命名通常起到子解释子程序功能的作用。
2、避免使用无意义的、模糊或表达不清的动词。有些词语表达非常灵活,可以延伸到涵盖几乎任何含义。例如HandleOutput()改成FormatAndPrintOutput(),就很清楚这个子程序的功能了。
3、不要仅适用于数字来形成不同的子程序名字。例如OutputUser.OutputUser1、OutputUser3这种子程序名字后面的数字无法显示出子程序所代表的抽象有何不同。
4、给过程起名时使用语气强烈的动词家宾语的形式。例如PrintDocument()、CalcMonthlyRevenues()、CheckOrderInfo()等都是不错的名字。在面向对象语言中,不用在过程名中接入对象的名字(宾语),因为对象本身已经包含在调用语句中了,可以用document.Print(),OrderInfo.Check()来表达。document.PrintDocument()这样的语句不仅显得太臃肿,并且在他们的派生类中被调用时也容易产生误解。如Check(支票)类是从Document(文档)类继承而来的,那么check.Print()就很显然表示打印一张支票,而check.PrintDocument()看上去就像是要打印支票簿或信用卡的对账单而不是想要打印支票本身。
5、准确使用对仗词。命名时遵守对仗词的命名规则有助于保持一致性。下面是一些常用队长词组:
add/remove increment/decrement open/close begin/end insert/delete show/hide create/destroy lock/unlock source/target first/last min/max start/stop get/put next/previous up/down get/set old/new
6、为常用操作确立命名规则。在不同的系统里,区分不同类型的操作非常重要。例如获取对象的id操作:
假如某个项目中的每个对象都被分配了一个唯一标识,由于忽视为返回这种对象标识的子程序建立一个命名规则,以至于有了下面命名:
employee.id.Get() dependent.GetId() supervisor() candidate.id()
其中:Employee类提供了其id对象,而该对象有进而提供了Get()方法;Dependent类提供了GetId()方法;SuperVisor类把id作为它的默认返回值;Candidate类则认为id对象的默认返回值是id,因此暴露了id对象。这样一来,一个id对象的获取就有这么多细节需要考虑,而这些问题完全可以通过建立获取id的命名规则而避免。
五、子程序应该多长?
一般来说,最大长度为100-150是一个不错的区间,当一个子程序超过200行时就应该特别注意了,通常这样的程序迟早会在可读性上遇到问题。
六、子程序的参数使用?
1、参数应该按照输入-修改-输出的顺序传递。这样传递正好与子程序输入、处理、输出的执行顺序一致。但是在c语言中,是按照修改的参数排在前面的顺序排列的。选用某种方式最好一致采用它,至少在整个项目中都是按照某种规则进行。
2、考虑定义自己的IN和OUT关键字。在这里,IN和OUT只是起到说明作用,如果想要改变参数的值,仍然需要传递指针或引用。在应用这项技术之前,有两个方面尤须注意。1)IN和OUT扩展了标准语言,可能会使得使用这个语言的人感到生疏。2)即使采用了IN,仍然可以在子程序中更改参数的值,而在别人看来,会认为子程序的功能是正确的,从而造成误解。注:在c++中,应该采用const关键字进行输入限定。
3、如果几个子程序都采用了类似的参数,则应该让它们的参数顺序保持一致。例如printf和fprintf只是前面多了一个f,所以其参数方面应该保持顺序一致,不要因为这样一个小小的顺序问题而导致记忆困难。
4、状态和出错变量应该参数表的最后。对于状态和出错变量,他们只是程序的附属功能,而且他们是仅用于输出的参数,因此这是一种很有道理的原则。
5、不要把子程序的参数用作工作变量。应在子程序中另外申请局部变量。如果将参数用作工作变量,由于已经在程序中改变参数的值,如果在子程序的某个地方要使用原有输入参数的值,就会出现错误。
6.在接口中对参数的假定加以说明。如果你假定了传给子程序的参数具有某种特征,就应该对这种假定加以说明。哪些参数假定需要加以说明呢?
1)参数是仅用于输入的、还是修改的、还是仅用于输出的。
2)表示数量的参数的单位(英寸、英尺、米等)。
3)如果没有枚举的话,应该说明状态代码和错误的含义。
4)所能接收的数值的类型。
5)不该出现的特定数值。
7、把子程序的参数个数限定在7个以内。如果你发现自己一直要传递很多参数,这就说明子程序之间的耦合太过紧密了,应该重新设计这个或这组子程序,降低期间的耦合度。如果你向很多不同的子程序传递相同的参数,就请把这些子程序组成一个类,并把那些经常用到的数据用作类的内部数据。
8、考虑对对参数采用某种表示输入、修改和输出的命名规则。如果你觉得把输入、修改、输出参数区分开很重要,那么就建立一种命名规则来对它们进行区分。
9、为子程序传递用以维持其接口抽象的对象或变量。比如说你有一个对象,它通过10个访问器子程序暴露其中的数据,被调用的子程序只需要其中的3项就能进行操作。如果要表达的抽象是子程序期望3项特定的值,但这3项特定的值只是碰巧由同一个对象所提供的,那就应该单独传递着3项数据。然而,如果子程序接口要表达的抽象是一直拥有某个特定对象,且该子程序要对这一对象执行这样、那样的操作,如果单独传递3项特定的数据,那就是破坏了几口的抽象。
如果你采用传递整个对象的做法,并发现自己是先创建对象,把被调用子程序所需的3项数据填入该对象,在调用过子程序后又从对象中取出3项数据的值,那就是一个证据,说明你应该只传递那3项数据而不是整个对象。(一般来说,如果在调用子程序之前出现装配的代码,或者在调用子程序之后出现拆卸的代码,都是子程序设计不佳的表现。)
如果你发现自己经常需要修改子程序的参数表,而每次修改的参数都是来自于同一个对象,那就说明你应该传递整个对象儿不是个别参数项了。
10、确保实际参数和形式参数相匹配。请养成好习惯,总是检查参数表中参数的类型,并对编译器给出的类型警告予以留意。
七、子程序的返回值?
1、检查所有可能的返回路径。应该确保所有的路径都有返回值。
2、不要返回局部变量的指针或引用。
八、宏子程序使用?
1、将宏表达式整个包含在括号中。将宏表达式包含在括号中是为了防止展开后出错和优先级问题。
2、将含有多条语句的宏用大括号括起来。这样做是为了在循环等操作中只执行一条语句,而其他语句没有执行。