No puede seleccionar más de 25 temas Los temas deben comenzar con una letra o número, pueden incluir guiones ('-') y pueden tener hasta 35 caracteres de largo.

34KB

The Psychology of Computer Programming — Chapter-02: What Makes a Good Program?

  • Source: /library/The Psychology of Computer Programming/source-file.pdf
  • PDF pages: 43–56
  • Pages without text: none
  • Note: Includes the Silver Anniversary comments that immediately follow the original chapter. Text comes from the scan's OCR layer and may contain recognition errors.

PROGRAM? f we plan to study programming as a human activity, we are going to have to develop some measures of programming performance. That is, we are going to have some idea of what we mean when we Say that one programmer is better than another, or one program is better than another. Although we all have opinions on these questions, we shall find that the answers are not as simple as we might wish. For programming is not just human behavior; it is complex human behavior. Consider our programming example once again. By analyzing Figure 1-1 rather thoroughly, we discover that only a small portion of it—no more than 20 percent—is directed at meeting the problem specifica- tions. Of the other 80 percent, much, if not most, can be eliminated not by physical change but by psychological change. In fact, even the ques- tion of a limited storage could have been dealt with by an appropriate virtual machine, so that even machine limitations can be overcome by proper psychological design. 15

16 Programming as Human Performance The program in Figure 1-1, though small, is typical in the percentage of coding that is actually devoted to solving the given problem. Because people do not read programs, few are aware of how common this situa- tion is. But common it is, and it gives us much scope for improvement in our programming. Still, there is more to the question of good program- ming than measuring the percentage of extraneous—or at least peripheral —code. Indeed, we cannot really measure a program by itself and say whether it is good or bad. Most programmers feel that there is such a thing as good programming. Although this feeling may be valid, it does not follow that there is such a thing as a good program. Not, at least, in the sense that we can study the program out of the context in which it was developed and in which it will be used and declare it to have a goodness of 83.72 percent. Consider Figure 1-1. Is it a good program? It is a clumsy program, perhaps, but that is only part of the mark. After all, we only knew it was clumsy because we read it, and for the ordinary program, nobody is ever going to read it, so grace is hardly an absolute measure of program goodness. Is it an efficient program? We can hardly answer this question without knowing the machine, the compiler, and the cost situation under which it will be run. Was it done on time? What did it cost? Does it meet specifications? We cannot know these things merely by looking at the code. In our studies of the psychology of programming, we shall be hampered by our inability to measure the goodness of programs on an absolute scale. But can we perhaps measure them on a relative scale—can we say that program A is better or worse than program B? Unfortunately, we will generally not even be able to do that, for several reasons. First of all, when is there ever another program with which to compare? And when there is, as in the case of, say, a FORTRAN compiler, how can we agree on the relative importance of the different attributes of the program. Is a compiler that quickly compiles slow code better than one that slowly compiles quick code? But still, aren't there times when we can make a relative ranking? Isn’t a compiler that quickly compiles quick code better than one that Slowly compiles slow code? We have to admit that sometimes such com- parisons can be made—although we must not be too hasty in making them. There are dimensions of compiler performance other than speed. There are diagnostics, scope of language covered, reliability of object code, and execution time monitoring, to mention a few. Only rarely will we find two programs so sufficiently similar that they can be compared point by point—and then find one superior on all points. More often, then, we will be doing evaluation of programs not with respect to one another but with respect to a situation—a total situation

What Makes a Good Program? 17 —in which they are developed. Looking honestly at the situation, we are never looking for the best program, seldom looking for a good one, but always looking for one that meets the requirements. SPECIFICATIONS Of all the requirements that we might place on a program, first and foremost is that it be correct. in other words, it should give the correct outputs for each possible input. This is what we mean when we Say that a program ‘‘works,”’ and it is often and truly said that “any program that works is better than any program that doesn’t.” An example may serve to drive home this point to those whose minds. are tangled in questions of efficiency and other secondary matters. A programmer was once called to Detroit to aid in the debugging of a new program—one that was to determine the parts requirements to build a certain set of automobiles. The input to the program was a deck of cards, each card representing a purchase order for an automobile, with differ- ent punches representing the different options selected by the customer. The program embodied the specifications relating the various options to the parts that would be needed. For instance, the choice of upholstery for the rear seat might be determined by such factors as body color, body style, options for deluxe or leatherette upholstery, and whether or not the car was air conditioned. The air-conditioning option is a good example of the basic complexity of the problem, for though to an un- trained eye the choice of air conditioning might have no connection with the choice of rear seat upholstery, it might very well require spaces for extra ducts. In general, then, each option might have some effect on the choice of parts made, so the determination of parts requirements was an excellent job for the computer. Unfortunately, when this programmer arrived on the scene, the basic approach to the problem had long been settled—and settled badly. Each option—as it affected each choice—was reflected as an_ individually programmed test and branch in the program. In a way, the program was an enormous tree, with more than 5000 branches, representing the de- cisions leading to part selection. Cast in this form—and with 16 program- mers working at the same time—it was impossible to debug, as each and every case had to be tested separately. To test the program, a particular card would be put in and the output would be observed. When our programmer arrived, things were so bad that typical cards were calling for the production of cars with eight tires, no engine, and three sets of upholstery. In short, a disaster. As is usual with programming disasters, nobody recognized it as such.

18 Programming as Human Performance Instead, the whole crew had gone on double shift to get out the bugs, and new programmers, including our hero, were brought in from all over the country. Naturally, this led to worse confusion than ever, and our pro- grammer, after a few days, determined that it was hopeless business— and in any case not reason enough to be away from his family and working night and day. He was roundly condemned for his uncooperative attitude but was allowed to leave. While on the plane, he had his first opportunity in a week to reflect calmly. He immediately saw the error in the approach and perceived that a much better approach would be to divide the work into two phases. The main operational program would simply loop through a set of spe- cially constructed specifications tables, so that all decisions would be made with a single test reapplied to different parts of the table. In that way, the program was at least assured to produce the right number of tires, engines, and so forth. The tables themselves would be compiled from input written in essentially the form of the engineering specifications. This would allow the engineering personnel, rather than the programmers, to check the specifications, and also permit one part of the specification to be changed without changing all parts further down a decision tree. By the time he got off the plane, he had coded the two programs. It was a day’s work to check them out, and another two days’ work with the local assembly plant engineers to create the specifications in input form. After a week’s testing in the plant, he was about to return to notify De- troit of the news when he got a telegram saying that the project had been cancelled—since the program was impossible to write. After a quick call and a plane trip, he was back in Detroit with his version of the program. A demonstration to the executives convinced them that the project could continue, and then he was asked to make a presentation to the rest of the programmers. Naturally, they were a rather cool audience—a phenomenon to which we shall return in our discussions —but they sat quietly enough through his explanation of the method. Even at the end, there was a lack of questioning—until the original creator of the old system raised his hand. “And how long does your program take?” he asked—emphasizing the possessive. “That varies with the input,” was the reply, ‘but on the average, about ten seconds per card.”’ “Aha,” was the triumphant reply. “But my program takes only one second per card.” The members of the audience—who had, after all, all contributed to the one-second version—seemed relieved. But our hero, who was rather young and naive, was not put down by this remark. Instead, he calmly observed, “But your program doesn’t work. If the program doesn’t have

What Makes a Good Program? 19 to work, | can write one that takes one millisecond per card—and that’s faster than our card reader.”’ This observation—though it undoubtedly failed to win our hero any friends—contains the fundamental truth upon which all programming evaluation must be based. If a program doesn’t work, measures of effi- ciency, of adaptability, or of cost of production have no meaning. Still, we must be realistic and acknowledge that probably no perfect program was ever written. Every really large and significant program has “just one more bug.” Thus, there are degrees of meeting specifications—of ‘‘work- ing’ —and evaluation of programs must take the type of imperfection into account. Any compiler, for example, is going to have at least “‘pathological’’ pro- grams which it will not compile correctly. What is pathological, however, depends to some extent on your point of view. If it happens in your pro- gram, you hardly classify it as pathological, even though thousands of other users have never encountered the bug. The producer of the com- piler, however, must make some evaluation of the errors on the basis of the number of users who encounter them and how much cost they incur. This is not always done scientifically. Indeed, it often amounts to an evaluation of who shouts the loudest, or who writes to the highest execu- tive. But whatever system is chosen, some bugs will remain, and some people will be unhappy with the same compiler that satisfies thousands. In effect, then, there is a difference between a program written for one user and a piece of ‘‘software.’’ When there are multiple users, there are multiple specifications. When there are multiple specifications, there are multiple definitions of when the program is working. In our discussions of programming practices, we are going to have to take into account the difference between programs developed for one user and programs de- veloped for many. They will be evaluated differently, and they should be produced by different methods. SCHEDULE Even after questions of meeting specifications have been set aside, the question of efficiency is still not uppermost. One of the recurring problems in programming is meeting schedules, and a program that is late is often worthless. At the very least, we have to measure the costs of not having the program against any potential savings that a more efficient program would produce. In one noteworthy case, the customer of a software firm estimated that the linear programming code being developed would save more than one million dollars per month in the company’s oil refining operations. Even one month’s delay in schedule would result in a loss that

20 Programming as Human Performance could not be recovered if the program were run free of charge for ten years. Losses from late delivery of programs are not always so severe, but even were the costs negligible, there seems to be, in the United States, at least, an incredible amount of annoyance when schedules are not met. In fact, the average programming manager would prefer that a project be estimated at twelve months and take twelve than that the same project be estimated at six months and take nine. This is an area where some psychological study could be rewarding, but there are indications from other situations that it is not the mean length of estimated time that annoys people but, rather, the standard deviation in the actual time taken. Thus, most people would prefer to wait a fixed ten minutes for the bus each morning than to wait one minute on four days and twenty-six minutes once a week. Even though the average wait is six minutes in the second case, the derangement caused by one long and unexpected delay more than compensates for this disadvantage. If this observation holds true for programming, then any studies of the effect of certain programming practices should measure the effect on variability in production time—not just mean time as most studies cur- rently do. We shall try to estimate this effect in all our subsequent dis- cussions. ADAPTABILITY Having disposed of the questions of meeting specifications and sched- ules, we might suppose that we had finally arrived at the question of efficiency as a measure of the goodness of programs. Without begging the question of relative importance of the two factors, however, it will prove advantageous first to dispose of the factor of adaptability of a program. No doubt there are programs that are used once and then thrown away. No doubt there are even more programs that should be thrown away be- fore ever being used. Nonetheless, the great majority of programs that are written, especially by professional programmers, remain in existence for a definite life span. And during that span, most of them become modified. Few programmers of any experience would contradict the assertion that most programs are modified in their lifetime. Why, then, when we are forced to modify programs do we find it such a Herculean task that we often decide to throw them away and start over? Reading programs gives us some insight, for we rarely find a program that contains any evidence of having been written with an eye to subsequent modification. But this is only a symptom, not a disease. Why, we should ask, do pro- grammers, who know full well that programs will inevitably be modified,

What Makes a Good Program? 21 write programs with no thought to such modifications? Why, indeed, do their programs sometimes look as if they had been devilishly contrived to resist modification—protected like the Pharoahs’ tomb against all in- truders? There are answers to these questions, and they lie in the direction of our psychological investigations. But our job for the moment is not to answer them, but merely to raise them and remind ourselves of their importance in any discussion of what makes good programming. A related question is, of course, documentation, for why do we document a program if not to render it more easily modifiable? We would all agree that the quality of documentation and the ease of making modifi- cations, both planned and unplanned, should count heavily in any grad- ing of a program, or of the programmer who writes that program. We would all agree further, then, that any improvement here to be gained from psychological investigations will be well worth the effort. But, a word of caution before we proceed to the question of efficiency. Adaptability is not free. Sometimes, to be sure, we get a program that happens to be adaptable as well as satisfactory in all other ways, but we generally pay for what we get—and often fail to get what we pay for. In his mathematical studies of genetic systems, R. A. Fisher derived a law that has often been called Fisher's Fundamental Theorem, a very signi- ficant name in view of the importance of Fisher’s many other theorems. Fisher’s Fundamental Theorem states—in terms appropriate to the present context—that the better adapted a system is to a particular environment, the less adaptable it is to new environments. By stretching our imagina- tion a bit, we can see how this might apply to computer programs as well as to snails, fruit flies, and tortoises. For a program to be efficient, it must take advantage of the peculiarities of the problem and the machine on which it is to be run. Programs that ignore the structure of the machine do so at the risk of incurring heavy cost penalties, and programmers who can exploit special situations in a problem are the ones who can speed things up and make them smaller. One peculiar example, not entirely untypical, of this type of programming was an assembly program that employed a special technique for looking up operation codts to be translated. The programmer had observed—or figured out—that there was one number which, when used to multiply the operation code bit pattern interpreted as numeric, yielded an almost solid and completely unduplicated list of addresses. Using this multiplier, he was able to effect a compact and equally fast transformation from sym- bolic codes to machine language. The programmer was much congratulated for this ingenuity—the ex- ample was displayed to several generations of programmers as a proto- type of the kind of work they should be doing. Sadly, however, this

22 Programming as Human Performance section of code did not have a very long life, for, as so frequently happens, the operation code repertoire of the machine was expanded. When a dozen or so codes had been added to the set, the old multiplier no longer had the same desirable properties of compactness and uniqueness. In fact, no multiplier could be found that would work in this way for the new codes. The outcome of attempts to find a new multiplier was to abandon this special technique for a more general hash table search—one that allowed for the existence of duplicates. Unhappily, the old technique was not abandoned before a great deal of effort had been expended on the attempt to adapt it to the changed circumstances. This proved to be time wasted. In retrospect, the savings in time of the specialized technique never paid in its lifetime for the extra cost of fooling around when the new codes were added. If a general hash table method had been used in the first place, somewhat lower efficiency would have been the result; but—and this is the “but” that makes the example—the modification would have been entirely trivial. In fact, essentially no modification would have been required except the addition of the new codes to the appro- priate places in the table. This example could be supplemented with hundreds of others, but the point would be the same. When we ask for efficiency, we are often asking for “‘tight’’ coding that will be difficult to modify. In terms of higher-level languages, we often descend to the machine-language level to make a program more efficient. This loses at least one of the benefits of having the program in the higher-level language—that of transportability between machines. In fact, it has the effect of freezing us to a machine or imple- mentation that we have admitted by our very act is unsatisfactory. However, the same managers who scream for efficiency are the ones who tear their hair when told the cost of modifications. Conversely, the managers who ask for generalized and easily modified programs are wont to complain when they find out how slow and spacious these programs turn out to be. We must be adult about such matters: neither psychology nor magic is going to help us to achieve contradictory goals simultane- ously. Asking for efficiency and adaptability in the same program is like asking for a beautiful and modest wife. Although beauty and modesty have been known to occur in the same woman, we’ll probably have to settle for one or the other. At least that’s better than neither. EFFICIENCY Measuring the true efficiency of a program is not the simple task it first seems. In the first place, efficiency is not always measured simply in terms of time on the computer, for it is often possible to trade time on

What Makes a Good Program? 23 the computer for time before and after the computer run. For example, a commonly used measure of the speed of a compiler is the number of cards per minute it processes. If we use this measure to compare an assembly program with, say, a FORTRAN compiler, the assembly is likely to appear more efficient, since each input card carries less information. Sometimes compilers are compared on the basis of the number of ma- chine instructions produced per minute, but this measure exposes us to the danger of giving credit to a compiler simply because it is verbose in the production of object code. A compiler, of course, is a special kind of beast anyway, since we must measure the efficiency of the code produced as well as that of the compilation process. Still, since compiler efficiency is a subject of much discussion, it may be worthwhile to pursue this example to illustrate how easily we can be misled on efficiency questions. Consider, for instance, the comparison of two compilers for the same language in terms of the “cards per minute’”’ measure. The first pitfall here lies in the term “same language.”’ Very few compilers handle exactly the same language, so if there is any difference between the two, we will have to adjust the efficiency obtained from cards per minute to account for the difference in value of the two languages. The effect of slight differences in source language on compiler effi- ciency can be striking. Typically, if the compiler writer can choose 10 percent of the language which he will not implement, he can produce a 50 percent faster compiler. Unfortunately, which 10 percent is chosen differs from one machine and one compiling technique to the other, so that language designers cannot simply provide “more efficient’ subsets to be implemented. Similar improvements in efficiency are possible from similar relaxation of specifications in other areas. Indeed, if our primary concern in a par- ticular application is efficiency, the first step should always be to look for areas in which the specifications can be changed to suit computer efficiency rather than user convenience. Of course, computer time is not the only cost element in a typical project. People time also costs money. Thus, we can leave certain crossfootings to be done by the recipient of a report, but we may only be saving computer time at the expense of executive time with paper and pencil. We may decide quite rationally to make this choice, but more often the choice is made without the execu- tive having anything to say about it. Trimming the external specifications can even have the opposite of the intended effect on computer time used. If, for example, a great deal of manual handling of input has been left in the system to simplify the com- puter run, we may find ourselves paying more in reruns than we saved in single-run efficiency. This effect is rather common when comparing two compilers which, even though they handle identical language, differ

24 Programming as Human Performance in the diagnostics and execution monitoring they produce. If we save 20 percent in raw compile time by eliminating or trimming error checking, we had better be sure that the user will not find himself making 40 percent more runs to get the same amount of information. As hazardous as it is to make simple measures of efficiency in a single- machine, simple-scheduler environment, it is child’s play compared with the difficulties of obtaining meaningful efficiency estimates in a multi- processing or multiprogramming environment. Is it better, for instance, to occupy 40K bytes of main storage for one hour or 80K bytes for 30 minutes? The answer to this question depends on the situation in the computer at the time the job is run—and may change each time the job is run. For example, one day it may be possible to add a 40K job to the machine load without affecting the performance of the other jobs by one second; yet the next day the same 40K job may lock out another job for the full hour. Running on systems capable of dynamic load adjustment may alleviate some of the instabilities in multiprogram performance that we often see today. It may very well be that, as with schedules for programming, what the user wants is not a small mean time to execute his program but a small standard deviation, so he can plan his work reasonably. If this is true, the entire schedule from origin to destination of the work must be considered, not just the “time on the computer.” We may be moving toward a computing environment in which the best programs are those that retain the ability to be run in various sizes of main memory, on various machine configurations. Such a program may not be very efficient on any one of the configurations—considering Fisher’s Fundamental Theorem applied in the inverse sense—but may lead to more consistent turnaround performance because it will never find itself waiting for just the correct slot in the day’s schedule. Machines with virtual memories give us this kind of flexibility without special pro- gramming, although there are indications that performance improvements can be obtained even in this environment by taking special character- istics into account. For instance, if we know the page size of our system, we can avoid excessive paging by tailoring our program sections to fit into single pages and not to cross page boundaries except at logical breaks in the program. If we do this, however, the program becomes somewhat less well adapted for running in a system with a different page size. Similarly, we can choose an algorithm that will require no more and no less page Space than is commonly allowed by our system; but again, this kind of program will fail to take advantage of extra page space and will give the worst possible performance in a system with a smaller page space. What all of this means is that efficiency is becoming a clouded issue

What Makes a Good Program? 25 in computing. Moreover, with cost per unit of computation decreasing every year and cost per unit of programming increasing, we have long since passed the point where the typical installation spends more money on programming than it does on production work. This imbalance is even more striking when all the work improperly classified as ‘‘pro- duction” is put under the proper heading of “debugging.” But by any measure, the imbalance exists. And grows. And so we expect that with each passing year we will hear less and less about efficiency—and more and more about effectiveness. SUMMARY The question of what makes a good program is not a simple one, and may not even be a proper question. Each program has to be con- sidered on its own merits and in relation to its own surroundings. Some of the important factors are:

  1. Does the program meet specifications? Or, rather, how well does it meet specifications?
  2. Is it produced on schedule, and what is the variability in the schedule that we can expect from particular approaches?
  3. Will it be possible to change the program when conditions change? How much will it cost to make the change?
  4. How efficient is the program, and what do we mean by efficiency? Are we trading efficiency in one area for inefficiency in another? In the future, and particularly in the discussion of this book, we should refrain from using the concept “good program” or ‘‘good programmer’’ as if it were something universally agreed upon, or something that even can be universally agreed upon, or something that even should be uni- versally agreed upon. QUESTIONS For Managers
  5. On what bases do you reward programmers? Are certain of your criteria mutually contradictory, as in asking for efficient but general programs? How explicit are you with your programmers in indicating -what you are looking for in their programs? Or do you just tell them that you want the programs to be fast, small, neat, easily modifiable, errorless, and done in a week?
  6. How do the programs in your installation stack up on adaptability? Is

26 Programming as Human Performance modifying programs a major expense in your installation? If so, can you see things in your programming practices—in the original goals you set—that may lead to this kind of expense? 3. How important is making the schedule in your shop? Is “a miss as good as a mile,’”’ or do you reward for consistency rather than the occasional lucky shot? Can you see how a programmer can choose an unreliable practice because it gives him the only hope of making the schedule, even though it may risk not finishing the program at all? For Programmers

  1. Do you have criteria explicitly in mind when you start on a project? Are these criteria obtained from your own impression of what is im- portant, or do you get them from your manager? Do your criteria change as the project progresses, or do you have some device for keeping them firmly in mind?
  2. How many times have you ever thought, when writing a program, of some person in the future who might modify it? How many times have you ever cursed someone whose program you were modifying?
  3. Have you ever tried for ‘‘efficiency” at the expense of getting the job done? For “meeting the deadline’’ at the expense of doing it right? BIBLIOGRAPHY Fisher, Ronald A., The Genetical Theory of Natural Selection, New York, Dover Publications, 1958. See particularly Chapter II for a discussion of the Fundamental Theorem of Na- tural Selection. Weinberg, G. M., PL// Programming, A Manual of Style, New York, McGraw-Hill,

See especially Chapter 4 on the question of the compromises among the various criteria for ‘‘good”’ programs.

Silver Anniversary Notes (27 Comments on Chapter 2: What Makes a Good Program? The biggest new factor in determining how we judge the quality of code is the economic factor. A program that sells, and makes profits, is obviously better than one that doesn’t. At least several non-programmers have told me that Bill Gates is the world’s greatest programmer. In my career as a programmer, most of the operating system code | wrote was given away, so naturally I’m suspicious of this financial measure. Bult, in all honesty, if the purpose of writing the pro- gram is to make money, then the one whose program makes the most money is certainly the best programmer—the best at realizing the requirements. In the original chapter’s story of efficiency versus effectiveness, the card-read- ing activity certainly dates the example, but the point is the same regardless of what input medium is used. Over the years, I’ve arrived at a definition of quality that generalizes this case: Quality is value to some person. To the programmers of that era, speed was a more important measure of value than was confor- mance to function. Studying the psychology of value judgments explains why software developers made the choices they did, puzzles that would otherwise simply be ancient mysteries. The passing years have validated the importance of variance in managing for quality. We are doing much better in the mean, but the variance remains, or has even grown, and customers are still dissatisfied. Ten years ago, | would have been embarrassed by the sexist language used so unconsciously in the seventies (“beautiful and modest wife,” and “him” and “his” where “her” and “hers” would have done fine [p. 22]), because | had

PS Silver Anniversary Notes become well aware of the presumption of this usage and had worked hard to root it out of my writing. Reading the original today, | simply find this change in language an interesting measure of my growing up, and hopefully of society’s growing up. Twenty-five years ago, few of us were aware of what assumptions we were making with our sexist writing. Many managers still seem to want everything, and fail to understand the importance of asking for intelligent trade-offs in order to get the best product they can. It’s as if they think all you have to do to manage the New York Yankees is tell every batter to hit a home run and every pitcher to strike the batter out. The trade-off between computer time and human time has changed, as predict- ed. “And so we expect that with each passing year we will hear less and less about efficiency—and more and more about effectiveness” (p. 25). Nevertheless, there are still jobs where efficiency is essential, just not all. We are learning this. For the most part, I’m astonished at how well the Questions sections at the end of the chapters stand up to the test of time—with some hilarious exceptions. The same is true for the bibliographic references, though not the computer-spe- cific ones. It seems that there is some consistency to human behavior, if not to human technology.

Powered by TurnKey Linux.