更好的方法是明确地引入一些工作变量,从而避免当前或日后的麻烦。下面这段代码演示了这一技术:
Java示例:正确地使用输入参数
int Sample( int inputVal ) {
int workingVal = inputVal;
workingVal = workingVal * CurrentMultiplier( workingVal );
workingVal = workingVal + CurrentAdder( workingVal );
...
...
return workingVal;
}
引入了新变量workingVal,就澄清了inputVal的角色,同时也消除了在错误的时间误用inputVal的可能。(千万不要以此为由就把变量命名为inputVal或workingVal。一般说来,inputVal和workingVal都是极其糟糕的变量名,上例中用到这两个名字只是为了能够明确这两个变量的角色。)
把输入值赋给工作变量,这种方式强调了数据是来自何方的。它也能避免对于参数表中变量的值的意外修改。在C++中可以利用const关键字,让编译器帮助实施这一限制。当一个参数被标记为const时,在子程序中就不能修改其值。
在接口中对参数的假定加以说明 如果你假定了传递给子程序的参数具有某种特征,那就要对这种假定加以说明。在子程序内部和调用子程序的地方同时对所做的假定进行说明是值得的。不要等到把子程序写完之后再回过头去写注释——你是不会记住所有这些假定的。一种比用注释还好的方法,是在代码中使用断言(assertions)。
应该对哪些接口参数的假定进行说明呢?
■ 参数是仅用于输入的、要被修改的、还是仅用于输出的;
■ 表示数量的参数的单位(英寸、英尺、米等);
■ 如果没有用枚举类型的话,应说明状态代码和错误值的含义;
■ 所能接受的数值的范围;
■ 不该出现的特定数值。
把子程序的参数个数限制在大约7个以内 对于人的理解力来说,7是一个神奇的数字。心理学研究发现,通常人类很难同时记住超过7个单位的信息(Miller 1956)。这一发现已经用于各种领域之中,因此,假定人不能同时记住超过约7个的子程序参数,也是合适的。
在实践中,子程序中参数的个数到底应该限制在多少,取决于你所使用的编程语言如何支持复杂的数据类型。如果你使用的是一种支持结构化数据的现代编程语言,你就可以传递一个含有13个成员的合成数据类型,并将它看做一个大数据块。如果你使用的是一种更为原始的编程语言,那你可能需要分别传递全部13个成员。
如果你发现自己一直需要传递很多参数,这就说明子程序之间的耦合太过紧密了。应该重新设计这个或这组子程序,降低其间的耦合度。如果你向很多不同的子程序传递相同的数据,就请把这些子程序组成一个类,并把那些经常使用的数据用作类的内部数据。
考虑对参数采用某种表示输入、修改、输出的命名规则 如果你觉得把输入、修改、输出参数区分开很重要,那么就建立一种命名规则来对它们进行区分。你可以给这些参数名字加上i_、m_、o_前缀。如果不闲啰嗦,也可以用Input_、Modify_或Output_来当前缀。
为子程序传递用以维持其接口抽象的变量或对象 关于如何把对象的成员传给子程序这一问题,存在着两种互不相让的观点。比如说你有一个对象,它通过10个访问器子程序(access routine)暴露其中的数据,被调用的子程序只需要其中的3项数据就能进行操作。
持第一种观点的人们认为,只应传递子程序所需的3项特定数据即可。他们的论据是,这样做可以最大限度地减少子程序之间的关联,从而降低其耦合度,使它们更容易读,更便于重用,等等。他们强调说,把整个对象传递给子程序就破坏了封装的原则,因为这样做就是潜在地把所有10个访问器子程序都暴露给被调用的那个子程序了。
持第二种观点的人们则认为应该传递整个对象。他们认为,如果在不修改子程序接口的情况下,让被调用子程序能够灵活使用对象的其余成员,就可以保持接口更稳定。他们争辩说,只传递3项特定的数据破坏了封装性,因为这样做就是把特定的数据项暴露给被调用的那个子程序了。
我认为这两种规则都过于简单,并没有击中问题的要害:子程序的接口要表达何种抽象?如果要表达的抽象是子程序期望3项特定的数据,但这3项数据只是碰巧由同一个对象所提供的,那就应该单独传递这3项数据。然而,如果子程序接口要表达的抽象是想一直拥有某个特定对象,且该子程序要对这一对象执行这样那样的操作,如果单独传递3项特定的数据,那就是破坏了接口的抽象。
如果你采用传递整个对象的做法,并发现自己是先创建对象,把被调用子程序所需的3项数据填入该对象,在调用过子程序后又从对象中取出3项数据的值,那就是一个证据,说明你应该只传递那3项数据而不是整个对象。(一般说来,如果在调用子程序之前出现进行装配(set up)的代码,或者在调用子程序之后出现拆卸(take down)的代码,都是子程序设计不佳的表现。)
如果你发现自己经常需要修改子程序的参数表,而每次修改的参数都是来自于同一个对象,那就说明你应该传递整个对象而不是个别数据项了。
使用具名参数 在某些语言中,你可以显式地把形式参数(formal parameter)和实际参数(actual parameter)对应起来。这使得参数的用法更具自我描述性,并有助于避免因为用错参数而带来的错误。下面是用Visual Basic语言写的一个示例:
Visual Basic示例:显式的标识参数
Private Function Distance3d( _
ByVal xDistance As Coordinate, _
ByVal yDistance As Coordinate, _
ByVal zDistance As Coordinate _
)
...
End Function
...
Private Function Velocity( _
ByVal latitude as Coordinate, _
ByVal longitude as Coordinate, _
ByVal elevation as Coordinate _
)
...
Distance = Distance3d(xDistance:=latitude,yDistance:= longitude,_
zDistance := elevation )
...
End Function
当你有超乎平均数量的同样类型的参数时,就可能发生参数放错位置但编译器却检测不到的情况,这时上述技术就格外有用了。在很多场合下,显式地把参数对应起来可能会有些矫枉过正,但在需要高安全性或高可靠性的情形下,花额外的功夫把参数按照设想的方式对应起来是十分值得的。
确保实际参数与形式参数相匹配 形式参数也称为“哑参数(dummy parameters)”,是指在子程序定义中声明的变量。实际参数是指在实际的子程序调用中用到的变量、常量或表达式。
一个常见的错误是在调用子程序时使用了类型错误的变量——例如,在本该使用浮点类型的地方用了整型。(只有当你使用像C这样的弱类型编程语言,并且没有开启全部的编译器警告功能时,才会遇到这个问题;在C++和Java这样的强类型语言中不存在该问题。)如果是仅用于输入的参数,这种情况很少会带来问题;编译器在把参数传递给子程序之前,通常会将实际类型转换成形式类型。如果有问题的话,编译器通常会给出警告。但在某些情况下,特别是当所用的参数既用于输入也用于输出时,如果传错了参数类型,你就会遇上麻烦了。
请养成好的习惯,总要检查参数表中参数的类型,同时留意编译器给出的关于参数类型不匹配的警告。







