541
Interfaces between routines are some of the most error-prone areas of a program.
542
One often-cited study by Basili and Perricone (1984) found that 39 percent of all
543
errors were internal interface errors—errors in communication between routines.
544
Here are a few guidelines for minimizing interface problems:
545
Put parameters in input-modify-output order
546
Instead of ordering parameters randomly or alphabetically, list the parameters
547
that are input-only first, input-and-output second, and output-only third. This
548
ordering implies the sequence of operations happening within the routine-
549
inputting data, changing it, and sending back a result. Here are examples of
550
parameter lists in Ada:
551
Ada Example of Parameters in Input-Modify-Output Order
552
procedure InvertMatrix(
553
originalMatrix: in Matrix;
554
resultMatrix: out Matrix
555 ); 556 ... 557 558 procedure ChangeSentenceCase( 559 desiredCase: in StringCase; 560
sentence: in out Sentence
561 ); 562 ... 563 564 procedure PrintPageNumber( 565 pageNumber: in Integer; 566
status: out StatusType
567
);
568
This ordering convention conflicts with the C-library convention of putting the
569
modified parameter first. The input-modify-output convention makes more sense
570
to me, but if you consistently order parameters in some way, you still do the
571
readers of your code a service.
572
HARD DATA
CROSS-REFERENCE For
details on documenting routine parameters, see “Commenting Routines” in Section 32.5. For details on formatting parameters, see Section 31.7, “Laying Out Routines.”
Ada uses in and out keywords to make input and output parameters clear.
Create your own in and out keywords
573
Other modern languages don’t support the in and out keywords like Ada does. In 574
those languages, you might still be able to use the preprocessor to create your
575
own in and out keywords. Here’s how that could be done in C++: 576
C++ Example of Defining Your Own In and Out Keywords
577 #define IN 578 #define OUT 579 580 void InvertMatrix( 581 IN Matrix originalMatrix, 582
OUT Matrix *resultMatrix
583 ); 584 ... 585 586 void ChangeSentenceCase( 587 IN StringCase desiredCase, 588
IN OUT Sentence *sentenceToEdit
589 ); 590 ... 591 592 void PrintPageNumber( 593 IN int pageNumber, 594
OUT StatusType &status
595
);
596
In this case, the IN and OUT macro-keywords are used for documentation 597
purposes. To make the value of a parameter changeable by the called routine, the
598
parameter still needs to be passed as a pointer or as a reference parameter.
599
If several routines use similar parameters, put the similar parameters in a
600
consistent order
601
The order of routine parameters can be a mnemonic, and inconsistent order can
602
make parameters hard to remember, For example, in C, the fprintf() routine is the 603
same as the printf() routine except that it adds a file as the first argument. A 604
similar routine, fputs(), is the same as puts() except that it adds a file as the last 605
argument. This is an aggravating, pointless difference that makes the parameters
606
of these routines harder to remember than they need to be.
607
On the other hand, the routine strncpy() in C takes the arguments target string, 608
source string, and maximum number of bytes, in that order, and the routine
609
memcpy() takes the same arguments in the same order. The similarity between 610
the two routines helps in remembering the parameters in either routine.
In Microsoft Windows programming, most of the Windows routines take a
612
“handle” as their first parameter. The convention is easy to remember and makes
613
each routine’s argument list easier to remember.
614
Use all the parameters
615
If you pass a parameter to a routine, use it. If you aren’t using it, remove the
616
parameter from the routine interface. Unused parameters are correlated with an
617
increased error rate. In one study, 46 percent of routines with no unused
618
variables had no errors. Only 17 to 29 percent of routines with more than one
619
unreferenced variable had no errors (Card, Church, and Agresti 1986).
620
This rule to remove unused parameters has two exceptions. First, if you’re using
621
function pointers in C++, you’ll have several routines with identical parameter
622
lists. Some of the routines might not use all the parameters. That’s OK. Second,
623
if you’re compiling part of your program conditionally, you might compile out
624
parts of a routine that use a certain parameter. Be nervous about this practice, but
625
if you’re convinced it works, that’s OK too. In general, if you have a good
626
reason not to use a parameter, go ahead and leave it in place. If you don’t have a
627
good reason, make the effort to clean up the code.
628
Put status or error variables last
629
By convention, status variables and variables that indicate an error has occurred
630
go last in the parameter list. They are incidental to the main purpose of the
631
routine, and they are output-only parameters, so it’s a sensible convention.
632
Don’t use routine parameters as working variables
633
It’s dangerous to use the parameters passed to a routine as working variables.
634
Use local variables instead. For example, in the Java fragment below, the
635
variable InputVal is improperly used to store intermediate results of a 636
computation.
637
Java Example of Improper Use of Input Parameters
638
int Sample( int inputVal ) {
639
inputVal = inputVal * CurrentMultiplier( inputVal );
640
inputVal = inputVal + CurrentAdder( inputVal );
641 ... 642 return inputVal; 643 } 644
inputVal in this code fragment is misleading because by the time execution 645
reaches the last line, inputVal no longer contains the input value; it contains a 646
computed value based in part on the input value, and it is therefore misnamed. If
647
you later need to modify the routine to use the original input value in some other
648
place, you’ll probably use inputVal and assume that it contains the original input 649
value when it actually doesn’t.
650
HARD DATA
At this point, inputVal no longer contains the value that was input.
How do you solve the problem? Can you solve it by renaming inputVal? 651
Probably not. You could name it something like workingVal, but that’s an 652
incomplete solution because the name fails to indicate that the variable’s original
653
value comes from outside the routine. You could name it something ridiculous
654
like InputValThatBecomesWorkingVal or give up completely and name it X or 655
Val, but all these approaches are weak. 656
A better approach is to avoid current and future problems by using working
657
variables explicitly. The following code fragment demonstrates the technique:
658
Java Example of Good Use of Input Parameters
659
int Sample( int inputVal ) {
660
int workingVal = inputVal;
661
workingVal = workingVal * CurrentMultiplier( workingVal );
662
workingVal = workingVal + CurrentAdder( workingVal );
663 ... 664 665 ... 666 return workingVal; 667 } 668
Introducing the new variable workingVal clarifies the role of inputVal and 669
eliminates the chance of erroneously using inputVal at the wrong time. (Don’t 670
take this reasoning as a justification for literally naming a variable workingVal. 671
In general, workingVal is a terrible name for a variable, and the name is used in 672
this example only to make the variable’s role clear.)
673
Assigning the input value to a working variable emphasizes where the value
674
comes from. It eliminates the possibility that a variable from the parameter list
675
will be modified accidentally. In C++, this practice can be enforced by the
676
compiler using the keyword const. If you designate a parameter as const, you’re 677
not allowed to modify its value within a routine.
678
Document interface assumptions about parameters
679
If you assume the data being passed to your routine has certain characteristics,
680
document the assumptions as you make them. It’s not a waste of effort to
681
document your assumptions both in the routine itself and in the place where the
682
routine is called. Don’t wait until you’ve written the routine to go back and write
683
the comments—you won’t remember all your assumptions. Even better than
684
commenting your assumptions, use assertions to put them into code.
685
What kinds of interface assumptions about parameters should you document?
686
● Whether parameters are input-only, modified, or output-only
687
● Units of numeric parameters (inches, feet, meters, and so on)
688
If you need to use the original value of inputVal here or somewhere else, it’s still available.
CROSS-REFERENCE For
details on interface assumptions, see the introduction to Chapter 8, “Defensive Programming.” For details on documentation, see Chapter 32, “Self- Documenting Code.”
● Meanings of status codes and error values if enumerated types aren’t used
689
● Ranges of expected values
690
● Specific values that should never appear
691
Limit the number of a routine’s parameters to about seven
692
Seven is a magic number for people’s comprehension. Psychological research
693
has found that people generally cannot keep track of more than about seven
694
chunks of information at once (Miller 1956). This discovery has been applied to
695
an enormous number of disciplines, and it seems safe to conjecture that most
696
people can’t keep track of more than about seven routine parameters at once.
697
In practice, how much you can limit the number of parameters depends on how
698
your language handles complex data types. If you program in a modern language
699
that supports structured data, you can pass a composite data type containing 13
700
fields and think of it as one mental “chunk” of data. If you program in a more
701
primitive language, you might need to pass all 13 fields individually.
702
If you find yourself consistently passing more than a few arguments, the
703
coupling among your routines is too tight. Design the routine or group of
704
routines to reduce the coupling. If you are passing the same data to many
705
different routines, group the routines into a class and treat the frequently used
706
data as class data.
707
Consider an input, modify, and output naming convention for parameters
708
If you find that it’s important to distinguish among input, modify, and output
709
parameters, establish a naming convention that identifies them. You could prefix
710
them with i_, m_, and o_. If you’re feeling verbose, you could prefix them with 711
Input_, Modify_, and Output_. 712
Pass the variables or objects that the routine needs to maintain its interface
713
abstraction
714
There are two competing schools of thought about how to pass parameters from
715
an object to a routine. Suppose you have an object that exposes data through 10
716
access routines, and the called routine needs 3 of those data elements to do its
717
job.
718
Proponents of the first school of thought argue that only the 3 specific elements
719
needed by the routine should be passed. They argue that that will keep the
720
connections between routines to a minimum, reduce coupling, and make them
721
easier to understand, easier to reuse, and so on. They say that passing the whole
722
object to a routine violates the principle of encapsulation by potentially exposing
723
all 10 access routines to the routine that’s called.
724
HARD DATA
CROSS-REFERENCE For
details on how to think about interfaces, see “Good Abstraction” in Section 6.2.
Proponents of the second school argue that the whole object should be passed.
725
They argue that the interface can remain more stable if the called routine has the
726
flexibility to use additional members of the object without changing the routine’s
727
interface. They argue that passing 3 specific elements violates encapsulation by
728
exposing which specific data elements the routine is using.
729
I think both these rules are simplistic and miss the most important consideration,
730
which is, what abstraction is presented by the routine’s interface? 731
● If the abstraction is that the routine expects you to have 3 specific data
732
elements, and it is only a coincidence that those 3 elements happen to be
733
provided by the same object, then you should pass the 3 specific data
734
elements individually.
735
● If the abstraction is that you will always have that particular object in hand
736
and the routine will do something or other with that object, then you truly do
737
break the abstraction when you expose the three specific data elements.
738
If you’re passing the whole object and you find yourself creating the object,
739
populating it with the 3 elements needed by the called routine, and then pulling
740
those elements out of the object after the routine is called, that’s an indication
741
that you should be passing the 3 specific elements rather than the whole object.
742
(Generally code that “sets up” for a call to a routine or “takes down” after a call
743
to a routine is an indication that the routine is not well designed.)
744
If you find yourself frequently changing the parameter list to the routine, with
745
the parameters coming from the same object each time, that’s an indication that
746
you should be passing the whole object rather than specific elements.
747
Used named parameters
748
In some languages, you can explicitly associate formal parameters with actual
749
parameters. This makes parameter usage more self-documenting and helps avoid
750
errors from mismatching parameters. Here’s an example in Visual Basic:
751
Visual Basic Example of Explicitly Identifying Parameters
752
Private Function Distance3d( _
753
ByVal xDistance As Coordinate, _
754
ByVal yDistance As Coordinate, _
755
ByVal zDistance As Coordinate _
756 ) 757 ... 758 End Function 759 ... 760
Private Function Velocity( _
761
ByVal latitude as Coordinate, _
762
Here’s where the formal parameters are declared.
ByVal longitude as Coordinate, _
763
ByVal elevation as Coordinate _
764
)
765
...
766
Distance = Distance3d( xDistance := latitude, yDistance := longitude, _
767 zDistance := elevation ) 768 ... 769 End Function 770
This technique is especially useful when you have longer-than-average lists of
771
identically typed arguments, which increases the chances that you can insert a
772
parameter mismatch without the compiler detecting it. Explicitly associating
773
parameters may be overkill in many environments, but in safety-critical or other
774
high-reliability environments the extra assurance that parameters match up the
775
way you expect can be worthwhile.
776
Don’t assume anything about the parameter-passing mechanism
777
Some hard-core nanosecond scrapers worry about the overhead associated with
778
passing parameters and bypass the high-level language’s parameter-passing
779
mechanism. This is dangerous and makes code nonportable. Parameters are
780
commonly passed on a system stack, but that’s hardly the only parameter-
781
passing mechanism that languages use. Even with stack-based mechanisms, the
782
parameters themselves can be passed in different orders and each parameter’s
783
bytes can be ordered differently. If you fiddle with parameters directly, you
784
virtually guarantee that your program won’t run on a different machine.
785
Make sure actual parameters match formal parameters
786
Formal parameters, also known as dummy parameters, are the variables declared
787
in a routine definition. Actual parameters are the variables or constants used in
788
the actual routine calls.
789
A common mistake is to put the wrong type of variable in a routine call—for
790
example, using an integer when a floating point is needed. (This is a problem
791
only in weakly typed languages like C when you’re not using full compiler
792
warnings. Strongly typed languages such as C++ and Java don’t have this
793
problem.) When arguments are input only, this is seldom a problem; usually the
794
compiler converts the actual type to the formal type before passing it to the
795
routine. If it is a problem, usually your compiler gives you a warning. But in
796
some cases, particularly when the argument is used for both input and output,
797
you can get stung by passing the wrong type of argument.
798
Develop the habit of checking types of arguments in parameter lists and heeding
799
compiler warnings about mismatched parameter types.
800
Here’s where the actual parameters are mapped to the formal parameters.