Computation
3.2 Objectives and tools
CC
Our job as programmers is to express computations Correctly
Simply Efficiently
Please note the order of those ideals: it doesn’t matter how fast a program is if it gives the wrong results. Similarly, a correct and efficient program can be so complicated that it must be thrown away or completely rewritten to
produce a new version (release). Remember, useful programs will always be modified to accommodate new needs, new hardware, etc. Therefore a
program – and any part of a program – should be as simple as possible to perform its task. For example, assume that you have written the perfect
program for teaching basic arithmetic to children in your local school, and that its internal structure is a mess. Which language did you use to
communicate with the children? English? English and Spanish? What if I’d like to use it in Finland? In Kuwait? How would you change the (natural) language used for communication with a child? If the internal structure of the program is a mess, the logically simple (but in practice almost always very difficult) operation of changing the natural language used to communicate with users becomes insurmountable.
AA
Concerns about correctness, simplicity, and efficiency become ours the minute we start writing code for others and accept the responsibility to do that well; that is, we must accept that responsibility when we decide to
become professionals. In practical terms, this means that we can’t just throw code together until it appears to work; we must concern ourselves with the structure of code. Paradoxically, concerns for structure and “quality of code”
are often the fastest ways of getting something to work. When programming is done well, such concerns minimize the need for the most frustrating part of programming: debugging; that is, good program structure during
development can minimize the number of mistakes made and the time needed to search for such errors and to remove them.
CC
Our main tool for organizing a program – and for organizing our thoughts as we program – is to break up a big computation into many little ones. This technique comes in two variations:
Abstraction: Hide details that we don’t need to use a facility
(“implementation details”) behind a convenient and general interface.
For example, rather than considering the details of how to sort a phone book (thick books have been written about how to sort), we just call the
sort algorithm from the C++ standard library. All we need to know to sort is how to invoke (call) that algorithm, so we can write sort(b) where
b refers to the phone book; sort() is a version of the standard-library sort
algorithm (§21.5). Another example is the way we use computer memory. Direct use of memory can be quite messy, so we access it through typed and named variables (§2.2), standard-library vectors
(§3.6), maps (§20.2), etc.
Divide-and-conquer: Here we take a large problem and divide it into several little ones. For example, if we need to build a dictionary, we can separate that job into three: read the data, sort the data, and output the data. Each of the resulting problems is significantly smaller than the original.
XX
Why does this help? After all, a program built out of parts is likely to be slightly larger than a program where everything is optimally merged together.
The reason is that we are not very good at dealing with large problems. The way we actually deal with those – in programming and elsewhere – is to break them down into smaller problems, and we keep breaking those into even smaller parts until we get something simple enough to understand and solve. In terms of programming, you’ll find that a first attempt of a 1000-line program has far more than ten times as many errors as a 100-line program, so we try to compose the 1000-line program out of parts with fewer than 100 lines. For large programs, say 10,000,000 lines, applying abstraction and divide-and-conquer is not just an option, it’s an essential requirement. We simply cannot write and maintain large monolithic programs. One way of looking at the rest of this book is as a long series of examples of problems that need to be broken up into smaller parts together with the tools and techniques needed to do so.
When we consider dividing up a program, we must always consider what tools we have available to express the parts and their communications. A good library, supplying useful facilities for expressing ideas, can crucially affect the way we distribute functionality into different parts of a program.
We cannot just sit back and “imagine” how best to partition a program; we must consider what libraries we have available to express the parts and their communication. It is early days yet, but not too soon to point out that if you can use an existing library, such as the C++ standard library, you can save yourself a lot of work, not just on programming but also on testing and documentation. The iostreams save us from having to directly deal with the hardware’s input/output ports. This is a first example of partitioning a program using abstraction. Every new chapter will provide more examples.
Note the emphasis on structure and organization: you don’t get good code
just by writing a lot of statements. Why do we mention this now? At this stage you (or at least many readers) have little idea about what code is, and it will be months before you are ready to write code upon which other people could depend for their lives or livelihood. We mention it to help you get the emphasis of your learning right. It is very tempting to dash ahead, focusing on the parts of programming that – like what is described in the rest of this chapter – are concrete and immediately useful and to ignore the “softer,”
more conceptual parts of the art of software development. However, good programmers and system designers know (often having learned it the hard way) that concerns about structure lie at the heart of good software and that ignoring structure leads to expensive messes.
Without structure, you are (metaphorically speaking) building with mud bricks. It can be done, but you’ll never get to the tenth floor (classical mud bricks lack the structural strength for that). If you have the ambition to build something reasonably permanent or something large, you pay attention to matters of code structure and organization along the way, rather than having to come back and learn them after failures.