• No se han encontrado resultados

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.