Most software design is lousy. Most software is so bad, in fact, that if it were a bridge, no one in his or her right mind would walk across it. If it were a house, we would be afraid to enter. The only reason we (software engineers) get away with this scam is the general public cannot see inside of software systems. If software design were as visible as a bridge or house, we would be hiding our heads in shame.
We would not accept a new house with sloping floors, holes in the ceilings, nails sticking out of the walls, and an outrageous price — even if it minimally met basic needs. We would not be content with the explanation: “Well, it has a front door, which usually opens. You can find your way to the kitchen, but watch out for the nails. The holes in the ceiling don’t really leak. And sure it ran 300% over budget, but houses often do.” Rather than crooked floors, the software manifestations of poor design are redundancy, unnecessary performance bottlenecks, intertwined bugs that cannot be fixed, impenetrable code, and other ills. Unfortunately, we often accept software in just such a state. Regularly, companies release code like this to external and internal customers. And customers accept delivery. Businesses pay billions of dollars per year for this kind of software during mergers and acquisitions.
|Compared to a poorly designed solution, well designed software meets users’ needs more closely, can be completed more quickly, works more reliably, and costs far less money initially and throughout its life.|
This article is a challenge to engineers, managers, executives and software users (which is everyone) to raise our standards about software. We should expect the same level of quality and performance in software we demand in physical construction. Instead of trying to create software that works in a minimal sense, we should be creating software that has internal beauty. Beautiful programs work better, cost less, match user needs, have fewer bugs, run faster, are easier to fix, and have a longer life span. Raising our standards for software is not a luxury to be reserved for programmers with extra time on their hands. Creating aesthetically pleasing software is crucial to creating better and less expensive software — in fact, they are one and the same endeavor.
Software aesthetics is a qualitative judgment, but, like physical architecture, it includes some general principles. All beautiful software has the following properties.
- Appropriate form
- System minimality
- Component singularity
- Functional locality
Just as a building should be designed to blend with and enhance its site, software should work well with its surroundings. The site for a building is the slope of its land, its orientation to the sun, its local weather, other buildings nearby, etc. The “site” for a software system is its computer hardware, the operating system, any middleware (such as database or security systems), other software applications on the computer, and the style of the intended users.
Examples of software systems that cooperate with their environment include:
- A pocketsize electronic appointment book that works within the limitations of its small black and white display screen, and still manages to display information clearly.
- An income-tax filing system that allows people to submit returns through the Internet, because many users already have Internet access.
- A reading tutorial for second-grade students that runs quickly on older computers, since schools are likely to have old, slow computers rather than the latest technology.
A more technical example is a large-scale document management system I worked on. The software architect cleverly designed the storage and retrieval methods to take into account the operating properties of the computer’s disk drive, so the whole system worked as efficiently as possible. In all of these cases, the software is working in cooperation with its environment — just as a well-designed building works in harmony with its site.
The internal design of a software system should reflect and create its external functions. Beautiful buildings merge form and function, and good (beautiful) software does the same. Why does this matter though? As long as a software system works correctly, does it matter what internal design achieves that end? It does matter. An internal structure that acknowledges the external features is more likely to create those features correctly. A software form that follows the software’s function also is likely to be simpler, since the external features arise from the internal design rather than being forced on top of the design. A software system whose form does not mirror its function forever will be difficult to debug, will have more bugs, will be difficult to extend and modify, and likely will perform is core functions poorly.
Since we cannot see or touch software, it is sometimes difficult to judge whether a software system’s form matches its function. All software has a definite form however. Consider an accounting system. Business accounting consists of several well-defined operations: purchasing, billing, payroll, general ledger, etc. To a large extent these functions are separate, but there is some overlap among them also. A software system for business accounting had better reflect these logical accounting operations in its internal design. There should be clearly defined parts of the software for purchasing, billing, payroll, etc. There also should be clear overlap in the software where the logical operations overlap. Without such a software design, it will be impossible to change just one aspect of payroll without affecting other, unrelated operations. Appropriate form also will allow engineers to make a change in an area that overlaps several others (such as general ledger) and have it correctly change all related operations.
Software design that reflects external function implies there are no fixed rules for good programming technique. For many years, programmers were taught that global variables and GOTO statements are poor programming practice. In some situations though, these constructs may be exactly what the software needs to marry form with function. It would be incorrect to propose the rule of building construction: “Always use teak wood instead of pine.” Teak is an excellent wood, but sometimes pine is the right choice. In the same way, the right question about a programming technique is: “Is this an appropriate design for the external features we are trying to achieve?”
Imagine a house being built on a street that contains public water supply and electrical service. Now suppose the builders of the house dig a private well and construct their own power generating station. On asking them why they did this, they reply, “We wanted our own well, so it would be just they way we like it. And we didn’t know there was electricity on the street.” This extra work and expense in building a house would be unacceptable. There is nothing wrong with including a well or power generation at a house — if the home needs them. It is horrible design to include them for little reason or out of ignorance about the public services. Good building design keeps the building as small as possible, by using available external resources.
Beautiful software follows the same principle — it is as small as it can be, by using existing computing resources where possible. It is the responsibility of every software architect and engineer to understand the computing system they are using, and to take advantage of its facilities whenever possible. Software should contain just what it needs to, but no more.
Some years ago, I worked on a project that exemplified everything that can be wrong with software in this regard. The project was a financial information system on Digital’s VAX minicomputers. In dozens of places throughout the code we chose to “roll our own” method of doing something, rather than use a facility built into the VAX. We wrote our own sorting procedures, screen input/output package, source code control, and automated build tools — all of which were provided on the VAX. I am embarrassed to admit we even wrote our own run-time debugger — although the VAX contained an excellent one. In each case our reason was hubris, ignorance, or laziness to learn more about the computer we were using. The resulting system was many times larger than it needed to be, ran slowly, took a long time to create, and cost far too much money.
In general, beautiful buildings contain one room for each purpose, and that room gets the function correct. For example, in most houses there is one master bedroom, which contains everything the room needs. It would be exceedingly poor design for a house to contain four master bedrooms because the builders could not make any of them complete. Or worse, if the builders forgot they had already created a master bedroom and built another one, then forgot about the second and built another, etc.
Of course, some large buildings may require more than one space of each type. For example, an office building with 5000 workers may need more than one cafeteria. It is poor architecture, however, unnecessarily to duplicate a space.
In the same way, well-designed software generally contains one instance of each component, and makes the component work correctly. The opposite of this is redundancy and is well recognized as poor software architecture. To cite a specific example, imagine a software system containing three drivers for one printer. Any change to the printer would require changes to three pieces of software. It is remarkable, however, how many software systems contain unnecessary, redundant code. In many cases, it is because one programmer does not know another engineer already solved the same problem. (The builders forgot they already had a master bedroom.)
Good building design places related items together. The equipment and supplies for preparing meals usually are in one room. Plumbing, electrical and heating devices often are together in the basement. The coat closet is near the front door. Note that houses do not have to be designed this way. A house could be built with a refrigerator in the attic, an oven in the living room, and a dishwasher in the bedroom — but it would be poor design to do so.
Good software design follows the same principle of placing related items together. When software is constructed this way, it is easy for members of the project team to understand the software — because the structure makes sense. It is easy to fix bugs and make changes — because the relevant code is located in an obvious place. It is easier to replace an entire feature with a new approach — because there is one distinct piece to replace.
Functional locality implies levels of abstraction. Each room in a house has a purpose, with related items for that purpose in the room. At a broader view though, good building architecture places related rooms together. Rooms for daytime use often are at one end of a house (or on one floor). Rooms for nighttime use are at the other end of the house or upstairs. Office buildings place mechanical infrastructure rooms in one area, away from business people. Good software architecture also uses levels of abstraction to achieve functional locality. In an operating system, all the low-level code for audio effects (sound) should be in one place. Within each higher-level feature (such as the file system), the code for those sound effects should be together. Moving up a level, the code for related features (such as the file system and Internet Explorer in Windows) should be together.
It is possible to have two different versions of a software program that function in exactly the same way, and have the same internal design and construction from a technical perspective, but which are vastly different in their human readability. Consider these examples.
Code Fragment A
Const MIN_AGE = 0
Const MAX_AGE = 120
Const RETIREMENT_AGE = 65
Dim AgeString As String
Dim AgeNumber As Integer, YearsToRetirement As Integer
AgeString = Inputbox$(“Please enter your age.”, “Age?”, “”)
AgeNumber = Cint(AgeString)
If AgeNumber < MIN_AGE Or AgeNumber > MAX_AGE Then
Msgbox “Are you sure you entered the right age? It should be between ” _
& MIN_AGE & ” and ” & MAX_AGE & “.”
If AgeNumber < RETIREMENT_AGE Then
YearsToRetirement = RETIREMENT_AGE – AgeNumber
YearsToRetirement = 0
Code Fragment B
Dim x As String,A2 As Integer,Y As Integer
Const m =120
L47: x=Inputbox$(“Please enter your age.”, “Age?”, “”)
If A2<xyz Or A2>m Then
Msgbox “Are you sure you entered the right age? It should be between ” _
& xyz & ” and ” & m & “.”
If A2<A Then
A close inspection reveals that these code fragments are identical, in a strict sense. Based on the other metrics listed in this article, they have the same overall quality. But if our goal is for real people to maintain and extend real software systems, then we must judge the first fragment to be superior to the second. If a software system is not readable, it is hard (or impossible) to debug it, modify it, extend it, scale it, etc.
There are two aspects to software readability: clarity that is built into the code, and comments that annotate the code. The first includes meaningful names for variables and constants, good use of white space and indenting, transparent control structures, and straight-line normal execution paths. Good commenting practice stresses comments that educate the next programmer about topics that cannot be gleaned from the code itself, such as the intention of each module. (Steve McConnell has written the definitive discussion of this topic in Chapter 19 of Code Complete.)
Readability is the one area of software aesthetics that does not have an obvious parallel with physical construction. (And I neglected it in the first version of this essay.) But it is vitally important. If other software developers cannot make sense of an engineer’s source code, then the code effectively does not contain any of the other metrics discussed here. The qualities may be there in some technical sense, but their impenetrability makes them nonexistent for practical purposes. For example, suppose that a set of source files exhibits perfect minimality (non-redundancy). But if no human can find the single section of source code that relates to a certain feature, no one will be able to fix bugs there or extend the feature in any way.
Software should do its work and solve its problems in the simplest manner possible. In many ways, simplicity is the most important principle of all and overlaps with all the other principles. Simple software is beautiful. Beautiful software is simple. Simple programs have fewer bugs (because there are fewer lines of code which can be wrong), run faster (because there are fewer machine instructions), are smaller (because there is less compiled code), and are easier to fix when broken (because there are fewer places where a bug can occur). Simple programs are dramatically less expensive to create and maintain, for all of the above reasons.
The simplicity of software also is a key metric distinguishing programming ability. Junior programmers create simple solutions to simple problems. Senior programmers create complex solutions to complex problems. Great programmers find simple solutions to complex problems. The code written by topnotch programmers may appear obvious, once it is finished, but it is vastly more difficult to create. Just as the goal of science is to find simplicity and order in a seemingly complex universe, the goal of programming should be to find simple solutions to complex problems.
Putting It Together
Finally, there is a global quality to beautiful software that is not the sum of the previous attributes. All of the above qualities must come together to create an overall design that is beautiful. Such beauty cannot be obtained by following a rigid set of guidelines based on the above principles — even though each is important. Just as house architects cannot design beautiful buildings simply by including known elements that have worked elsewhere, good software design is more than a collection of programming techniques that make sense on their own. Beautiful software is achieved by creating a “wonderful whole” which is more than the sum of its parts. Beautiful software is the right solution, both internally and externally, to the problem presented to its designers.
The result of good software design is a program that is better by many measures; it is not just better in the abstract. Compared to a poorly designed solution, well designed software meets users’ needs more closely, can be completed more quickly, works more reliably, and costs far less money initially and throughout its life. All of us in the software world have accepted poor software architecture too often. We should demand the same level of quality in software systems as we do in the buildings where we live and work.
About the Author
Version 1 — October 1998. The original article, which appeared only on my web site.
Version 2 — July 1999. A significantly shorter version, published by the Boston Globe, which I adapted to their space requirements.
Version 3 — July 2001. This version.
Copyright 1998-2001 by Charles H. Connell Jr.