Safe as Houses

scales over which architects and software engineers work, but scales of what? .... In the simplest, procedural, languages these data structures are modi-.
91KB taille 3 téléchargements 287 vues
Andrew Cooke

Safe as Houses

Background

This article was written (unsolicited) for the London Review of Books (LRB), but it “wasn’t quite general enough” — which is a pity, because I was hoping it would give people with no technical knowledge a better understanding of programming. Just as I (a scientist and engineer) enjoy reading in the LRB about the craft of writers and artists, I hoped that people with a background in the arts might enjoy an article on my own profession. If you have read the LRB you will already know that the reviews frequently have only a tenuous connection with their subjects. For the rest of you: do not expect a traditional book review. My aim is to introduce some general, but important, ideas in modern computer science.

Design Design Patterns: Elements of Reusable Object–Oriented SoftPatterns ware; Erich Gamma, Richard Helm, Ralph Johnson and John Vlissides; Addison Wesley 1994. Houses

My computer crashes regularly, but the building I work in remains standing. Why are programs not as safe as houses? Perhaps houses are simpler. They are certainly more familiar – so familiar that it is difficult to look at them with innocent eyes. Walls contain thousands of bricks (embedded in a matrix made from a water-based paste that sets hard and somehow won’t dissolve in the rain). The central heating is controlled by a box with a couple of buttons; buttons that are connected to a small computer. And how does the Yale lock in the front door work? We could make an objective comparison by considering the range of scales over which architects and software engineers work, but scales of what? Programs are best considered as structures in time, rather than space: the number of calculations made each second on a modern PC is similar to the number of pieces needed to construct a house from its smallest component alone (a panel pin, say). So, by this rough calculation, houses and programs are similar in their complexity1 . Of course, houses are not fabricated entirely from small nails, but nor are programs written in the machine code that controls the heart of a computer; buildings and software are both designed using a variety of components with different levels of complexity. Just as architects ignore the internal wiring of air conditioning units, software engineers write programs that manipulate abstract components like lists, buttons and windows. 1 De Lillo’s Underworld covers 50 years of history by focusing on fragments of people’s lives, a similar range of scales again and, despite Microsoft’s greatest efforts, aesthetically more impressive than my computer’s screen. That it is the work of one man, rather than teams of programmers, says as much about the richness of human language as it does about De Lillo’s literary ability.

If houses and programs are similar in scale they also have important differences. Software is more flexible. Not only is a program expected to change more rapidly than a building, but the variations across the program are strongly interdependent. Walking from the kitchen to the bedroom will change the decor, but walls, foundations and roof are the same. A single mouse click in a word processing package, however, could save the current document to disk, load a new document into memory, update the display, and start checking the spelling. Worse still, describing structures accurately appears to be more difficult when they vary in time rather than space. Not all differences make programming more difficult than building. Programs can be tested and corrected as they are written, which helps to improve reliability, but also removes the meatworld distinction between architect and builder. In contrast, there is little scope for changing physical structures once they are complete (the Millennium Bridge to the new Tate, which is being modified to reduce swaying, is so exceptional that it made national news). If programs and buildings are so different, why bother to compare them? Because sufficient similarities remain for Design Patterns, the most influential recent book on software engineering, to be inspired by an analysis of architecture (A Pattern Language: Towns, Buildings, Construction; Christopher Alexander et al.; OUP 1977). This architectural approach to software would not have been possible without Object Oriented (OO) design, a particularly modular way of constructing programs. Questions

Why was this book so important? What are objects? How do they help programmers? Underlying all of these: what are programs?

Programs

Programs are texts that specify how a computer should act. They are limited by the language in which they are written and, with a few honourable exceptions, that language cannot be extended from within the program. More exactly, it is not possible to change the underlying semantics: Twas brillig and the slithy toves might2 be acceptable, but Head haught eyes white fixed front old ping last murmur one second is not. This distinction between programs and languages is arbitrary, but it has some useful advantages: writing a new language tends to be harder than writing a program; re-using a language encourages investment in making the language implementation more efficient; people only have to learn a small number of languages to read a large number of programs. So programming can progress in two directions, either better algorithms can be developed or, more radically, the language in which the algorithms are expressed can be improved. The first of these is comparable to building better houses, the second to finding a completely new way for people to live. Although Alexander might argue that how we express design is as significant for architects as it is for software engineers, the 2 With appropriate definitions of brillig and toves. Of course, practical programming languages are necessarily much simpler than English, or any other natural language.

constraints of language are much more flexible than the laws of physics (or the available sizes of bricks) that restrict architecture. To understand these limitations we must look more closely at programming languages. The electronic circuits within a computer are switched by machine code. Programs written in more abstract languages must be translated down to this level. This process is automated and constrains any language to be consistent with the underlying physical machine: it is not acceptable for a programmer to invoke miracles. Despite this, the variety of languages is bewildering; each a small experiment in how to build software3 . By tracing a path through some of these languages we can begin to understand the rationale behind OO programming. The route we will take is not an official history of any one idea, but a more scenic route that explores how these restricted, artificial languages can be developed into elegant, expressive tools. Names

It is difficult to imagine instructions that do not involve names and unsurprising, therefore, to find that names are present in programming languages, or that relative names (the equivalent of “that value” or “this result”) are critical for abstraction. Without names a program would always process the same, fixed data; by modifying the value of a name, which can change to identify different data, the program becomes generic. This is our first illustration of something we will call the abstraction principle: languages become more expressive when different entities – in this case different data – can be handled in the same way. Following the same principle, using names to identify other names (and lists of names) gives further flexibility, allowing a hierarchy of data to be constructed and imposing an order on the values in a program. In the simplest, procedural, languages these data structures are modified by groups of instructions. Typical instructions might, in response to the user pressing a key, trace a path down the hierarchy of data associated with the display, through the names of windows and then of one window’s contents, to eventually modify a particular character on the screen. This is how typing is displayed, letter by letter. A program written in a procedural language consists of many interconnected data structures and the instructions that modify them. The main difficulty with this approach may already be clear: as programs become larger the hierarchies of data become more complex and unwieldy. Instructions written by one programmer should be able to make the correct changes to data that he understands, but another programmer may have added an extra piece of information, one that she expects to be altered in an appropriate way. If this does not happen then, when the program is executed, the data become corrupt, inconsistencies multiply, and the program crashes. A parallel can be drawn between programs in procedural languages and large bureaucracies. Files (data) are passed between departments (groups of instructions) where they are modified and extended. This 3

Using new languages to express new ideas is a refreshing change from everyday life, where new and old ideas must share the same space.

may be acceptable when the bureaucrats are intelligent enough to act on their own initiative, but when they follow instructions by rote – as in a computer – the system becomes fragile. Functions

If the hierarchies of data are fragile, how can they be removed? Programs calculate many intermediate values. Simply adding a column of numbers, for example, involves not just a running total, but also a counter that lets instructions refer to each entry in the column in turn. Functional languages are constructed so that it is impossible to describe values that change and so banish both the running total and the counter. This is a drastic restriction. Switching from a procedural to a functional language can be a disturbing process – modes of expression that were once natural (like counting through a list: first item; second item; third...) become unthinkable. An experienced programmer moves unconsciously from an algorithm in the abstract to its concrete expression in a particular language, so it comes as an unpleasant, dizzying surprise when, mid-thought, the language stops. A deeper frustration than that felt when learning a natural language and struggling for “the right expression”, here the problem is not the lack of a suitable word, but an utter blank when trying to think in a certain way. On the other hand, learning to think in a new way can be rewarding. Software can still be written in functional languages, with instructions that are often elegant and succinct. Instead of counting through a list, for example, a group of instructions will refer to “the next item” and will progress down the list by repeatedly invoking the same instructions on the “the remaining items”. In this way programs become nested, recursive instructions, each process taking the output from a preceding calculation. The bureaucracy of procedural programming is replaced by a compact production line. This demand-driven programming style is surprisingly effective. Intermediate values no longer clutter the design and so cannot be corrupted. Software becomes more reliable at the expense of a certain rigidity in how the program is written. Since software can still be written in functional languages we might expect that some new feature has been introduced to compensate for the restriction on modifying data. Running the abstraction principle in reverse, so to speak, we can look for a new unifying principle that has provided a compensating expressiveness. Take the instructions to run through a list: they will include something like “do this on the remaining items until none are left”. The word “this” does not refer to data, but to instructions. Previously, in procedural languages, only data were named in this way, but here we can see that functional languages have unified the treatment of data and instructions.

Context

Parcelling instructions up, naming them, and passing them around within a program introduces problems with context. Consider the equivalent in our own world: sealed orders. If you receive the orders to “shoot Bob”, who do you shoot? You could simply ask the name of each person that you meet, but are unlikely to find the target that the writer intended.

Instead, you will try to remember if you and the writer share a common acquaintance by that name4 . In the same way, instructions in most functional languages are associated with the context in which they were written rather than that in which they are executed. Changes

Contexts also provide a way of reintroducing mutable values. If you read a note that asks you to think of a number, double it, add your age and subtract your shoe size, your answer will depend upon your own particular circumstances. Identical instructions to someone else will not be affected; they will calculate with different numbers. So context can be used to both provide and protect changeable data for specific sets of instructions. Functional languages which allow values to change are termed impure. Doing the calculation above in an impure functional language is equivalent to writing down the number you first think of, then tippexing that out to replace it with its double, then tippexing that out and writing down the sum, etc etc. All this scribbling remains on one copy of the instructions, inaccessible to someone following a photocopied set of instructions elsewhere.

Reprise

We’ve come a long way. Starting with simple procedural languages in which instructions are separate from data we’ve seen how names allowed algorithms to become generic and how naming names and lists of names introduced (fragile) hierarchies of data. The Orwellian functional languages removed the ability to change data but gave instructions equal status. This raised the question of context which provided a mechanism for safely managing mutable data. Which, at last, brings us back to objects. Many very different languages claim to be “Object Oriented”, but the essential feature that they all share is the grouping together of mutable data and instructions to form objects, as with contexts in impure functional languages. The instructions mediate access to the data, protecting the values from corruption and providing a high-level interface for other instructions within the program. Both of these properties help improve the quality of software. Protecting values from corruption avoids the fragile hierarchies of data that we saw when programs were written in procedural languages. Providing an interface to the rest of the program allows objects to be treated in a more abstract manner than the values they contain. Remember the millennium bug? A lot of money was spent paying people to read programs line by line, checking how instructions modify dates. If an OO language had been used then it would only have been necessary to check the instructions associated with date objects5 . So by group4

If you are a conscientious assassin you may also check whether the first person that comes to mind is the only Bob that you both know – to avoid similar problems programming languages have clearly defined rules for handling duplicate names. 5 In most OO languages similar objects take instructions from an archetype called a class, so only a few lines of instructions, inside the date class, would need to be checked.

ing together operations and data, objects make the software easier to understand and maintain. This provides the software engineer with the tools to both design and build very modular programs. Just as an architect does not need to understand how the circuits inside a central heating controller works, a software engineer does not need to understand all the instructions inside an object; the important actions, like the buttons on the controller’s box, are presented via a simple interface. In a curious historical progression programming has evolved from bureaucracy and production lines to communist cells: objects that are largely independent, communicating only through selected intermediaries. Architecture

When designing a building an architect must consider how various components interact: if a door is placed there then the weight of the roof can be taken here, giving an open reception area, for example. Objects allow software engineers to assemble programs in a similar manner. Design Patterns catalogues the most common arrangements of objects within programs, just as Alexander catalogued the design patterns of architects. When designing a program a series of abstractions must be made that bridge the distance between the formal specifications of the system and the instructions and data that will implement them. By identifying and naming these abstractions Gamma et al. have provided software engineers with a common vocabulary for discussing their designs. Any discussion of OO software on the Internet will be peppered with terms like “Singleton”, “Observer”, or “Adapter”; all patterns from this book. Patterns also function as tools that allow programmers to study and improve their own programs. The common response to reading descriptions of patterns is a recognition of habits that have been learnt through experience, but never considered important enough to be given serious thought. Design Patterns is a survey of the craft of programming, a formal record and analysis of what were, until publication, tricks of the trade.

You

Finally, then, if objects bring software and architecture closer together, why do we hear that pensions were not paid, or a rocket lost, or a balance incorrect, because of computer error and not because the building in which these things are arranged has collapsed? Practice, unfortunately, is not as simple as theory. We’ve followed two related developments. First, an increasing emphasis has been put on the integrity of data. Second, languages have become more expressive through increased abstraction. At the same time, although we have not followed them, there have been improvements in the handling of types6 , threads7 and garbage collection8 ; all are critical for quality software, all are supported in the best current languages, 6

Preventing the programming equivalent of comparing apples and oranges. When several processes work in parallel. 8 Allowing the computer to decide which parts of the memory can be re-used for new data 7

but only objects have been accepted by the commercial mainstream. Functional languages, for example, hardly exist outside academia. Rather than following a path of steady improvement, object oriented ideas have been bolted on to the old procedural languages. Perhaps the most popular OO language (C++) is also the least elegant, extending a much older language infamous for encouraging programming errors. This transplant has left scars: instead of benefiting from the abstraction principle, in which a reduced number of more general concepts provides increased flexibility, the language is extended by making it more complicated. If C++ were a car, it would be a Model T Ford with a new engine, straight from a Formula 1 car, but without the brakes, suspension or bodywork to match. No wonder it comes off the road at the first bend. There is no single solution that will produce reliable software. Programming languages are still being improved. But the best tools are lying unused because the market quietly accepts second best: you continue to buy software that doesn’t work. Would you live in a house as unreliable as your word processor? Things could be better. Complain.

Contact

Andrew Cooke, [email protected] I am always happy to receive contructive comments, but please remember that anyone who writes “popular science” has to select and simplify (and provoke a little). (About 3000 words)