Nash - Scientific Computing with PCs (523165), страница 18
Текст из файла (страница 18)
They write their computer programs so that it is obvious what they are doing.Formalizing such arrangement or tidiness of programs has become a fashionable and at times lucrativeoccupation.Since there are many programs written by those who do not know what they are doing, the use of toolsand programming practices that force some order on the chaos is to be welcomed. However, themovement can have the same fervor as a temperance meeting. Our personal practice is to strive forwell-ordered programs that are easy to comprehend, without slavishly following the dictates of structure.For small experimental programs, we have sometimes produced some shockingly unstructured code, mostof which is soon discarded.Fundamentally, we wish to subdivide any program into parts, each of which has only one pathway "in"and one pathway "out". Clearly, any program can be organized artificially to have just one beginning andone end.
To be useful however, the structuring must divide the range of processes into sensible pieces sothat each can be considered by itself. Our recommendation:Divide the program into the largest pieces that can be conveniently considered by themselves.We wish to avoid any procedure that alters the state of several variables. Such a procedure may have toomany options or features, much like a fancy kitchen appliance or workshop tool. This is the wrong typeof structuring.At the other end of the spectrum, a frequent mistake of novice programmers who have just been taughtmacros or subroutines is to put each small loop or calculation in its own sub-program.
The resulting mainprogram becomes a difficult-to-read collection of CALLs. The difficulty experienced when one tries to readundocumented APL programs is closely related to this type of over-structuring; each operator performsseveral actions and looks like a CALL to a short subroutine. If the functions performed do not need to beconsidered at the detailed level, it may be better to ignore the individual operators and lump togetherseveral small routines into one.The issue of GOTO commands in any programming language has been hotly debated in the computerscience literature. Knuth (1974) presents a moderate viewpoint with wisdom and humor.
Clearly, it is agood idea to avoid jumping around all over a program, since this makes it difficult to break up into pieces6: PROGRAMMING47that can be conveniently considered one at a time. Nevertheless, there are occasions where a simple jumpover several lines of code is the most straightforward and understandable means to accomplish a task. Itcan sometimes be useful to be able to execute the same segment of program code in more than onesituation, yet not set up a separate procedure or function. A balance must then be struck between an easilyunderstood program and one that avoids unreasonable code duplication.
In our opinion the C languageconstructs break and continue, or their equivalents in other programming languages, can be astroublesome to understand as the GOTOs they implicitly replace.As a convenient set of programming rules, we try to keep in mind the following ideas:•Sections of code should not extend over more than one page in length.
This allows us to see all of onesection of code at once.•Each section of code should, if possible, have one entry point and one exit point. For multiplebranching (case statement in PASCAL, ON...GOTO... in BASIC, switch in C, computed GOTO inFORTRAN, or similar construct), the individual branches can be replaced by subroutine calls to makethe code more readable, but the calls should be documented to tell the reader what is done by eachof them.•Every section should have sufficient comments or remarks to explain its fundamental purpose.6.5Sub-programs - Data FlowIn most computer languages, including many of the command scripts for computational packages, thereis the provision for building sub-programs to carry out frequently-used and (hopefully) well-defined tasks.FORTRAN, in particular, probably owes much of its popularity to the relative ease with which users maycall subroutines to compute, for example, the normal (Gaussian) distribution probabilities, or to invert amatrix.The importance of the sub-program in any programming language is that it isolates the task to be doneand allows us to focus on doing the best job we can on that task alone, without concern for the largergoal.
In particular, we should not have to be concerned with the names or functions of particular variablesapart from those involved in the present calculations. The sub-program has its own local data structures(variables, arrays, etc.) so we do not have to worry about inadvertently changing some value in a way thathas perverse consequences for the whole of our program. Unfortunately, things are never that simple, andthe transfer of information between calling and called routines is a frequent source of errors. Setting upthe calls can involve much work.The simplest subroutine is the GOSUB . .
. RETURN structure in BASIC. However, users of "oldfashioned" BASIC must be very careful to recognize that the code invoked by GOSUB is really a part ofthe main program, with which it shares all the variables and arrays yet defined. While executing, thesubroutine may generate more variables, which are in turn grist for the mill of other parts of the program.This becomes particularly nasty when a subroutine is called from a loop. The subroutine code may alterthe looping parameter, with disastrous consequences. In some dialects, variables may be used forinput/output channel numbers. Changing such a variable to a nonexistent device number will usuallycause the system to "hang".Other languages may pose fewer dangers in this regard, but they are hardly immune to them.
We havemade the same mistake in FORTRAN at an early IBM OS/MVT site. The interesting outcome ofattempting to "read" a record from a line printer was that a 12 inch diameter bell of the fire-alarm varietyjust inside the front cabinet of the IBM /360 model 50 machine started ringing. It was an indication thatthe operating software — down to the hardwired monitor (like the BIOS on today’s PCs) — had detectedan unrecoverable error. Among the regular programmers at this site it became a matter of pride to "ringthe bell".48Copyright © 1984, 1994 J C & M M NashNash Information Services Inc., 1975 Bel Air Drive, Ottawa, ON K2C 0X1 CanadaSCIENTIFIC COMPUTING WITH PCsCopy for:Dr. Dobb’s JournalClearly, the operators of the system could have saved much aggravation if simple checks for valid callingarguments were built into the internal functions and subroutines of the FORTRAN compiler used.
Whenwriting sub-programs, we strongly urge readers to include such checks. While they require additionallines of code and may slow the overall execution of the program, the checks serve to document what weexpect as input to the sub-program. If execution time becomes an issue, the lines of code constitutingchecks can be commented out or bypassed with some sort of compile-time or run-time switch. We regardthe presence of such code as a form of documentation for all users. Once we can be assured that thecalling routing will never present unacceptable inputs, the checks can be set aside.The actual passing of data to and from sub-programs is another matter.
There are several mechanisms,each of which presents its own concerns.Argument lists for functions and procedures provide the most obvious mechanism for passing data.Suppose we want to compute the natural logarithm of a variable named X. Then we could writeY = ALOG(X)in FORTRAN. Similar statements can be used in other languages. Unfortunately, none provide a way tokeep control if X has a non-positive value, or even a value that is so small that the logarithm cannot becomputed within the available arithmetic. A better alternative would beY = OURLOG(X, FAILED)where FAILED is a Boolean (LOGICAL) variable that is TRUE whenever the logarithm cannot becomputed.
In such cases we may want to provide Y with some default value, which in IEEE arithmetic(IEEE, 1985) might be the NAN (Not-A-Number) value. An alternative is to use the NAN to signal thatthe log function has failed, but we need to be sure that this convention is followed universally within ourprograms if it is to have any value in protecting against errors.Notice that FAILED is now an argument passed from the called to the calling routine. Thus we introducethe issue of direction of flow of information. This is frequently a source of confusion and errors.
InPASCAL, variables whose values are passed back to the calling routine (they may also be passed into theroutine) must have the prefix var in the definition of the function or procedure. We have experiencedmore "bugs" than we would like to admit from omission of the var. Worse, the internal arrangements ofcompilers for argument transfer are such that some variables are called by value — their values copiedand, if passed back, copied back.