In the simplest terms, a working program is one that solves the problem it set out to solve. The criteria of success is based largely on the description of the original problem. For instance, if I tell you: "Write a program that does stuff." It's very easy to write this kind of a program and to convince yourself that it works. Of course, I might not be very happy with it, because it probably doesn't do what I expected. If I want something more specific, I need to be clearer in my request. So I might say: "Write a program that draws a house." Now you have a somewhat more specific problem to solve, and we can have a more reasonable discussion about whether or not your program actually solves it. Suppose you deliver a program that draws a house with no windows. I might complain, but arguably your program has solved the problem I set forth. If I'd wanted windows, I should have made that part of my request.
The technical term for this kind of request is a specification. Specifications should have a level of detail sufficient to solve the real-world problem they are setting out to solve. For instance, the specifications for an air-traffic control system are likely to be much more detailed than the specifications for a beginning programming assignment.
Another useful way to think about specifications is that they express a contract between a client and a vendor. Contracts often have certain requirements (things that the vendor expects to be true for the contract to be excuted), and make certain guarantees (things that the vendor insists will be true once the contract is executed). Programming by contract is a useful way to split large programming tasks into smaller components. We can apply the concept of specification at various levels of a program. Initially, we might just write a specification for the whole program, but later, we'll break it down, perhaps writing specifications for individual methods that need to be written. These specifications are often advertised as part of the interface of a method. Having specifications at various levels and degrees of detail allows us to reason more effectively about the supposed behavior of a program.
The phrase "meets the spec" is often used to mean "the program works." How can you convince yourself that the program "meets the spec"? There are several approaches -- including testing and analysis. Suppose I give you this specification:
Write a program that reads a number between one and a million, divides it by two, and prints the result.You could imagine trying your program on every possible value and checking that the results. If they are correct for every input, then your program works -- it meets the spec. While this might work for toy programs, it's not very practical for larger pieces of software. In these cases, testers will often build suites of test cases. If the program "passes" all of the test cases, they can say it works for the given test suite. Finding a good set of test cases is an art in itself, and we'll talk about finding good test cases later on.
Specifications themselves are of course rooted in human language, so it seems that they can only be as precise as our own, somewhat imprecise language. In the extreme, the definition of terms itself seems like it leads to an infinite regress. Consider the following example:
Write a program that takes two numbers and prints their sum.This seems simple enough, but it assumes that the terms "program", "numbers", "prints", "sum", and so forth are well-defined. We typically take the definition of these sorts of terms for granted, but imagine the dialog you could have with an inquisitive two-year old:
- What does 'print' mean? - It means to show the information on the computer screen. - What's a computer screen? - It's like the television, but it's connected to the computer. - What's a computer? - It's a machine that can execute a set of instructions you give it. - What's an instruction? - [And so on, and so on...]Clearly, I'm exaggerating the point here, but I want to show that there is a lot that we take for granted when we give and receive specifications. Our language is full of subtle shades of meaning, that can be interpreted in a variety of ways. People have attempted to make the specification process more formal, usually by creating increasingly mathematical notation. In some ways, this can make specifications more clear, but in some sense, it skirts the issue. If the problem domain itself is not a mathematical one, a highly formal notation may not assist the designer. Imagine trying to create a system to support the work of air-traffic controllers. While some aspects of the air-traffic control problem are undoubtedly mathematical in nature, studies have shown that its effectiveness is rooted in large part in the language, practices, signs, and implicit knowledge of the air-traffic controllers. Besides relying on instruments to make good and safe decisions, controllers engage in constant linguistic negotiation and interaction with pilots and other controllers both locally and at other airports. Ultimately, it seems that we need to answer the fundamental question of whether or not our own language is completely and unambiguously formalizable. The quest to formalize human language characterized many of the great philosophical projects of the 19th and 20th century. Today it is generally accepted (at least outside of the Computer Science discipline!) that they all ended in failure.
In terms of structuring programs as contracts, the same problems arise. Arguably, calling specifications "contracts" is just sugaring over the core issue. To be effective, the contracts themselves must be crafted in just the appropriate amount of exacting detail. Having a contract is not enough -- it has to be a "good" contract. Lawyers understand that contracts are incredibly difficult to write well. It is no mistake that Contracts is typically regarded as one of the most difficult courses a law student will face. In a sufficiently large software system, writing a contract can be as difficult as writing a contract in the real world. Eventually, the contractual view seems to return us to the deep philosophical issues we wrestled with above.
At this point, you may be thinking that if it's so difficult or even impossible to specify anything at a sufficient level of unambiguous detail, how can we ever write a program that "works." The solution is perhaps to redefine what we mean by "works." A working program is one that is generally useful to the people who use it in their everyday lives. It is a program that supports rather than hinders their daily practices.
Many philosophers have wrestled with the question of understanding. In other words, they attempted to answer the question: "How do humans understand language? What does it mean to understand a word or a sentence? What is meaning?" Wittgenstein gave what many people consider a rather exotic answer to this question. Traditionally, this question has been answered in a denotational manner. In other words, the meaning of a word is that which it denotes. The word "apple" denotes (refers to) a crunchy, sweet, juicy fruit that grows on trees. For you to understand the word "apple" is for you to grasp this reference. Wittgenstein argued, however, that this view of language is impoverished. Consider a one year old who is just learning to speak. She may utter the word "banana". What she really "means" by saying that word is that she wants a piece of the banana that she sees sitting on the counter. For you to "understand" her is to actually cut and mash the banana and give her some. The traditional view of language does not explain this action- and use-oriented quality of language. For you to understand is to "do the right thing," rather than simply grasp the denotation of the word "banana" to the concept of the thing you have in your head.
Stepping back into the world of computer programs now, we see that the difficulty with specifications is that they do not capture the richness of meaning in the same way that a denotational theory of language cannot express the richness of how we use language. Fundamentally, we humans use programs to do things, the same way that we use language to do things. To have a successful (that is, understood by both parties) conversation is to get the thing done that you are wanting to get done. To have a successful, working computer program is to write one that gets done what you intended. The metric of success is usefulness to the humans that use it rather than simply meeting the specification.
This redefinition of success implies a markedly different approach to designing and developing computer programs -- where designers, developers, and users work together and contribute broadly to the creation of a software artifact. By working together more closely, designers and developers can better learn and understand aspects of the user's domain that may not have been well expressed in the initial specification. Furthermore, by testing prototypes - early versions of the software - on actual users, feedback can be gathered at a time when it makes the greatest impact -- early in the project. Understanding and improving the concept of user-centered or participatory design is an active field of research at the moment.
While specifications have their weaknesses, I want to emphasize that they do play an important role in the software development process. Specifications are useful. While it is arguable that they are even a necessary condition for building good software, it is a mistake to believe that they are sufficient condition. Perhaps the best we can do is formalize a specification to the extent that it assists us, but not to the extent that we lose the richness of the orginal domain. When asked about rules (definitions) for using language, Wittgenstein provided a characteristically fuzzy answer. He argued that rules should be viewed more as signposts at a fork in the road. Rules are adequate, he said, if they eliminate our doubt as to which direction to take. While the signpost cannot guarantee that someone will take the correct fork in the road, they will tend to do so if they are effective in their design. The metric of their success is their usefulness, rather than a normative standard of completeness or definiteness. By answering the question this way, he underscored the importance of rules without needing to make any claims or requirements as to their form.