17 Jan 1996

The Programming Process

See also pp 675-680 of Standish.

One of the most popular models of software development is the waterfall model, so called due to the resemblance of the diagram to a waterfall:

  /-------\
  | Def'n |----v
  \-------/  /--------\
        ^----| Design |----v
             \--------/  /------\
                    ^----| Code |----v
                         \------/  /------\
                              ^----| Test |----v
                                   \------/  /-------\
                                        ^----| Debug |
                                             \-------/

The idea is that during the programming process each stage is completed before the next is begun. If any failures are detected, then you move backward to the previous stage to fix the problem there. If the problem still can't be fixed, then you continuing going up the waterfall.

Let us examine each stage in more detail:

  1. Definition - This is the most difficult, the most important, and the most error-prone part. During this stage the programmer consults with the expected users to determine what is needed, tries to cull out the important features to include, and defines what should be in the final system.

    In this class, however, the problems we give you will be well-defined, and this stage will rarely come into play.

  2. Design - During the design stage, the programmer breaks the problem into manageable pieces and plans how each piece will be accomplished (ie, determines data structures, file structures, and algorithms to be used). The idea is to avoid rewriting code because it was done wrong the first time.

    You may well be accustomed to cranking out solutions to programming problems without much forethought. In the problems we assign in this course, however, you should put some time (an hour, perhaps) into design before starting. One of the most important things you should gain from this course is a first glimmer of what software design involves.

  3. Coding - Your preceding course probably concentrated on the coding aspect. During coding, you put the design into action. If the definition and design were done fairly well, this will be easy and fun; if it was done extremely well, it might even be tedious.

  4. Testing - Be sure to test your program thoroughly. Watch out especially for degenerate cases and boundary cases. For large programs, for which you can only do so many tests, you may have to design a testing strategy. You can reduce the number of tests, of course, by testing each section as you go.

  5. Debugging - You'll find this easier if you take the time to learn and use ObjectCenter or another debugger (such as gdb or Turbo C's). In my opinion, uncovering bugs is by far the most fun portion of the programming process.

    You probably know by now that it's a good idea to print diagnostic messages with variable values, etc. Of course, you shouldn't actually submit to your manager or instructor a program that prints these messages. To easily add and remove diagnostic messages, you can use the following type of code:

    #define DEBUG
     :
    #ifdef DEBUG
       cerr << "reached here; i = " << i << endl
    #endif
    
    In this way, you can remove the diagnostic messages by deleting the ``#define'' line.

    Finally, you should document your code. Documentation of course includes comments, but it also includes meaningful function and variable names. You should document your code as you go along; and you should be careful to revise the documentation as you change the code. (A programming adage: A bad comment is worse than none at all.) You'll find documentation useful if you ever wish to reuse the code, and you'll appreciate it if you ever have to modify somebody else's code. Documentation is also required for good grades in this class. We'll discuss the type of documentation we expect later.

    Arrays and Pointers

    I'd like to say one quick thing about the difference between arrays and pointers. The difference is subtle, but you should understand it before going further.

    Consider the following variable declarations:

    int a[5] = { 0, 2, 4, 6, 8};
    int *p = &(a[0]);
    
    This will create two memory locations, looking like the following:
       /-------------------\
    a: | 0 | 2 | 4 | 6 | 8 |
       \-------------------/
         ^-----\
               |
       /-------|-------\
    p: |       ^       |
       \---------------/
    
    To some small extent, then, using the pointer uses more space since it uses bytes to hold the pointer plus the bytes of the actual array; this is a negligible difference, however.

    C and C++ hide the difference in storage and make the array and pointers behave almost identically. In particular, the following is true:

    p == a && *p == *a && p[2] = a[2] && *(a + 2) == *(p + 2)
    
    Moreover, a function given either as an argument will not be able to tell the difference. In fact, you are unlikely to ever see a difference unless you try to take the addresses; in particular, a equals &a, but p does not equal &p. This is because in the case of arrays, the ampersand is ignored, but the address of p is the location of the pointer in memory, and not the value of the pointer itself.

    You ought to come to understand this subtle difference well, since it is easy to forget as you code.

    Input and output: C v. C++

    See also appendix D (sections 1, 2, and 4) of Pohl.

    Let's look at a small segment of C:

    #include 
     /* ... other code ... */
    printf("name: %s\n", myname);
    printf("numbers: ");
    scanf("%d%d", &i, &j);
    ch = getchar();
    

    Including ``stdio.h'' gives us three useful identifiers: stdin, stdout, and stderr. To use the C++ library, we include ``iostream.h'' instead, giving three analogous identifiers: cin, cout, and cerr. The C++ equivalent to the preceding is:

    #include 
     /* ... other code ... */
    cout << "name: "; /* printf("name: %s\n", myname) in C */
    cout << myname;
    cout << "\n";
    
    cout << "numbers: "; /* printf("numbers: ") */
    
    cin >> i; /* scanf("%d%d", &i, &j) */
    cin >> j;
    
    cin.get(ch); /* ch = getchar() */
    

    Actually, the C code will work in C++ also. The code is not interchangeable, however; to combine the ``stdio.h'' functions and the ``iostream.h'' functions, you must synchronize the two with the sync_to_stdio function. This is described in section D.8 of Pohl.

    With ``iostream.h'' one uses streams, which can be thought as the equivalent to C FILE pointers. The identifiers cin, cout, and cerr are in fact streams (cin is an input stream; cout and cerr are output streams). C++ includes a facility to redefine operators on particular operators; here, you see that ``iostream'' has defined what the left-shift operator (``<<'') does when applied to an output stream and a string: it prints the string into the stream. Similarly, ``iostream'' has defined the right-shift operator (``>>'') to work so that given an input stream and an integer variable, it will read an integer from the stream and put it into the variable.

    We can improve on the above C++ code in two ways. First, successive prints can be catenated into a single, more readable line. You might (should) replace the first three lines of code above with:

    cout << "name: " << myname << "\n";
    
    Similarly, you might (should) replace the pair of integer reads with
    cin >> i >> j;
    

    Secondly, "\n" can be replaced with the endl identifier, which you can think of as standing for the same thing (strictly speaking, they aren't, but this shouldn't bother you). This gives more readable code with fewer quotation marks. The preceding output line, then, would be:

    cout << "name: " << myname << endl;
    

    The last line of the example (``cin.get(ch)'') provides an example of two new features. First, we pass a variable parameter to be changed by the function. This is impossible in C, but C++ includes the ability to call by reference: to pass a variable that is changed by the called function. The second new feature is the appearance dot operator (``.'') to reference a function associated with the ``cin'' stream. This is not quite exactly the same as the structure member operator. You'll see more of this later; for now, don't worry about it.

    Given that C++ handles both I/O libraries, you can validly ask which is better. The efficiency difference is negligible; the I/O operations themselves will takes far, far longer than any difference in code efficiency. There is a significant difference, however, in debugging and portability. Naturally, since C is more widely implemented, the C code will port more easily. In the C++ code, though, type checking will occur, leading to fewer bugs (anybody who has used printf or scanf has received unexpected results due to inconsistencies between the arguments and the formatting string). The C++ code is also slightly more readable. Therefore, unless you anticipate a C port (which will be very rare since C++ is widely available), you should become accustomed to the C++ version.


    17 Jan 1996 / 211 A, B

    Back Back