No Silver Bullet - Essence And Accidents of Software Engineering
英文原文
作者Frederick P. Brooks, Jr.
University of North Carolina at Chapel Hill
There is no single development, in either technology or management technique, which by itself promises even one order-of-magnitude improvement within a decade in productivity, in reliability, in simplicity.
*Abstract*
All software construction involves essential tasks, the fashioning of the complex conceptual structures that compose the abstract software entity, and accidental tasks, the representation of these abstract entities in programming languages and the mapping of these onto machine languages within space and speed constraints. Most of the big past gains in software productivity have come from removing artificial barriers that have made the accidental tasks inordinately hard, such as severe hardware constraints, awkward programming languages, lack of machine time. How much of what software engineers now do is still devoted to the accidental, as opposed to the essential? Unless it is more than 9/10 of all effort, shrinking all the accidental activities to zero time will not give an order of magnitude improvement.
Therefore it appears that the time has come to address the essential parts of the software task, those concerned with fashioning abstract conceptual structures of great complexity. I suggest:
- Exploiting the mass market to avoid constructing what can be bought.
- Using rapid prototyping as part of a planned iteration in establishing software requirements.
- Growing software organically, adding more and more function to systems as they are run, used, and tested.
- Identifying and developing the great conceptual designers of the rising generation.
Introduction
Of all the monsters who fill the nightmares of our folklore, none terrify more than werewolves, because they transform unexpectedly from the familiar into horrors. For these, we seek bullets of silver than can magically lay them to rest.
The familiar software project has something of this character (at least as seen by the non-technical manager), usually innocent and straightforward, but capable of becoming a monster of missed schedules, blown budgets, and flawed products. So we hear desperate cries for a silver bullet, something to make software costs drop as rapidly as computer hardware costs do.
But, as we look to the horizon of a decade hence, we see no silver bullet. There is no single development, in either technology or management technique, which by itself promises even one order of magnitude improvement in productivity, in reliability, in simplicity. In this chapter we shall try to see why, but examining both the nature of the software problem and the properties of the bullets proposed.
Skepticism is not pessimism, however. Although we see no startling breakthroughs, and indeed, believe such to be inconsistent with the nature of software, many encouraging innovations are under way. A disciplined, consistent effort to develop, propagate, and exploit them should indeed yield an order-of-magnitude improvement. There is no royal road, but there is a road.
The first step toward the management of disease was replacement of demon theories and humours theories by the germ theory. That very step, the beginning of hope, in itself dashed all hopes of magical solutions. It told workers the progress would be made stepwise, at great effort, and that a persistent, unremitting care would have to be paid to a discipline of cleanliness. So it is with software engineering today.
Does It Have To Be Hard? – Essential Difficulties
Not only are there no silver bullets now in view, the very nature of software makes it unlikely that there will be any−no inventions that will do for software productivity, reliability, and simplicity what electronics, transistors, and large-scale integration did for computer hardware. We cannot expect ever to see twofold gains every two years.
First, we must observe that the anomaly is not that software progress is so slow but that computer hardware progress is so fast. No other technology since civilization began has seen six orders of magnitude price-performance gain in 30 years. In no other technology can one choose to take the gain in either improved performance or in reduced costs. These gains flow from the transformation of computer manufacture from an assembly industry into a process industry.
Second, to see what rate of progress we can expect in software technology, let us examine its difficulties. Following Aristotle, I divide them into essence−the difficulties inherent in the nature of the software−and accidents−those difficulties that today attend its production but that are not inherent.
The accidents I discuss in the next section. First let us consider the essence.
The essence of a software entity is a construct of interlocking concepts: data sets, relationships among data items, algorithms, and invocations of functions. This essence is abstract, in that the conceptual construct is the same under many different representations. It is nonetheless highly precise and richly detailed.
I believe the hard part of building software to be the specification, design, and testing of this conceptual construct, not the labor of representing it and testing the fidelity of the representation. We still make syntax errors, to be sure; but they are fuzz compared to the conceptual errors in most systems.
If this is true, building software will always be hard. There is inherently no silver bullet.
Let us consider the inherent properties of this irreducible essence of modern software systems: complexity, conformity, changeability, and invisibility.
Complexity. Software entities are more complex for their size than perhaps any other human construct, because no two parts are alike (at least above the statement level). If they are, we make the two similar parts into one, a subroutine, open or closed. In this respect software systems differ profoundly from computers, buildings, or automobiles, where repeated elements abound.
Digital computers are themselves more complex than most things people build; they have very large numbers of states. This makes conceiving, describing, and testing them hard. Software systems have orders of magnitude more states than computers do.
Likewise, a scaling-up of a software entity is not merely a repetition of the same elements in larger size; it is necessarily an increase in the number of different elements. In most cases, the elements interact with each other in some nonlinear fashion, and the complexity of the whole increases much more than linearly.
The complexity of software is in essential property, not an accidental one. Hence descriptions of a software entity that abstract away its complexity often abstract away its essence. Mathematics and the physical sciences made great strides for three centuries by constructing simplified models of complex phenomena, deriving properties from the models, and verifying those properties experimentally. This worked because the complexities ignored in the models were not the essential properties of the phenomena. It does not work when the complexities are the essence.
Many of the classical problems of developing software products derived from this essential complexity and its nonlinear increased with size. From the complexity comes the difficulty of communication among team members, which leads to product flaws, cost overruns, schedule delays. From the complexity comes the difficulty of enumerating, much less understanding, all the possible states of the program, and from that comes the unreliability. From the complexity of the functions comes the difficulty of invoking those functions, which makes programs hard to use. From complexity of structure comes the difficulty of extending programs to new functions without creating side effects. From the complexity of structure comes the unvisualized state that that constitute security trapdoors.
Not only technical problems but management problems as well come from the complexity. This complexity makes overview hard, thus impeding conceptual integrity. It makes it hard to find and control all the loose ends. It creates the tremendous learning and understanding burden that makes personnel turnover a disaster.
Conformity. Software people are not alone in facing complexity. Physics deals with terribly complex objects even at the “fundamental” particle level. The physicist labors on, however, in a firm faith that there are unifying principles to be found, whether in quarks or in unified field theories. Einstein repeatedly argued that there must be simplified explanations of nature, because God is not capricious or arbitrary.
No such faith comforts the software engineer. Much of the complexity he must master is arbitrary complexity, forced without rhyme or reason by the many human institutions and systems to which his interfaces must confirm. These differ from interface to interface, and from time to time, not because of necessity but only because they were designed by different people, rather than by God.
In many cases the software must confirm because it has most recently come to the scene. In others it must conform because it is perceived as the most conformable. But in all cases, much complexity comes from conformation to other interfaces; this cannot be simplified out by any redesign of the software alone.
Changeability. The software entity is constantly subject to pressures for change. Of course, so are buildings, cars, and computers. But manufactured things are infrequently changed after manufacture; they are superseded by later models, or essential changes are incorporated in later serial-number copies of the same basic design. Callbacks of automobiles are really quite infrequent; field changes of computers somewhat less so. Both are much less frequent than modifications to fielded software.
Partly this is because the software in a system embodies its function, and the function is the part that most feels the pressures of change. Partly it is because software can be changed more easily−it is pure thought-stuff, infinitely malleable. Buildings do in fact get changed, but the high costs of change, understood by all, serve to dampen the whim of the changers.
All successful software gets changed. Two processes are at work. As a software product is found to be useful, people try it in new cases at the edge of, or beyond, the original domain. The pressures for extended function come chiefly from users who like the basic function and invent new uses for it.
Second, successful software also survives beyond the normal life of the machine vehicle for which it is first written. If not new computers, then at least new disks, new displays, new printers come along; and the software must be conformed to its new vehicles of opportunity.
In short, the software product is embedded in a cultural matrix of applications, users, laws, and machine vehicles. These all change continually, and their changes inexorably force change upon the software product.
Invisibility. Software is invisible and unvisualizable. Geometric abstractions are powerful tools. The floor plan of a building helps both architect and client evaluate spaces, traffic flows, and views. Contradictions become obvious, omissions can be caught. Scale drawings of mechanical parts and stick-figure models of molecules, although abstractions, serve the same purpose. A geometric reality is captured in a geometric abstraction.
The reality of software is not inherently embedded in space. Hence it has no ready geometric representation in the way that land has maps, silicon chips have diagrams, computers have connectivity schematics. As soon as we attempt to diagram software structure, we find it to constitute not one, but several, general directed graphs, superimposed one upon another. The several graphs may represent the flow of control, the flow of data, patterns of dependency, time sequence, name-space relationships. These are usually not even planar, much less hierarchical. Indeed, one of the ways of establishing conceptual control over such structure is to enforce link cutting until one or more of the graphs becomes hierarchical.
In spite of progress in restricting and simplifying the structures of software, they remain inherently unvisualizable, thus depriving the mind of some of its most powerful conceptual tools. This lack not only impedes the process of design within one mind, it severely hinders communication among minds.
Past Breakthroughs Solved Accidental Difficulties
If we examine the three steps in software technology that have been most fruitful in the past, we discover that each attacked a different major difficulty in building software, but they have been the accidental, not the essential, difficulties. We can also see the natural limits to the extrapolation of each such attack.
High-level languages. Surely the most powerful stroke for software productivity, reliability, and simplicity has been the progressive use of high-level languages for programming. Most observers credit that development with at least a factor of five in productivity, and with concomitant gains in reliability, simplicity, and comprehensibility. What does a high-level language accomplish? It frees a program from much of its accidental complexity. An abstract program consists of conceptual constructs: operations, data types, sequences, and communication. The concrete machine program is concerned with bits, registers, conditions, branches, channels, disks, and such. To the extent that the high-level language embodies the constructs wanted in the abstract program and avoids all lower ones, it eliminates a whole level of complexity that was never inherent in the program at all.
The most a high-level language can do is to furnish all the constructs the programmer imagines in the abstract program. To be sure, the level of our sophistication in thinking about data structures, data types, and operations is steadily rising, but at an ever- decreasing rate. And language development approaches closer and closer to the sophistication of users.
Moreover, at some point the elaboration of a high-level language becomes a burden that increases, not reduces, the intellectual task of the user who rarely uses the esoteric constructs.
Time-sharing. Most observers credit time-sharing with a major improvement in the productivity of programmers and in the quality of their product, although not so large as that brought by high-level languages.
Time-sharing attacks a distinctly different difficulty. Time-sharing preserves immediacy, and hence enables us to maintain an overview of complexity. The slow turnaround of batch programming means that we inevitably forget the minutiae, if not the very thrust, of what we were thinking when we stopped programming and called for compilation and execution. This interruption of consciousness is costly in time, for we must refresh. The most serious effect may well be the decay of grasp of all that is going on in a complex system.
Slow turn-around, like machine-language complexities, is an accidental rather than an essential difficulty of the software process. The limits of the contribution of time-sharing derive directly. The principle effect is to shorten system response time. As it goes to zero, at some point it passes the human threshold of noticeability, about 100 milliseconds. Beyond that no benefits are to be expected.
Unified programming environments. Unix and Interlisp, the first integrated programming environments to come into widespread use, are perceived to have improved productivity by integral factors. Why?
They attack the accidental difficulties of using programs together, by providing integrated libraries, unified file formats, and piles and filters. As a result, conceptual structures that in principle could always call, feed, and use one another can indeed easily do so in practice.
This breakthrough in turn stimulated the development of whole toolbenches, since each new tool could be applied to any programs by using the standard formats.
Because of these successes, environments are the subject of much of today’s software engineering research. We will look at their promise and limitations in the next section.
Hopes for the Silver
Now lets us consider the technical developments that are most often advanced as potential silver bullets. What problems do they address? Are they the problems of essence, or are they remainders of our accidental difficulties? Do they offer revolutionary advances, or incremental ones?
Ada and other high-level language advances. One of the most touted recent developments is the programming language Ada, a general-purpose, high-level language of the 1980s. Ada indeed not only reflects evolutionary improvements in language concepts but embodies features to encourage modern design and modularization concepts. Perhaps the Ada philosophy is more of an advance than the Ada language, for it is the philosophy of modularization, of abstract data types, of hierarchical structuring. Ada is perhaps over-rich, the natural product of the process by which requirements were laid on its design. That is not fatal, for subset working vocabularies can solve the learning problem, and hardware advances will give us the cheap MIPS to pay for the compiling costs. Advancing the structuring of software systems is indeed a very good use for the increased MIPS our dollars will buy. Operating systems, loudly decried in the 1960s for their memory and cycle costs, have proved to be an excellent form in which to use some of the MIPS and cheap memory bytes of the past hardware surge.
Nevertheless, Ada will not prove to be the silver bullet that slays the software productivity monster. It is, after all, just another high-level language, and the biggest payoff from such languages came from the first transition, up from the accidental complexities of the machine into the more abstract statement of step-by-step solutions. Once those accidents have been removed, the remaining ones are smaller, and the payoff from their removal will surely be less.
I predict that a decade from now, when the effectiveness of Ada is assessed, it will be seen to have made a substantial difference, but not because of any particular language feature, nor indeed because of all of them combined. Neither will the new Ada environment prove to be the cause of the improvements. Ada’s greatest contribution will be that switching to it occasioned training programmers in modern software design techniques.
Object-oriented programming. Many students of the art hold out more hope for object- oriented programming than for any of the other technical fads of the day. I am among them. Mark Sherman of Dartmouth notes that we must be careful to distinguish two separate ideas that go under that name: abstract data types and hierarchical types, also called classes. The concept of the abstract data type is that an object’s type should be defined by a name, a set of proper values, and a set of proper operations, rather than its storage structure, which should be hidden. Examples are Ada packages (with private types) or Modula’s modules.
Hierarchical types, such as Simula-67’s classes, allow the definition of general interfaces that can be further refined by providing subordinate types. The two concepts are orthogonal−there may be hierarchies without hiding and hiding without hierarchies. Both concepts represent real advances in the art of building software.
Each removes one more accidental difficulty from the process, allowing the designer to express the essence of his design without having to express large amounts of syntactic material that add no new information content. For both abstract types and hierarchical types, the result is to remove a higher-order sort of accidental difficulty and allow a higher-order expression of design.
Nevertheless, such advances can do no more than to remove all the accidental difficulties from the expression of the design. The complexity of the design itself is essential; and such attacks make no change whatever in that. An order-of-magnitude gain can be made by object-oriented programming only if the unnecessary underbrush of type specification remaining today in our programming language is itself responsible for nine- tenths of the work involved in designing a program product. I doubt it.
Artificial intelligence. Many people expect advances in artificial intelligence to provide the revolutionary breakthrough that will give order-of-magnitude gains in software productivity and quality. I do not. To see why, we must dissect what is meant by “artificial intelligence” and then see how it applies.
Parnas has clarified the terminological chaos: Two quite different definitions of AI are in common use today. AI-1: The use of computers to solve problems that previously could only be solved by applying human intelligence. AI-2: The use of a specific set of programming techniques knows as heuristic or rule-based programming. In this approach human experts are studies to determine what heuristics or rules of thumb they use in solving problems. The program is designed to solve a problem the way that humans seem to solve it. The first definition has a sliding meaning. Something can fit the definition of AI-1 today but, once we see how the program works and understand the problem, we will not think of it as AI anymore. Unfortunately I cannot identify a body of technology that is unique to this field. Most of the work is problem-specific, and some abstraction or creativity is require to see how to transfer it.
I agree completely with this critique. The techniques used for speech recognition seem to have little in common with those used for image recognition, and both are different from those used in expert systems. I have a hard time seeing how image recognition, for example, will make any appreciable difference in programming practice. The same is try of speech recognition. The hard thing about building software is deciding what to say, not saying it. No facilitation of expression can give more than marginal gains.
Expert systems technology, AI-2, deserves a section of its own.
Expert systems. The most advanced part of the artificial intelligence art, and the most widely applies, is the technology for building expert systems. Many software scientists are hard at work applying this technology to the software-building environment. What is the concept, and what are the prospects?
An expert system is a program containing a generalized inference engine and a rule base, designed to take input data and assumptions and explore the logical consequences through the inferences derivable from the rule base, yielding conclusions and advice, and offering to explain its results by retracing its reasoning for the user. The inference engines typically can deal with fuzzy or probabilistic data and rules in addition to purely deterministic logic.
Such systems offer some clear advantages over programmed algorithms for arriving at the same solutions to the same problems:
- Inference engine technology is developed in an application-independent way, and then applied to many uses. One can justify much more effort on the inference engines. Indeed, that technology is well advanced.
- The changeable parts of the application-peculiar materials are encoded in the rule base in a uniform fashion, and tools are provided for developing, changing, testing, and documenting the rule base. This regularizes much of the complexity of the application itself.
Edward Feigenbaum says that the power of such systems does not come from ever- fancier inference mechanisms, but rather from ever-richer knowledge bases that reflect the real world more accurately. I believe the most important advance offered by the technology is the separation of the application complexity from the program itself.
How can this be applied to the software task? In many ways: suggesting interface rules, advising on testing strategies, remembering but-type frequencies, offering optimization hints, etc.
Consider an imaginary testing advisor, for example. In its most rudimentary form, the diagnostic expert system is very like a pilot’s checklist, fundamentally offering suggestions as to possible causes of difficulty. As the rule base is developed, the suggestions become more specific, taking more sophisticated account of the trouble symptoms reported. One can visualize a debugging assistant that offers very generalized suggestions at first, but as more and more system structure is embodied in the rule base, comes more and more particular in the hypotheses is generates and the tests it recommends. Such an expert system may depart most radically from the conventional ones in that its rule base should probably be hierarchically modularized in the same way the corresponding software product is, so that as the product is modularly modified, the diagnostic rule base can be modularly modified as well.
The work required to generate the diagnostic rules is work that will have to be done anyway in generating the set of test cases for the modules and for the system. If it is done in a suitably general manner, with a uniform structure for rules and a good inference engine available, it may actually reduce the total labor of generating bring-up test cases, as well as helping in lifelong maintenance and modification testing. In the same way, we can postulate other advisors probably many of them and probably simple ones for the other parts of the software construction task.
Many difficulties stand in the way of early realization of useful expert advisors to the program developer. A crucial part of our imaginary scenario is the development of easy ways to get from program structure specification to the automatic or semi-automatic generation of diagnostic rules. Even more difficult and important is the twofold task of knowledge acquisition: finding articulate, self-analytical experts who know why they do things; and developing efficient techniques for extracting what they know and distilling it into rule bases. The essential prerequisite for building an expert system is to have an expert.
The most powerful contribution of expert systems will surely be to put at the service of the inexperienced programmer the experience and accumulated wisdom of the best programmers. This is no small contribution. The gap between the best software engineering practice and the average practice is very wide−perhaps wider than in any other engineering discipline. A tool that disseminates good practice would be important.
“Automatic” programming. For almost 40 years, people have been anticipating and writing about “automatic programming”, the generation of a program for solving a problem from a statement of the problem specifications. Some people today write as if they expected this technology to provide the next breakthrough.7
Parnas implies that the term is used for glamour and not semantic content, asserting, In short, automatic programming always has been a euphemism for programming with a higher-level language than was presently available to the programmer.
He argues, in essence, that in most cases it is the solution method, not the problem, whose specification has to be given.
Exceptions can be found. The technique of building generators is very powerful, and it is routinely used to good advantage in programs for sorting. Some systems for integrating differential equations have also permitted direct specification of the problem. The system assessed the parameters, chose from a library of methods of solution, and generated the programs.
- These applications have very favorable properties:
- The problems are readily characterized by relatively few parameters.
- There are many known methods of solution to provide a library of alternatives.
- Extensive analysis has led to explicit rules for selecting solution techniques, given problem parameters.
It is hard to see how such techniques generalize to the wider world of the ordinary software system, where cases with such neat properties are the exception. It is hard even to imagine how this breakthrough in generalization could conceivably occur.
Graphical programming. A favorite subject for PH.D. dissertations in software engineering is graphical, or visual, programming, the application of computer graphics to software design.9 Sometimes the promise of such an approach is postulated from the analogy with VLSI chip design, where computer graphics plays so fruitful a role. Sometimes the approach is justified by considering flowcharts as the ideal program design medium, and providing powerful facilities for constructing them.
Nothing even convincing, much less exciting, has yet emerged from such efforts. I am persuaded that nothing will.
In the first place, as I have argued elsewhere, the flow chart is a very poor abstraction of software structure. Indeed, it is best viewed as Burks, von Neumann, and Goldstine’s attempt to provide a desperately needed high-level control language for their proposed computer. In the pitiful, multipage, connection-boxed form to which the flow chart has today been elaborated, it has proved to be essentially useless as a design-tool programmers draw flow charts after, not before, writing the programs they describe.
Second, the screens of today are too small, in pixels, to show both the scope and the resolution of any serious detailed software diagram. The so-called “desktop metaphor” of today’s workstation is instead an “airplane-seat” metaphor. Anyone who has shuffled a lapful of papers while seated in a coach between two portly passengers will recognize the difference−one can see only a very few things at once. The true desktop provides overview of and random access to a score of pages. Moreover, when fits of creativity run strong, more than one programmer or writer has been known to abandon the desktop for the more spacious floor. The hardware technology will have to advance quite substantially before the scope of our scopes is sufficient to the software design task.
More fundamentally, as I have argued above, software is very difficult to visualize. Whether we diagram control flow, variable scope nesting, variable cross-references, data blow, hierarchical data structures, or whatever, we feel only one dimension of the intricately interlocked software elephant. If we superimpose all the diagrams generated by the many relevant views, it is difficult to extract any global overview. The VLSI analogy is fundamentally misleading−a chip design is a layered two-dimensional object whose geometry reflects its essence. A software system is not.
Program verification. Much of the effort in modern programming goes into the testing and repair of bugs. Is there perhaps a silver bullet to be found by eliminating the errors at the source, in the system design phase? Can both productivity and product reliability be radically enhance by following the profoundly different strategy of proving designs correct before the immense effort is poured into implementing and testing them?
I do not believe we will find the magic here. Program verification is a very powerful concept, and it will be very important for such things as secure operating system kernels. The technology does not promise, however, to save labor. Verifications are so much work that only a few substantial programs have ever been verified.
Program verification does not mean error-proof programs. There is no magic here, either. Mathematical proofs also can be faulty. So whereas verification might reduce the program-testing load, it cannot eliminate it.
More seriously, even perfect program verification can only establish that a program meets its specification. The hardest part of the software task is arriving at a complete and consistent specification, and much of the essence of building a program is in fact the debugging of the specification.
Environments and tools. How much more gain can be expected from the exploding researches into better programming environments? One’s instinctive reaction is that the big-payoff problems were the first attacked, and have been solved: hierarchical file systems, uniform file formats so as to have uniform program interfaces, and generalized tools. Language-specific smart editors are developments not yet widely used in practice, but the most they promise is freedom from syntactic errors and simple semantic errors.
Perhaps the biggest gain yet to be realized in the programming environment is the use of integrated database systems to keep track of the myriads of details that must be recalled accurately by the individual programmer and kept current in a group of collaborators on a single system.
Surely this work is worthwhile, and surely it will bear some fruit in both productivity and reliability. But by its very nature, the return from now on must be marginal.
Workstations. What gains are to be expected for the software art from the certain and rapid increase in the power and memory capacity of the individual workstation? Well, how many MIPS can one use fruitfully? The composition and editing of programs and documents is fully supported by today’s speeds. Compiling could stand a boost, but a factor of 10 in machine speed would surely leave think-time the dominant activity in the programmer’s day. Indeed, it appears to be so now.
More powerful workstations we surely welcome. Magical enhancements from them we cannot expect.
Promising Attacks on the Conceptual Essence
Even though no technological breakthrough promises to give the soft of magical results with which we are so familiar in the hardware area, there is both an abundance of good working going on now, and the promise of steady, if unspectacular progress.
All of the technological attacks on the accidents of the software process are fundamentally limited by the productivity equation:

If, as I believe, the conceptual components of the task are now taking most of the time, then no amount of activity on the task components that are merely the expression of the concepts can give large productivity gains.
Hence we must consider those attacks that address the essence of the software problem, the formulation of these complex conceptual structures. Fortunately, some of these are very promising.
Buy versus build. The most radical possible solution for constructing software is not to construct it at all.
Every day this becomes easier, as more and more vendors offer more and better software products for a dizzying variety of applications. While we software engineers have labored on production methodology, the personal computer revolution has created not one, but m any, mass markets for software. Every newsstand carried monthly magazines which, sorted by machine type, advertise and review dozens of products at prices from a few dollars to a few hundred dollars. More specialized sources offer very powerful products for the workstation and other Unix markets. Even software tolls and environments can be bought off-the-shelf. I have elsewhere proposed a market place for individual modules.
Any such product is cheaper to buy than to build afresh. Even at a cost of $100,000, a purchased piece of software is costing only about as much as one programmer-year. And delivery is immediate! Immediate at least for products that really exist, products whose developer can refer the prospect to a happy user. Moreover, such products tend to be much better documented and somewhat better maintained than homegrown software.
The development of the mass market is, I believe, the most profound long-run trend in software engineering. The cost of software has always been development cost, not replication cost. Sharing that cost among even a few users radically cuts the per-user cost. Another way of looking at it is that the use of n copies of a software system effectively multiplies the productivity of its developers by n. That is an enhancement of the productivity of the discipline and of the nation.
The key issue, of course, is applicability. Can I use an available off-the-shelf package to do my task? A surprising thing has happened here. During the 1950s and 1960s, study after study showed that users would not use off-the-shelf packages for payroll, inventory control, accounts receivable, etc. The requirements were too specialized, the case-to-case variation too high. During the 1980s, we find such packages in high demand and widespread use. What has changed?
Not really the packages. They may be somewhat more generalized and somewhat more customizable than formerly, but not much. Not really the applications, either. If
anything, the business and scientific needs of today are more diverse, more complicated than those of 20 years ago.
The big change has been in the hardware/software cost ratio. The buyer of a $2- million machine in 1960 felt that he could afford $250,000 more for a customized payroll program, one that slipped easily and nondisruptively into the computer-hostile social environment. Buyers of $50,000 office machines today cannot conceivably afford customized payroll programs; so they adapt their payroll procedures to the packages available. Computers are now so commonplace, if not yet so beloved, that the adaptations are accepted as a matter of course.
There are dramatic exceptions to my argument that the generalization of the software packages has changed little over the years: electronic spreadsheets and simple database systems. These powerful tools, so obvious in retrospect and yet so late appearing, lend themselves to myriad uses, some quite unorthodox. Articles and even books now abound on how to tackle unexpected tasks with the spreadsheet. Large numbers of applications that would formerly have been written as custom programs in Cobol or Report Program Generator are now routinely done with these tools.
Many users now operate their own computers day in and day out on varied applications without ever writing a program. Indeed, many of these users cannot write new programs for their machines, but they are nevertheless adept at solving new problems with them.
I believe the single most powerful software productivity strategy for man organizations to day is to equip the computer-naïve intellectual workers on the firing line with personal computers and good generalized writing, drawing, file and spreadsheet programs, and turn them loose. The same strategy, with simple programming capabilities, will also work for hundreds of laboratory scientists.
Requirements refinement and rapid prototyping. The hardest single part of building a software system is deciding precisely what to build. No other part of the conceptual work is to difficult as establishing the detailed technical requirements, including all the interfaces to people, to machines, and to other software systems. No other part of the work so cripples the resulting system if done wrong. No other part is more difficult go rectify later.
Therefore the most important function that software builders do for their clients is the iterative extraction and refinement of the product requirements. For the truth is, the clients do not know what they want. They usually do not know what questions must be answered, and they almost never have thought of the problem in the detail that must be specified. Even the simple answer−”Make the new software system work like our old manual information-processing system” −is in fact too simple. Clients never want exactly that. Complex software systems are, moreover, things that act, that move, that work. The dynamics of that action are hard to imagine. So in planning any software activity, it is necessary to allow for an extensive iteration between the client and the designer as part of the system definition.
I would go a step further and assert that it is really impossible for clients, even those working with software engineers, to specify completely, precisely, and correctly the exact requirements of a modern software product before having built and tried some versions of the product they are specifying.
Therefore one of the most promising of the current technological efforts, and one which attacks the essence, not the accidents, of the software problem, is the development of approaches and tools for rapid prototyping of systems as part of the iterative specification of requirements.
A prototype software system is one that simulates the important interfaces and performs the main functions of the intended system, while not being necessarily bound by the same hardware speed, size, or cost constraints. Prototypes typically perform the mainline tasks of the application, but make no attempt to handle the exceptions, respond correctly to invalid inputs, abort cleanly, etc. The purpose of the prototype is to make real the conceptual structure specified, so that the client can test it for consistency and usability
Much of present-day software acquisition procedures rests upon the assumption that one can specify a satisfactory system in advance, get bids for its construction, have it built, and install it. I think this assumption is fundamentally wrong, and that many software acquisition problems spring from that fallacy. Hence they cannot be fixed without fundamental revision, one that provides for iterative development and specification of prototypes and products.
Incremental development−grow, not build, software. I still remember the jolt I felt in 1958 when I first heard a friend talk about building a program, as opposed to writing one. In a flash be broadened my whole view of the software process. The metaphor shift was powerful, and accurate. Today we understand how like other building processes the construction of software is, and we freely use other elements of the metaphor, such as specifications, assembly of components, and scaffolding.
The building metaphor has outlived its usefulness. It is time to change again. If, as I believe, the conceptual structures we construct today are too complicated to be accurately specified in advance, and too complex to be built faultlessly, then we must take a radically different approach.
Let us turn to nature and study complexity in living things, instead of just the dead works of man. Here we find constructs whose complexities thrill us with awe. The brain alone is intricate beyond mapping, powerful beyond imitation, rich in diversity, self- protecting, and self-renewing. The secret is that it is grown, not built.
So it must be with our software systems. Some years ago Harlan Mills proposed that any software system should be grown by incremental development.11 That is, the system should first be made to run, even though it does nothing useful except call the proper set of dummy subprograms. Then, bit-by-bit it is fleshed out, with the subprograms in turn being developed into actions or calls to empty stubs in the level below.
I have seen the most dramatic results since I began urging this technique on the project builders in my software engineering laboratory class. Nothing in the past decade has so radically changed my own practice, or its effectiveness. The approach necessitates top-down design, for it is a top-down growing of the software. It allows easy backtracking. It lends itself to early prototypes. Each added function and new provision for more complex data or circumstances grown organically out of what is already there.
The morale effects are startling. Enthusiasm jumps when there is a running system, even a simple one. Efforts redouble when the first picture from a new graphics software system appears on the screen, even if it is only a rectangle. One always has, at every stage in the process, a working system. I find that teams can grow much more complex entities in four months than they can build.
The same benefits can be realized on large projects as on my small ones.
Great designers. The central question of how to improve the software art centers, as it always, on people.
We can get good designs by following good practices instead of poor ones. Good design practices can be taught. Programmers are among the most intelligent part of the population, so they can learn good practice. Thus a major thrust in the United States is to promulgate good modern practice. New curricula, new literature, new organizations such as the Software Engineering Institute, all have come into being in order to raise the level of our practice from poor to good. This is entirely proper.
Nevertheless, I do not believe we can make the next step upward in the same way. Whereas the difference between poor conceptual designs and good ones may lie in the soundness of design method, the difference between good designs and great ones surely does not. Great designs come from great designers. Software construction is a creative process. Sound methodology can empower and liberate the creative mind; it cannot enflame or inspire the drudge.
The differences are not minor−it is rather like Salieri and Mozart. Study after study shows that the very best designers produce structures that are faster, smaller, simpler, cleaner, and produced with less effort. The differences between the great and the average approach an order of magnitude.
A little retrospection shows that although many fine, useful software systems have been designed by committees and built by multipart projects, those software systems that have excited passionate fans are those that are the products of one or a few designing minds, great designers. Consider Unix, APL, Pascal, Modula, the Smalltalk interface, even Fortran; and contrast with Cobol, PL/I, Algol, MVS/370, and MS-DOS (fig. 1)

Hence, although I strongly support the technology transfer and curriculum development efforts now underway, I think the most important single effort we can mount is to develop ways to grow great designers.
No software organization can ignore this challenge. Good managers, scarce though they be, are no scarcer than good designers. Great designers and great managers are both very rare. Most organizations spend considerable effort in finding and cultivating the management prospects; I know of none that spends equal effort in finding and developing the great designers upon whom the technical excellence of the products will ultimately depend.
My first proposal is that each software organization must determine and proclaim that great designers are as important to its success as great managers are, and that they can be expected to be similarly nurtured and rewarded. Not only salary, but the perquisites of recognition−office size, furnishings, personal technical equipment, travel funds, staff support−must be fully equivalent.
How to grow great designers? Space does not permit a lengthy discussion, but some steps are obvious:
- Systematically identify top designers as early as possible. The best are often not the most experienced.
- Assign a career mentor to be responsible for the development of the prospect, and keep a careful career file.
- Devise and maintain a career development plan for each prospect, including carefully selected apprenticeships with top designers, episodes of advanced formal education, and short courses, all interspersed with solo design and technical leadership assignments.
- Provide opportunities for growing designers to interact with and stimulate each other.
翻译
作者:小弗雷德里克·P·布鲁克斯 (Frederick P. Brooks, Jr.) 北卡罗来纳大学教堂山分校 (University of North Carolina at Chapel Hill)
在技术或管理方法上,没有任何一项单一的发展能够本身承诺在十年内让生产力、可靠性或简洁性获得哪怕是一个数量级的提升。
摘要 (Abstract)
所有的软件构建都包含本质性任务(塑造构成抽象软件实体的复杂概念结构)和附属性任务(在编程语言中表示这些抽象实体,并在空间和速度限制内将它们映射到机器语言上)。 过去软件生产力的绝大多数大幅提升,都来自于消除了那些使附属性任务变得异常困难的人为障碍,例如严苛的硬件限制、蹩脚的编程语言、机器时间的缺乏。 如今软件工程师所做的工作中,还有多少是投入在附属性任务而非本质性任务上的? 除非它占据了所有努力的 9/10 以上,否则即使将所有附属性活动的耗时缩减为零,也无法带来数量级的提升。
因此,现在似乎是时候着手解决软件任务中的本质部分了,即那些涉及塑造高度复杂的抽象概念结构的部分。 我建议:
- 利用大众市场,避免去构建可以直接购买的东西。
- 在确立软件需求时,使用快速原型作为有计划的迭代的一部分。
- 有机地“生长”软件,随着系统的运行、使用和测试,为其添加越来越多的功能。
- 识别并培养新一代中伟大的概念设计师。
简介 (Introduction)
在所有充斥于我们民间传说噩梦中的怪物里,没有比狼人更令人恐惧的了,因为它们会出人意料地从熟悉的事物变成恐怖的怪物。 对于这些怪物,我们寻找可以神奇地让它们安息的银弹。
我们熟悉的软件项目也带有这种特征(至少在非技术管理者看来是这样),它们通常看起来单纯且简单直接,但却有能力变成一个由进度延误、预算超支和产品缺陷组成的怪物。 因此,我们听到了对银弹的绝望呼唤,希望有某种东西能让软件成本像计算机硬件成本那样迅速下降。
但是,当我们放眼未来十年的地平线时,我们没有看到银弹。 在技术或管理技术方面,没有任何单一的发展能够本身承诺在生产力、可靠性和简洁性方面实现哪怕一个数量级的提升。 在本章中,我们将通过考察软件问题的本质以及被提出的“子弹”的属性,来试着弄清其中的原因。
然而,怀疑并不等于悲观。 虽然我们没有看到惊人的突破,并且确实认为这种突破与软件的本质不符,但许多令人鼓舞的创新正在进行中。 通过有纪律、持续的努力来开发、传播和利用它们,确实应该能带来数量级的提升。 虽然没有捷径(皇家大道)可走,但路是有的。
人类应对疾病迈出的第一步,是用细菌学说取代了恶魔学说和体液学说。 正是这带来希望的开端的一步,本身也粉碎了所有对神奇疗法的幻想。 它告诉工作者们,进步将是逐步取得的,需要付出巨大的努力,并且必须坚持不懈地关注清洁的纪律。 今天的软件工程也是如此。
必须这么难吗?——本质的困难 (Does It Have To Be Hard? – Essential Difficulties)
不仅现在看不到银弹,软件的本质使得未来也不太可能出现——没有任何发明能像电子学、晶体管和大规模集成电路对计算机硬件所做的那样,为软件的生产力、可靠性和简洁性带来同等的效果。 我们不能指望每两年就能看到两倍的增长。
首先,我们必须观察到,反常的不是软件的进展如此缓慢,而是计算机硬件的进展如此之快。 自文明开始以来,没有任何其他技术能在 30 年内实现六个数量级的性价比提升。 在任何其他技术中,人们都无法选择将收益体现在性能提升或成本降低上。 这些收益源于计算机制造从装配工业向流程工业的转变。
其次,为了看看我们在软件技术中可以期待怎样的进步速度,让我们检查一下它的困难。 遵循亚里士多德的观点,我将它们分为本质(存在于软件性质中的固有困难)和偶然/附属(今天伴随其生产但并非固有的困难)。
我将在下一节讨论偶然性困难。 首先让我们考虑本质。
软件实体的本质是一个由相互交织的概念构成的结构:数据集、数据项之间的关系、算法以及函数的调用。 这种本质是抽象的,因为这种概念结构在许多不同的表示形式下都是相同的。 尽管如此,它依然是高度精确且细节丰富的。
我认为构建软件的困难部分在于这种概念结构的规范、设计和测试,而不在于表示它并测试表示保真度的劳作。 诚然,我们仍然会犯语法错误;但与大多数系统中的概念错误相比,它们不过是微不足道的细枝末节。
如果这是真的,那么构建软件将总是困难的。 本质上不存在银弹。
让我们考虑一下现代软件系统这种不可还原的本质的固有属性:复杂性、一致性、可变性和不可见性。
复杂性 (Complexity)。 就其规模而言,软件实体也许是所有人类构造物中最复杂的,因为没有任何两个部分是相同的(至少在语句级别之上)。 如果它们相同,我们会将这两个相似的部分合二为一,变成一个子程序(开放或封闭的)。 在这方面,软件系统与计算机、建筑或汽车有着深刻的区别,在后者中,重复的元素大量存在。
数字计算机本身比人们建造的大多数东西都要复杂;它们有大量的状态。 这使得构思、描述和测试它们变得困难。 而软件系统的状态比计算机还要多出几个数量级。
同样,软件实体的扩大不仅仅是相同元素的更大规模重复;它必然是不同元素数量的增加。 在大多数情况下,这些元素以某种非线性方式相互作用,并且整体的复杂性增加远超线性。
软件的复杂性是一个本质属性,而不是偶然属性。 因此,抽离了软件实体复杂性的描述,往往也抽离了它的本质。 三个世纪以来,数学和物理科学通过构建复杂现象的简化模型、从模型中推导属性并通过实验验证这些属性,取得了巨大的进步。 这之所以行之有效,是因为模型中忽略的复杂性并非现象的本质属性。 当复杂性本身就是本质时,这种方法就不起作用了。
开发软件产品的许多经典问题都源于这种本质的复杂性及其随规模非线性增加的特性。 复杂性导致了团队成员之间沟通的困难,进而导致产品缺陷、成本超支、进度延误。 复杂性导致了列举(更不用说理解)程序所有可能状态的困难,而这就导致了不可靠性。 功能的复杂性导致了调用这些功能的困难,这使得程序难以使用。 结构的复杂性导致了在不产生副作用的情况下将程序扩展至新功能的困难。 结构的复杂性导致了未被察觉的状态,这些状态构成了安全漏洞。
不仅技术问题,管理问题也源于这种复杂性。 它使得全面掌控变得困难,从而阻碍了概念的完整性。 它使得寻找和控制所有未解决的细节变得困难。 它创造了巨大的学习和理解负担,使得人员流失成为一场灾难。
一致性 (Conformity)。 面对复杂性的不仅是软件人员。 物理学即使在“基本”粒子层面也处理着极其复杂的对象。 然而,物理学家在坚定的信念中继续工作,相信可以找到统一的原理,无论是在夸克还是在统一场论中。 爱因斯坦反复争辩说自然界一定有简化的解释,因为上帝不是反复无常或任意妄为的。
软件工程师没有这种信念带来的安慰。 他必须掌握的复杂性中,有很多是任意的复杂性,是由他的接口必须遵循的许多人类机构和系统毫无章法地强加的。 这些接口之所以因时因地而异,并非出于必要,仅仅是因为它们是由不同的人,而不是由上帝设计的。
在许多情况下,软件必须保持一致,因为它最新来到这个场景。 在其他情况下,它必须保持一致,因为它被认为是最容易顺应的。 但在所有情况下,很多复杂性都来自于对其他接口的顺应;这不可能通过仅仅重新设计软件来简化掉。
可变性 (Changeability)。 软件实体不断受到要求变更的压力。 当然,建筑物、汽车和计算机也是如此。 但制造出来的东西在制造后很少改变;它们会被后来的型号取代,或者将基本的更改整合到同一基本设计的后续序列号副本中。 汽车的召回其实非常少见;计算机的现场更改稍微多一些。 两者的频率都远低于对已部署软件的修改。
部分原因是系统中的软件体现了它的功能,而功能是最能感受到变更压力的部分。 部分原因是软件更容易被改变——它是纯粹的思想产物,具有无限的延展性。 建筑物实际上也会被改变,但众所周知的高昂变更成本起到了抑制变更者心血来潮的作用。
所有成功的软件都会被改变。 有两个过程在起作用。 当一个软件产品被发现是有用的时候,人们会尝试在原有领域的边缘或之外的新情况中使用它。 扩展功能的压力主要来自喜欢基本功能并为其发明新用途的用户。
其次,成功的软件的寿命也往往超过了它最初为其编写的机器载体的正常寿命。 如果没有新计算机,至少也会出现新磁盘、新显示器、新打印机;而软件必须适应它新的机会载体。
简而言之,软件产品被嵌入到一个由应用程序、用户、法律和机器载体组成的文化矩阵中。 这些都在不断变化,它们的改变无情地迫使软件产品发生改变。
不可见性 (Invisibility)。 软件是不可见且无法可视化的。 几何抽象是强大的工具。 建筑物的平面图帮助建筑师和客户评估空间、交通流和景观。 矛盾会变得明显,遗漏可以被捕捉。 机械零件的比例图和分子的火柴棍模型虽然是抽象,但服务于同样的目的。 一种几何现实被捕捉在一种几何抽象中。
软件的现实本质上并不嵌入在空间中。 因此,它没有像土地有地图、硅片有图纸、计算机有连接原理图那样现成的几何表示。 一旦我们试图用图表来表示软件结构,我们就会发现它构成的不是一个,而是几个相互叠加的一般有向图。 这几个图可能代表控制流、数据流、依赖模式、时间序列、命名空间关系。 这些图通常甚至不是平面的,更不用说是分层的了。 实际上,建立对这种结构的概念控制的方法之一,就是强制切割链接,直到一个或多个图变成层次结构。
尽管在限制和简化软件结构方面取得了进展,但它们本质上仍然是不可可视化的,从而剥夺了大脑一些最强大的概念工具。 这种缺失不仅阻碍了一个人头脑中的设计过程,还严重阻碍了不同头脑之间的沟通。
好,咱们接着往下扒。这部分是全篇最硬核的“本质困难(Essential Difficulties)”。
作者毫不客气地指出,软件开发天生就是地狱难度,根本原因在于它自带四大无法根除的“基因缺陷”:
1. 复杂性(Complexity):牵一发而动全身 软件的规模一旦扩大,绝不是简单元素的重复拼凑,而是各种不同元素的非线性增加。系统状态多得根本无法穷举,这直接导致了团队沟通不畅、进度拖延以及无处不在的系统 Bug。就像设计复杂的深度学习架构(比如将注意力机制或物理信息约束融合进网络)时,层与层、参数与参数之间的交互极其复杂,牵一发而动全身,远非简单的线性叠加。
2. 一致性(Conformity):被迫向不讲理的现实妥协 物理学家坚信世界有统一的优美规律,但软件工程师没有这种福气。软件必须去适应各种杂乱无章的外部接口、老旧的人为系统和制度,这些复杂性是外部环境强加的,毫无逻辑可言。这就好比在处理现实中的工程数据时,不得不写大量毫无美感的预处理代码,去对齐各种乱七八糟的传感器格式。
3. 可变性(Changeability):永无止境的变更压力 因为软件是纯粹的思想产物,改起来“看上去”没有成本,所以所有人都在逼着它改。只要软件一成功,用户就会想要新功能;哪怕业务不变,底层硬件、操作系统换代了,软件也得跟着重构。
4. 不可见性(Invisibility):看不见摸不着的“多维网” 造大楼有平面图,造芯片有电路图,但软件没有物理实体,没法用直观的几何图形完全展示。它的控制流、数据流、依赖关系是多维交错的网,很难在人脑中建立起完整的全局视图,这极大地阻碍了思考和团队沟通。很多复杂模型和算法的内部状态流转难以具象化,也是吃了“不可见”的亏。
过去的突破解决了偶然性困难 (Past Breakthroughs Solved Accidental Difficulties)
如果我们考察过去在软件技术中最富有成效的三个步骤,我们会发现每一个都攻击了构建软件中一个不同的主要困难,但它们都是偶然的/附属性的,而不是本质性的困难。 我们也可以看到对每一次这种攻击进行推断的自然限制。
高级语言 (High-level languages)。 毫无疑问,对软件生产力、可靠性和简单性最有力的一击,是高级语言在编程中的逐步使用。 大多数观察家认为,这一发展使生产力至少提高了五倍,并带来了可靠性、简单性和可理解性方面的同步提升。 高级语言实现了什么? 它将程序从其许多偶然的复杂性中解放出来。 一个抽象程序由概念结构组成:操作、数据类型、序列和通信。 具体的机器程序则关注位、寄存器、条件、分支、通道、磁盘等。 只要高级语言体现了我们在抽象程序中想要的结构并避免了所有底层的结构,它就消除了一整个级别的原本就并非程序固有的复杂性。
高级语言最多能做的,就是提供程序员在抽象程序中想象到的所有构造。 诚然,我们关于数据结构、数据类型和操作的思考复杂程度正在稳步上升,但速度越来越慢。 而且语言的发展也越来越接近用户的复杂程度。
此外,在某一点上,高级语言的复杂化会变成一种负担,它增加而不是减少了极少使用那些深奥构造的用户的脑力劳动。
分时系统 (Time-sharing)。 大多数观察家认为分时系统在程序员的生产力和其产品的质量方面带来了重大改善,尽管不如高级语言带来的那样大。
分时系统攻击了一个明显不同的困难。 分时系统保持了即时性,从而使我们能够维持对复杂性的概览。 批处理编程缓慢的周转意味着当我们停止编程并呼叫编译和执行时,我们不可避免地会忘记我们当时在想什么的细节,甚至可能是核心主旨。 这种意识的中断在时间上代价高昂,因为我们必须重新回忆。 最严重的后果很可能是对复杂系统中正在发生的所有事情的掌控力的衰退。
缓慢的周转,就像机器语言的复杂性一样,是软件过程的偶然困难而不是本质困难。 分时系统贡献的限制直接来源于此。 其主要作用是缩短系统响应时间。 当它趋于零时,在某一点上它跨过了人类注意力的阈值,大约是 100 毫秒。 超过这个界限,就不期待有任何好处了。
统一的编程环境 (Unified programming environments)。 Unix 和 Interlisp 作为首批广泛使用的集成编程环境,被认为成倍地提高了生产力。 为什么?
它们通过提供集成的库、统一的文件格式以及管道和过滤器,攻击了将程序放在一起使用的附属性困难。 结果,原则上可以相互调用、提供数据和相互使用的概念结构,在实践中确实可以轻松做到。
这一突破反过来又刺激了整个工具台的发展,因为每个新工具都可以通过使用标准格式应用于任何程序。
由于这些成功,环境成为当今软件工程研究的许多主题。 我们将在下一节探讨它们的前景和局限性。
对银弹的期望 (Hopes for the Silver)
现在让我们考虑那些最常被提出作为潜在银弹的技术发展。 它们解决了什么问题? 它们是本质问题,还是我们附属性困难的残余? 它们提供的是革命性的进步,还是渐进式的?
Ada 及其他高级语言的进步 (Ada and other high-level language advances)。 最近最受吹捧的发展之一是编程语言 Ada,这是一种 1980 年代的通用高级语言。 Ada 确实不仅反映了语言概念的演进式改进,而且包含了鼓励现代设计和模块化概念的特性。 也许 Ada 的哲学比 Ada 语言本身更是一种进步,因为它是模块化、抽象数据类型、分层结构的哲学。 Ada 可能过于丰富了,这是向其设计提出需求的过程的自然产物。 这并非致命缺陷,因为子集工作词汇表可以解决学习问题,而且硬件的进步将为我们提供廉价的 MIPS(每秒百万条指令)来支付编译成本。 推进软件系统的结构化,确实是对我们的美元所能买到的增加的 MIPS 的极好利用。 操作系统在 1960 年代因其内存和周期成本而备受谴责,现在已证明是利用过去硬件激增带来的一些 MIPS 和廉价内存字节的绝佳形式。
尽管如此,Ada 不会被证明是能杀死软件生产力怪物的银弹。 毕竟,它只是另一种高级语言,而此类语言的最大回报来自于最初的转变,即从机器的偶然复杂性中脱离出来,进入更为抽象的循序渐进的解决方案的陈述。 一旦那些偶然性被消除,剩下的就小了,消除它们带来的回报肯定也会少。
我预测十年后,当评估 Ada 的有效性时,将会发现它产生了实质性的差异,但这并不是因为任何特定的语言特性,甚至也不是因为它们所有的组合。 新的 Ada 环境也不会被证明是这些改进的原因。 Ada 最大的贡献将是,转向它促使了用现代软件设计技术培训程序员。
面向对象的编程 (Object-oriented programming)。 许多该领域的学生对面向对象编程抱有比对当今任何其他技术狂热更多的希望。 我也是其中之一。 达特茅斯学院的马克·谢尔曼指出,我们必须小心区分在这个名称下的两个独立想法:抽象数据类型和层次类型(也称为类)。 抽象数据类型的概念是,对象的类型应该由名称、一组适当的值和一组适当的操作来定义,而不是它的存储结构,存储结构应该是隐藏的。 例子有 Ada 包(带有私有类型)或 Modula 的模块。
分层类型,例如 Simula-67 的类,允许定义可通过提供从属类型来进一步细化的一般接口。 这两个概念是正交的——可以有不带隐藏的层次结构,也可以有不带层次结构的隐藏。 这两个概念都代表了构建软件艺术的真正进步。
每一个概念都从过程中消除了一层附属性困难,使设计师能够表达其设计的本质,而不必表达大量没有增加新信息内容的句法材料。 对于抽象类型和层次类型两者而言,结果都是消除了一种更高阶的附属性困难,并允许进行更高阶的设计表达。
尽管如此,这样的进步所能做的,不过是从设计的表达中消除所有的附属性困难。 设计本身的复杂性是本质的;这种攻击对其没有任何改变。 只有当当今残留在我们的编程语言中不必要的类型规范杂芜本身占据了设计程序产品所涉及工作的十分之九时,面向对象的编程才能取得数量级的收益。 对此我表示怀疑。
人工智能 (Artificial intelligence)。 许多人期待人工智能的进步能提供革命性的突破,从而在软件生产力和质量方面带来数量级的提升。 我不这么认为。 要明白为什么,我们必须剖析“人工智能”的含义,然后看看它是如何应用的。
Parnas 澄清了这种术语上的混乱:
今天常用的两种截然不同的人工智能定义。 AI-1:使用计算机解决以前只能通过应用人类智能来解决的问题。 AI-2:使用一组被称为启发式或基于规则的编程的具体编程技术。 在这种方法中,通过研究人类专家来确定他们在解决问题时使用了什么启发式方法或经验法则。 程序被设计成似乎像人类一样解决问题。
第一个定义具有滑动(变化)的含义。 有些东西今天可能符合 AI-1 的定义,但是一旦我们看清了程序是如何工作的并理解了问题,我们就不再认为它是人工智能了。 不幸的是,我无法识别出这个领域独有的一门技术主体。 大部分工作都是针对具体问题的,需要一些抽象或创造力才能明白如何转移它。
我完全同意这种批评。 用于语音识别的技术似乎与用于图像识别的技术几乎没有共同之处,而且两者都与用于专家系统的技术不同。 例如,我很难看出图像识别会在编程实践中产生任何明显的差异。 语音识别也是如此。 构建软件困难的地方在于决定要说什么,而不是怎么说。 任何表达的便利都只能提供边际收益。
专家系统技术,即 AI-2,值得专门拿出一节来讨论。
专家系统 (Expert systems)。 人工智能艺术中最先进的部分,也是应用最广泛的,是构建专家系统的技术。 许多软件科学家正努力将这项技术应用于软件构建环境中。 其概念是什么,前景如何?
专家系统是一个包含通用推理机和规则库的程序,旨在获取输入数据和假设,并通过从规则库中得出的推论探索逻辑结果,产生结论和建议,并提出通过为用户追溯其推理来解释其结果。 推理机通常除了处理纯粹确定性的逻辑外,还能处理模糊或概率性的数据和规则。
这类系统为得出相同问题的相同解决方案,提供了优于单纯编程算法的一些明显优势:
- 推理机技术以独立于应用程序的方式开发,然后应用于许多用途。 我们可以合理地在推理机上投入更多的努力。 确实,这项技术已经很先进了。
- 应用程序特有材料的可变部分以统一的方式编码在规则库中,并提供了开发、更改、测试和记录规则库的工具。 这使应用程序本身的大部分复杂性变得有规律。
爱德华·费根鲍姆 (Edward Feigenbaum) 表示,此类系统的力量并非来自越来越花哨的推理机制,而是来自越来越丰富的、更准确反映现实世界的知识库。 我认为这项技术提供的最重要进步,是将应用程序的复杂性与程序本身分离。
这该如何应用于软件任务中呢? 有很多方法:建议接口规则、为测试策略提供建议、记住 bug 类型的频率、提供优化提示等等。
以一个虚构的测试顾问为例。 在它最基本的形式下,这个诊断专家系统非常像飞行员的检查单,主要提供关于困难可能原因的建议。 随着规则库的开发,建议变得更加具体,对报告的故障症状有了更复杂的考量。 我们可以设想一个调试助手,最初提供非常笼统的建议,但随着越来越多的系统结构体现在规则库中,它生成的假设和推荐的测试变得越来越具体。 这样的专家系统可能与传统系统最根本的区别在于,它的规则库可能应该以与相应的软件产品相同的方式进行分层模块化,这样当产品被模块化修改时,诊断规则库也可以被模块化修改。
生成诊断规则所需的工作,是在为模块和系统生成测试用例集时无论如何都必须做的工作。 如果是以适当通用的方式完成的,具有统一的规则结构并且有可用的良好推理机,它可能实际上会减少生成初始测试用例的总劳动力,并在终身维护和修改测试中提供帮助。 以同样的方式,我们可以假设针对软件构造任务其他部分的其他顾问(可能很多而且很简单)。
许多困难阻碍了能帮助程序开发人员的实用专家顾问的早期实现。 我们想象的情景中的一个关键部分,是开发将程序结构规范变为自动或半自动生成诊断规则的简便方法。 知识获取的双重任务更加困难和重要:寻找能言善辩、自我分析能力强、知道自己为什么做事的专家;以及开发有效的技术来提取他们所知并将其提炼到规则库中。 建立专家系统的先决条件是拥有专家。
专家系统最强大的贡献必将是,将最优秀程序员的经验和积累的智慧为缺乏经验的程序员所用。 这并非小小的贡献。 最好的软件工程实践和平均实践之间的差距非常宽——也许比任何其他工程学科都要宽。 一个传播良好实践的工具将会是重要的。
“自动”编程 (“Automatic” programming)。 近 40 年来,人们一直期待并撰写关于“自动编程”的文章,即根据问题规范说明来生成解决问题的程序。 今天有些人的文章写得好像他们期待这项技术能提供下一个突破。
Parnas 暗示这个词被用作噱头,没有语义内容,他断言:
简而言之,自动编程一直是用一种比程序员当前可用的更高级语言进行编程的委婉说法。
从本质上讲,他认为在大多数情况下,需要给出规范的是解决方法,而不是问题本身。
但也能找到例外。 构建生成器的技术非常强大,并且在排序程序中经常被很好地利用。 一些用于集成微分方程的系统也允许直接说明问题。 系统评估参数,从解决方法的库中进行选择,并生成程序。
这些应用具有非常有利的属性:
- 问题很容易用相对较少的参数来表征。
- 存在许多已知的解决方法来提供备选库。
- 广泛的分析已经导致了在给定问题参数的情况下选择解决技术的明确规则。
很难看出这样的技术如何能推广到普通软件系统更广阔的世界中去,在那里具有如此整洁属性的情况只是例外。 甚至很难想象这种泛化的突破能如何发生。
图形化编程 (Graphical programming)。 软件工程博士论文的一个最喜欢的主题是图形化,或称可视化编程,即将计算机图形学应用于软件设计。 有时这种方法的承诺是建立在与超大规模集成电路 (VLSI) 芯片设计的类比上的,在后者中计算机图形学扮演了如此富有成效的角色。 有时这种方法的合理性是通过认为流程图是理想的程序设计媒介,并提供构建它们的强大设施来证明的。
没有任何令人信服的,更不用说令人兴奋的东西从这些努力中涌现出来。 我确信将来也不会有。
首先,正如我在其他地方所论述的,流程图是软件结构中非常糟糕的一种抽象。 实际上,它最好被视为伯克斯 (Burks)、冯·诺依曼 (von Neumann) 和戈德斯坦 (Goldstine) 试图为他们提出的计算机提供一种急需的高级控制语言的尝试。 在流程图今天被详细阐述成那令人可怜的、多页的、满是连接框的形式下,它已被证明作为一种设计工具基本上是无用的——程序员在编写了他们描述的程序之后,而不是之前,才绘制流程图。
其次,今天屏幕上的像素太少了,无法同时显示任何严肃细节软件图的范围和分辨率。 当今工作站所谓的“桌面隐喻”实则是一种“飞机座椅”隐喻。 任何曾在长途客车座位上被夹在两个肥胖乘客之间洗牌一腿文件的人,都会认出其中的区别——一个人一次只能看到很少的东西。 真正的桌面提供了对大约二十页的概览和随机访问能力。 此外,当创造力强烈爆发时,不止一个程序员或作家众所周知地抛弃了桌面,转而使用更宽敞的地板。 硬件技术必须取得非常大的进步,我们屏幕的视野才能满足软件设计任务的需求。
更根本的是,正如我前面指出的,软件是非常难以可视化的。 无论我们是对控制流、变量作用域嵌套、变量交叉引用、数据流、层次数据结构,还是对其他任何东西进行图解,我们都仅仅触及了错综复杂互锁的软件大象的一个维度。 如果我们把许多相关视图生成的所有图表叠加起来,将很难提取出任何全局的概览。 VLSI 的类比从根本上是误导人的——芯片设计是一个分层的二维物体,其几何形状反映了它的本质。 软件系统则不然。
程序验证 (Program verification)。 现代编程中的许多努力都投入到了测试和修复错误中。 难道不能通过从源头上(在系统设计阶段)消除错误来找到银弹吗? 能否通过在将巨大努力倾注于实现和测试之前先证明设计正确的这种截然不同的策略,来从根本上提高生产力和产品可靠性?
我不认为我们会在这里找到魔法。 程序验证是一个非常强大的概念,它对于诸如安全的操作系统内核之类的东西将非常重要。 然而,该技术并未承诺节省劳动力。 验证是如此繁重的工作,以至于只有少数几个实质性的程序曾被验证过。
程序验证并不意味着程序毫无错误。 这里也没有魔法。 数学证明也可能是错误的。 所以虽然验证可能会减轻程序测试的负担,但它不能消除它。
更严重的是,即使是完美的程序验证,也只能证明程序符合其规范。 软件任务中最难的部分是达成一份完整且一致的规范,而构建程序的很多本质工作,实际上就是在调试这个规范。
环境和工具 (Environments and tools)。 从爆炸性研究更好的编程环境的过程中还能期待获得多大的收益? 人们的本能反应是,能够获得巨大回报的问题是最先被攻击并已被解决的:分层文件系统、统一的文件格式以便拥有统一的程序接口,以及通用的工具。 针对特定语言的智能编辑器是尚未在实践中广泛应用的发展,但它们最多承诺的只是免除语法错误和简单的语义错误。
也许在编程环境中尚待实现的最大收益,是使用集成的数据库系统来记录必须由个别程序员准确回忆并在一个系统的合作者群体中保持更新的无数细节。
当然,这项工作是值得的,而且它肯定会在生产力和可靠性方面结出一些果实。 但就其本质而言,从今往后的回报必定是边际性的。
工作站 (Workstations)。 对于软件艺术而言,个人工作站的计算能力和内存容量必定会快速增加,这能期待带来什么收益? 那么,一个人能有效地使用多少 MIPS 呢? 今天机器的速度已经完全支持程序和文档的构成和编辑了。 编译可能还需要提速,但机器速度提高 10 倍无疑会让思考时间成为程序员一天中占据主导地位的活动。 事实上,现在似乎已经是这样了。
我们当然欢迎更强大的工作站。 但不能指望它们带来神奇的提升。
戳破泡沫(Hopes for the Silver)。
在这部分,作者毫不留情地把当时(甚至到现在依然)被寄予厚望的各种流行技术挨个点评了一遍,告诉你为什么它们都不可能成为“银弹”:
- 面向对象编程(OOP):治标不治本。作者认可面向对象是一个真正的进步,它通过数据抽象和模块化,帮我们去掉了大量杂乱的语法结构。但这只是扫清了代码表达上的障碍,系统设计本身的内部复杂逻辑(本质困难)一点儿也没减少。
- 人工智能与专家系统:辅助而非替代。作者一针见血地指出,开发软件最难的是“决定系统应该干什么”(理清核心逻辑),而不是“怎么把它写出来”。因此通用 AI 无法带来革命。不过,把顶尖高手的经验提取出来做成“专家系统”(比如智能调试助手)来辅助新手,倒是一条非常有价值的务实路径。
- “自动”编程:局限性太大。那种“只要输入需求,系统就自动吐出代码”的构想,基本只在特定且参数简单的极窄领域(比如用生成器写排序代码)有效。现实中的普通软件系统错综复杂,根本无法套用这种模式。
- 图形化编程与形式化验证:脱离实际。作者直接把图形化编程毙了,因为软件的内部结构是多维且交错的,二维的屏幕和图形根本表达不了这种复杂性。至于程序验证,不仅成本极高,而且它最多只能证明“代码写得符合需求文档”,却完全无法证明“一开始写的需求文档就是对的”。
一句话总结这部分的思想:工具再怎么进化,也代替不了人脑在复杂业务和架构上的深度思考。
针对概念本质的有希望的攻击 (Promising Attacks on the Conceptual Essence)
尽管没有任何技术突破承诺带来我们在硬件领域所熟悉的各种神奇结果,但现在仍有大量出色的工作正在进行,并且承诺会取得稳定虽然不那么壮观的进展。
所有针对软件开发过程中附属性困难的技术攻击,都从根本上受限于生产力方程式:
(注:原文此处存在方程式逻辑,大意为任务耗时等于各部分的频率乘以耗时)
如果正如我相信的那样,任务的概念组件现在占据了大部分时间,那么在那些仅仅是概念表达的任务组件上进行再多活动,也无法带来生产力的大幅提升。
因此,我们必须考虑那些解决软件问题本质(即这些复杂概念结构的形成)的攻击手段。 幸运的是,其中一些非常具有前景。
购买与构建 (Buy versus build)。 构建软件最激进的可能解决方案就是完全不构建它。
随着越来越多的供应商为令人眼花缭乱的各种应用提供越来越好的软件产品,每天做到这一点都变得更加容易。 就在我们软件工程师在生产方法论上苦苦挣扎的同时,个人电脑革命已经为软件创造了不仅是一个,而是许多个大众市场。 每个报摊上都有按机器类型分类的月刊杂志,广告和评论着数十种从几美元到几百美元不等的软件产品。 更专业的来源为工作站和其他 Unix 市场提供了非常强大的产品。 甚至软件工具和环境也可以买现成的。 我曾在其他地方提出过为独立模块建立一个市场。
任何这种产品都比从头构建更便宜。 即使花费 10 万美元,购买一个软件的成本也仅仅相当于大约一个程序员一年的成本。 而且交付是即时的! 至少对于真正存在的产品,对于开发者可以向潜在客户介绍满意的用户的产品来说是即时的。 此外,这样的产品往往比本土开发的软件有更好的文档和一定程度上更好的维护。
我认为,大众市场的发展是软件工程中最深远的长期趋势。 软件的成本一直都是开发成本,而不是复制成本。 即使只在少数几个用户之间分担这种成本,也能极大地降低每个用户的成本。 另一种看待它的方式是,使用 n 个软件系统的副本有效地将其开发者的生产力乘以 n。 这是该学科乃至国家生产力的提升。
当然,关键问题是适用性。 我能否使用现成的软件包来完成我的任务? 在这里发生了一件令人惊讶的事情。 在 1950 年代和 1960 年代,一项又一项的研究表明,用户不会在工资单、库存控制、应收账款等应用中使用现成的软件包。 需求太过于专门化,个案之间的差异太大了。 在 1980 年代,我们发现这类软件包需求旺盛并被广泛使用。 是什么改变了?
实际上并不是软件包改变了。 它们可能比以前稍微更通用、更可定制一些,但改变不大。 应用程序本身也真的没有改变。 如果非要说有什么变化的话,那就是今天的商业和科学需求比 20 年前更多样、更复杂了。
最大的变化在于硬件/软件成本的比例。 1960 年购买 200 万美元机器的买家觉得他买得起再花 25 万美元定制一套工资单程序,这是一套能轻松且无干扰地融入计算机敌对社会环境的程序。 而今天 5 万美元办公机器的买家绝对无法承受定制的工资单程序;因此,他们调整自己的发薪程序去适应现有的软件包。 计算机现在是如此司空见惯,即使还不那么受欢迎,以至于这种适应被当作理所当然的事情接受了。
我关于软件包的泛化多年来变化不大的观点有一些戏剧性的例外:电子表格和简单的数据库系统。 这些强大的工具,回过头来看是如此显而易见,却出现得这么晚,它们适用于无数的用途,其中一些相当非正统。 现在关于如何用电子表格解决意想不到的任务的文章甚至书籍层出不穷。 大量过去会用 Cobol 或 RPG 编写为自定义程序的应用程序,现在常规性地使用这些工具完成。
许多用户现在每天都在自己的电脑上操作各种各样的应用程序,而从未写过一行程序。 实际上,许多这类用户无法为他们的机器编写新程序,但他们却熟练地利用它们解决新问题。
我认为当今许多组织最强大的软件生产力策略,就是为一线的、对计算机外行的知识工作者配备个人电脑和良好的通用写作、绘图、文件和电子表格程序,然后放手让他们去做。 同样的策略,加上简单的编程能力,也适用于数百名实验室科学家。
需求精炼与快速原型开发 (Requirements refinement and rapid prototyping)。 构建软件系统中最难的单一环节,就是准确决定要构建什么。 没有任何其他概念性工作比建立详细的技术需求(包括到人、到机器、到其他软件系统的所有接口)更加困难。 没有任何其他部分如果做错的话会如此严重地损害最终的系统。 没有任何其他部分在事后纠正起来更加困难。
因此,软件构建者为客户做的最重要的一项功能,就是迭代地提取和提炼产品需求。 因为真相是,客户并不知道他们想要什么。 他们通常不知道必须回答什么问题,并且他们几乎从未想过问题必须规范到何种细节程度。 即使是简单的回答——“让新软件系统像我们旧的手工信息处理系统一样工作”——事实上也过于简单了。 客户绝不是想要完全那样的东西。 此外,复杂的软件系统是动作的、移动的、运行的东西。 这种动态行为很难想象。 所以在规划任何软件活动时,必须允许作为系统定义一部分的客户和设计者之间进行广泛的迭代。
我想更进一步断言,对于客户,即使是与软件工程师合作的客户来说,在没有构建并尝试过他们正在详细说明的产品的一些版本之前,完全、准确和正确地详细说明现代软件产品的确切需求,实际上是不可能的。
因此,当前最有希望的技术努力之一,也是攻击软件问题的本质而非附属性质的一项努力,就是开发用于系统快速原型的方法和工具,以此作为需求迭代规范的一部分。
原型软件系统是一个模拟重要接口并执行预期系统主要功能的系统,但它不一定受到相同硬件速度、大小或成本限制的约束。 原型通常执行应用程序的主线任务,但不尝试处理异常、正确响应无效输入、干净地中止等。 原型的目的是使指定的概念结构变得真实,以便客户可以测试其一致性和可用性。
如今许多软件采购程序都建立在一个假设之上,即可以在事先详细说明一个令人满意的系统,对其构建进行招标,然后让它被构建出来并安装。 我认为这个假设从根本上是错误的,许多软件采购问题就源于那个谬误。 因此,如果不进行根本性的修正——提供原型和产品的迭代开发与规范,这些问题就无法修复。
增量开发——“生长”而非“建造”软件 (Incremental development−grow, not build, software)。 我依然记得1958年当我第一次听到一位朋友谈论“构建”一个程序,而不是“编写”一个程序时,我感受到的震撼。 在那一瞬间,他拓宽了我对整个软件过程的看法。 这种隐喻的转变是强有力且准确的。 今天我们明白软件结构与其他建筑物建造过程有多么相似,我们自如地使用这个隐喻的其他元素,例如规范、组件组装和脚手架。
“建造”的隐喻已经过时了。 是时候再次改变了。 如果像我所相信的那样,我们今天构建的概念结构太过复杂以至于无法在事先准确详述,且太复杂以至于无法毫无缺陷地建造出来,那么我们必须采取一种截然不同的方法。
让我们转向大自然,研究生物体中的复杂性,而不仅仅是人类死板的作品。 在这里,我们发现了一些复杂程度令我们心生敬畏的结构。 单单是大脑就复杂得无法绘制,强大得无法模仿,丰富多样,具有自我保护和自我更新的能力。 秘密在于它是生长出来的,而不是被建造出来的。
我们的软件系统也必须如此。 几年前,Harlan Mills 提出,任何软件系统都应该通过增量开发来生长。 也就是说,首先应该让系统运行起来,即使它除了调用一组适当的虚拟子程序外,不做任何有用的事情。 然后,一点一点地充实它,这些子程序依次被开发为具体的动作,或者对下一层空存根的调用。
自从我开始敦促我的软件工程实验室课程中的项目建设者使用这种技术以来,我看到了最具戏剧性的结果。 在过去的十年中,没有什么能像这样彻底地改变了我自己的实践或其有效性。 这种方法需要自上而下的设计,因为它是一个自上而下生长软件的过程。 它允许轻松地回溯。 它有利于早期原型。 每一个添加的功能和对更复杂数据或情况的新规定,都是从已有的基础上“有机生长”出来的。
这对士气的影响是惊人的。 即使是一个简单的运行系统,也能极大地激发热情。 当新的图形软件系统在屏幕上显示出第一张图片时,即使仅仅是一个矩形,努力也会加倍。 一个人在过程的每个阶段,始终拥有一个工作系统。 我发现,团队在四个月内能够生长出比他们能建造出的复杂得多的实体。
同样的好处也可以在大型项目上实现,就像在我的小型项目上一样。
伟大的设计师 (Great designers)。 如何改进软件艺术这一核心问题,一如既往地,集中在人身上。
我们可以通过遵循良好的实践而不是糟糕的实践来获得好的设计。 良好的设计实践是可以教授的。 程序员是人群中最聪慧的那部分,因此他们可以学习良好的实践。 因此,美国的一个主要推动力是颁布良好的现代实践。 新的课程、新的文献、新的组织(如软件工程研究所)如雨后春笋般涌现,以将我们的实践水平从差提高到好。 这完全是正确的。
然而,我不认为我们可以用同样的方法迈出向上的一步。 虽然糟糕的概念设计和好的概念设计之间的区别可能在于设计方法的合理性,但好的设计和伟大的设计之间的区别肯定不是这样。 伟大的设计来自伟大的设计师。 软件构建是一个创造性的过程。 健全的方法论可以赋予创造性思维力量和解放,但它无法点燃或启发单调乏味的苦工。
这差异绝非细枝末节——这就好比萨列里和莫扎特。 一项又一项的研究表明,最顶尖的设计师创造出的结构更快、更小、更简单、更干净,并且付出的努力更少。 伟大和一般之间的差距接近一个数量级。
稍微回顾一下就会发现,虽然许多精良、有用的软件系统是由委员会设计和由多部分项目构建的,但那些让粉丝充满激情的软件系统,却是出自一个或几个设计大脑、伟大的设计师之手。 想想 Unix、APL、Pascal、Modula、Smalltalk 界面,甚至是 Fortran;将它们与 Cobol、PL/I、Algol、MVS/370 和 MS-DOS 相比。
因此,虽然我强烈支持目前正在进行的技术转移和课程开发努力,但我认为我们能发起的单一最重要努力,就是制定培养伟大设计师的方法。
没有哪个软件组织能够无视这一挑战。 优秀的管理者虽然稀缺,但绝不比优秀的设计师更稀缺。 伟大的设计师和伟大的管理者都非常罕见。 大多数组织花费大量精力去寻找和培养管理人才的苗子;但我不知道有哪个组织花了同等精力去寻找和培养那些最终决定产品技术卓越性的伟大设计师。
我的第一个建议是,每个软件组织都必须确定并宣告:伟大的设计师对组织的成功与伟大的管理者同等重要,并且他们有期望得到类似的培养和回报。 不仅是薪水,而且认可的特权——办公室大小、家具、个人技术设备、差旅资金、员工支持——也必须完全对等。
如何培养伟大的设计师? 篇幅不允许长篇讨论,但有些步骤是显而易见的:
- 尽可能早地系统性识别顶尖设计师。 最优秀的往往不是最有经验的。
- 指派一名职业导师负责这位潜力人才的发展,并保持仔细的职业档案记录。
- 为每位潜力人才制定并维护一份职业发展计划,包括精心挑选的与顶尖设计师的学徒经历、高级正规教育的片段以及短期课程,所有这些都穿插以独立设计和技术领导的指派任务。
- 为正在成长的设计师提供相互交流和激励的机会。
既然奇迹不存在,我们该用什么务实的办法来死磕软件开发的本质困难?
作者给出了四副直到今天依然极其奏效的“药方”:
1. 能买现成,绝不自己造轮子 (Buy versus build) 最激进也最有效的开发方式,就是压根不去开发。市面上现成的软件、组件或工具,无论从研发分摊成本还是交付时间上看,都远优于从头写起。把团队极其宝贵的脑力节省下来,去攻克真正无法替代的核心业务逻辑。
2. 用快速原型挖掘真实需求 (Requirements refinement and rapid prototyping) 软件开发中最难的一环,是精准地决定到底要开发什么。一个残酷的真相是:客户在看到实物之前,根本不知道自己想要什么。因此,不要妄想一开始就写出完美的说明书,而是要快速搭个能跑主线流程的“毛坯房”原型,让客户去试用、去挑刺,在迭代中把需求磨清楚。
3. 让软件“长”出来,而不是“盖”出来 (Incremental development) 像大脑这样极其复杂的结构是自然生长出来的,不是像搭积木一样盖出来的。开发软件也是同理,必须采用增量开发:先搭一个只有基本骨架、能调通跑起来的最小可行系统,然后再一点点把肌肉(具体功能和异常处理)丰满上去。这种自顶向下的“有机生长”不仅能随时回溯,还能让团队每天都看到一个活生生跑着的系统,极大提振士气。
4. 像重视高管一样培养顶尖设计师 (Great designers) 流程和规范最多只能守住底线,把“烂设计”变成“好设计”;但“伟大的设计”永远只能来自顶尖的聪明脑袋。最优秀的设计师产出的系统往往更快、更小、更干净,与平庸设计的差距能达到一个数量级。企业绝不能只盯着培养管理层,必须投入同等级别的资源、导师、薪酬和特权,去尽早发掘和重赏那些真正懂设计的技术天才。
回过头来看,这四副药方分别对应了我们今天习以为常的开源生态与云服务、敏捷开发与MVP(最小可行性产品)、持续集成与灰度迭代,以及技术专家(Tech Lead/架构师)路线。老先生在几十年前,就已经把软件工程的底层逻辑彻底看透并指明了方向。
NO SILVER BULLET -
ESSENCE AND ACCIDENTS OF SOFTWARE ENGINEERING

浙公网安备 33010602011771号