Friday, November 27, 2009

15.3 POSIX:XSI Shared Memory



[ Team LiB ]






15.3 POSIX:XSI Shared Memory


Shared memory allows processes to read and write from the same memory segment. The sys/shm.h header file defines the data structures for shared memory, including shmid_ds, which has the following members.



struct ipc_perm shm_perm; /* operation permission structure */
size_t shm_segsz; /* size of segment in bytes */
pid_t shm_lpid; /* process ID of last operation */
pid_t shm_cpid; /* process ID of creator */
shmatt_t shm_nattch; /* number of current attaches */
time_t shm_atime; /* time of last shmat */
time_t shm_dtime; /* time of last shmdt */
time_t shm_ctime; /* time of last shctl */

The shmatt_t data type is an unsigned integer data type used to hold the number of times the memory segment is attached. This type must be at least as large as an unsigned short.



15.3.1 Accessing a shared memory segment


The shmget function returns an identifier for the shared memory segment associated with the key parameter. It creates the segment if either the key is IPC_PRIVATE or shmflg & IPC_CREAT is nonzero and no shared memory segment or identifier is already associated with key. Shared memory segments are initialized to zero.



SYNOPSIS

#include <sys/shm.h>

int shmget(key_t key, size_t size, int shmflg);
POSIX:XSI

If successful, shmget returns a nonnegative integer corresponding to the shared memory segment identifier. If unsuccessful, shmget returns �1 and sets errno. The following table lists the mandatory errors for shmget.


errno

cause

EACCES

shared memory identifier exists for key but permissions are not granted

EEXIST

shared memory identifier exists for key but ((shmflg & IPC_CREAT) && (shmflg & IPC_EXCL)) != 0

EINVAL

shared memory segment is to be created but size is invalid

EINVAL

no shared memory segment is to be created but size is inconsistent with system-imposed limits or with the segment size of key

ENOENT

shared memory identifier does not exist for key but (shmflg & IPC_CREAT) == 0

ENOMEM

not enough memory to create the specified shared memory segment

ENOSPC

systemwide limit on shared memory identifiers would be exceeded




15.3.2 Attaching and detaching a shared memory segment


The shmat function attaches the shared memory segment specified by shmid to the address space of the calling process and increments the value of shm_nattch for shmid. The shmat function returns a void * pointer, so a program can use the return value like an ordinary memory pointer obtained from malloc. Use a shmaddr value of NULL. On some systems it may be necessary to set shmflg so that the memory segment is properly aligned.



SYNOPSIS

#include <sys/shm.h>

void *shmat(int shmid, const void *shmaddr, int shmflg);
POSIX:XSI

If successful, shmat returns the starting address of the segment. If unsuccessful, shmat returns �1 and sets errno. The following table lists the mandatory errors for shmat.


errno

cause

EACCES

operation permission denied to caller

EINVAL

value of shmid or shmaddr is invalid

EMFILE

number of shared memory segments attached to process would exceed limit

ENOMEM

process data space is not large enough to accommodate the shared memory segment


When finished with a shared memory segment, a program calls shmdt to detach the shared memory segment and to decrement shm_nattch. The shmaddr parameter is the starting address of the shared memory segment.



SYNOPSIS

#include <sys/shm.h>

int shmdt(const void *shmaddr);
POSIX:XSI

If successful, shmdt returns 0. If unsuccessful, shmdt returns �1 and sets errno. The shmdt function sets errno to EINVAL when shmaddr does not correspond to the starting address of a shared memory segment.


The last process to detach the segment should deallocate the shared memory segment by calling shmctl.




15.3.3 Controlling shared memory


The shmctl function provides a variety of control operations on the shared memory segment shmid as specified by the cmd parameter. The interpretation of the buf parameter depends on the value of cmd, as described below.



SYNOPSIS

#include <sys/shm.h>

int shmctl(int shmid, int cmd, struct shmid_ds *buf);
POSIX:XSI

If successful, shmctl returns 0. If unsuccessful, shmctl returns �1 and sets errno. The following table lists the mandatory errors for shmctl.


errno

cause

EACCES

cmd is IPC_STAT and caller does not have read permission

EINVAL

value of shmid or cmd is invalid

EPERM

cmd is IPC_RMID or IPC_SET and caller does not have correct permissions


Table 15.3 gives the POSIX:XSI values of cmd for shmctl.


Table 15.3. POSIX:XSI values of cmd for shmctl.

cmd

description

IPC_RMID

remove shared memory segment shmid and destroy corresponding shmid_ds

IPC_SET

set values of fields for shared memory segment shmid from values found in buf

IPC_STAT

copy current values for shared memory segment shmid into buf



Example 15.11 detachandremove.c

The detachandremove function detaches the shared memory segment shmaddr and then removes the shared memory segment specified by semid.



#include <stdio.h>
#include <errno.h>
#include <sys/shm.h>

int detachandremove(int shmid, void *shmaddr) {
int error = 0;

if (shmdt(shmaddr) == -1)
error = errno;
if ((shmctl(shmid, IPC_RMID, NULL) == -1) && !error)
error = errno;
if (!error)
return 0;
errno = error;
return -1;
}




15.3.4 Shared memory examples


Program 4.11 on page 108 monitors two file descriptors by using a parent and a child. Each process echoes the contents of the files to standard output and then writes to standard error the total number of bytes received. There is no simple way for this program to report the total number of bytes received by the two processes without using a communication mechanism such as a pipe.


Program 15.5 modifies Program 4.11 so that the parent and child share a small memory segment. The child stores its byte count in the shared memory. The parent waits for the child to finish and then outputs the number of bytes received by each process along with the sum of these values. The parent creates the shared memory segment by using the key IPC_PRIVATE, which allows the memory to be shared among its children. The synchronization of the shared memory is provided by the wait function. The parent does not access the shared memory until it has detected the termination of the child. Program 15.5 calls detachandremove of Example 15.11 when it must both detach and remove the shared memory segment.



Program 15.5 monitorshared.c

A program to monitor two file descriptors and keep information in shared memory. The parent waits for the child, to ensure mutual exclusion.



#include <errno.h>
#include <fcntl.h>
#include <stdio.h>
#include <string.h>
#include <unistd.h>
#include <sys/shm.h>
#include <sys/stat.h>
#include <sys/wait.h>
#include "restart.h"
#define PERM (S_IRUSR | S_IWUSR)

int detachandremove(int shmid, void *shmaddr);

int main(int argc, char *argv[]) {
int bytesread;
int childpid;
int fd, fd1, fd2;
int id;
int *sharedtotal;
int totalbytes = 0;

if (argc != 3) {
fprintf(stderr, "Usage: %s file1 file2\n", argv[0]);
return 1;
}
if (((fd1 = open(argv[1], O_RDONLY)) == -1) ||
((fd2 = open(argv[2], O_RDONLY)) == -1)) {
perror("Failed to open file");
return 1;
}
if ((id = shmget(IPC_PRIVATE, sizeof(int), PERM)) == -1) {
perror("Failed to create shared memory segment");
return 1;
}
if ((sharedtotal = (int *)shmat(id, NULL, 0)) == (void *)-1) {
perror("Failed to attach shared memory segment");
if (shmctl(id, IPC_RMID, NULL) == -1)
perror("Failed to remove memory segment");
return 1;
}
if ((childpid = fork()) == -1) {
perror("Failed to create child process");
if (detachandremove(id, sharedtotal) == -1)
perror("Failed to destroy shared memory segment");
return 1;
}
if (childpid > 0) /* parent code */
fd = fd1;
else
fd = fd2;
while ((bytesread = readwrite(fd, STDOUT_FILENO)) > 0)
totalbytes += bytesread;
if (childpid == 0) { /* child code */
*sharedtotal = totalbytes;
return 0;
}
if (r_wait(NULL) == -1)
perror("Failed to wait for child");
else {
fprintf(stderr, "Bytes copied: %8d by parent\n", totalbytes);
fprintf(stderr, " %8d by child\n", *sharedtotal);
fprintf(stderr, " %8d total\n", totalbytes + *sharedtotal);
}
if (detachandremove(id, sharedtotal) == -1) {
perror("Failed to destroy shared memory segment");
return 1;
}
return 0;
}


Using shared memory between processes that do not have a common ancestor requires the processes to agree on a key, either directly or with ftok and a pathname.


Program 13.5 on page 456 used mutex locks to keep a sum and count for threads of a given process. This was particularly simple because the threads automatically share the mutex and the mutex could be initialized statically. Implementing synchronized shared memory for independent processes is more difficult because you must set up the sharing of the synchronization mechanism as well as the memory for the sum and the count.


Program 15.6 uses a semaphore and a small shared memory segment to keep a sum and count. Each process must first call the initshared function with an agreed-on key. This function first tries to create a shared memory segment with the given key. If successful, initshared initializes the sum and count. Otherwise, initshared just accesses the shared memory segment. In either case, initshared calls initsemset with the ready flag in shared memory to access a semaphore set containing a single semaphore initialized to 1. This semaphore element protects the shared memory segment. The add and getcountandsum functions behave as in Program 13.5, this time using the semaphore, rather than a mutex, for protection.



Program 15.6 sharedmemsum.c

A function that keeps a synchronized sum and count in shared memory.



#include <errno.h>
#include <signal.h>
#include <stdio.h>
#include <sys/sem.h>
#include <sys/shm.h>
#include <sys/stat.h>
#define PERM (S_IRUSR | S_IWUSR)

int initsemset(key_t mykey, int value, sig_atomic_t *readyp);
void setsembuf(struct sembuf *s, int num, int op, int flg);

typedef struct {
int count;
double sum;
sig_atomic_t ready;
} shared_sum_t;

static int semid;
static struct sembuf semlock;
static struct sembuf semunlock;
static shared_sum_t *sharedsum;

int initshared(int key) { /* initialize shared memory segment */
int shid;

setsembuf(&semlock, 0, -1, 0); /* setting for locking semaphore */
setsembuf(&semunlock, 0, 1, 0); /* setting for unlocking semaphore */
/* get attached memory, creating it if necessary */
shid = shmget(key, sizeof(shared_sum_t), PERM | IPC_CREAT | IPC_EXCL);
if ((shid == -1) && (errno != EEXIST)) /* real error */
return -1;
if (shid == -1) { /* already created, access and attach it */
if (((shid = shmget(key, sizeof(shared_sum_t), PERM)) == -1) ||
((sharedsum = (shared_sum_t *)shmat(shid, NULL, 0)) == (void *)-1) )
return -1;
}
else { /* successfully created, must attach and initialize variables */
sharedsum = (shared_sum_t *)shmat(shid, NULL, 0);
if (sharedsum == (void *)-1)
return -1;
sharedsum -> count = 0;
sharedsum -> sum = 0.0;
}
semid = initsemset(key, 1, &sharedsum->ready);
if (semid == -1)
return -1;
return 0;
}

int add(double x) { /* add x to sum */
if (semop(semid, &semlock, 1) == -1)
return -1;
sharedsum -> sum += x;
sharedsum -> count++;
if (semop(semid, &semunlock, 1) == -1)
return -1;
return 0;
}

int getcountandsum(int *countp, double *sum) { /* return sum and count */
if (semop(semid, &semlock, 1) == -1)
return -1;
*countp = sharedsum -> count;
*sum = sharedsum -> sum;
if (semop(semid, &semunlock, 1) == -1)
return -1;
return 0;
}


Each process must call initshared at least once before calling add or getcountandsum. A process may call initshared more than once, but one thread of the process should not call initshared while another thread of the same process is calling add or getcountandsum.



Example 15.12

In Program 15.6, the three fields of the shared memory segment are treated differently. The sum and count are explicitly initialized to 0 whereas the function relies on the fact that ready is initialized to 0 when the shared memory segment is created. Why is it done this way?


Answer:


All three fields are initialized to 0 when the shared memory segment is created, so in this case the explicit initialization is not necessary. The program relies on the atomic nature of the creation and initialization of ready to 0, but sum and count can be initialized to any values.



Program 15.7 displays the shared count and sum when it receives a SIGUSR1 signal. The signal handler is allowed to use fprintf for output, even though it might not be async-signal safe, since no output is done by the main program after the signal handler is set up and the signal is unblocked.


Program 15.8 modifies Program 15.5 by copying information from a single file to standard output and saving the number of bytes copied in a shared sum implemented by Program 15.6. Program 15.8 has two command-line arguments: the name of the file; and the key identifying the shared memory and its protecting semaphore. You can run multiple copies of Program 15.8 simultaneously with different filenames and the same key. The common shared memory stores the total number of bytes copied.



Program 15.7 showshared.c

A program to display the shared count and sum when it receives a SIGUSR1 signal.



#include <signal.h>
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>

int getcountandsum(int *countp, double *sump);
int initshared(int key);

/* ARGSUSED */
static void showit(int signo) {
int count;
double sum;
if (getcountandsum(&count, &sum) == -1)
printf("Failed to get count and sum\n");
else
printf("Sum is %f and count is %d\n", sum, count);
}

int main(int argc, char *argv[]) {
struct sigaction act;
int key;
sigset_t mask, oldmask;

if (argc != 2) {
fprintf(stderr, "Usage: %s key\n", argv[0]);
return 1;
}
key = atoi(argv[1]);
if (initshared(key) == -1) {
perror("Failed to initialize shared memory");
return 1;
}
if ((sigfillset(&mask) == -1) ||
(sigprocmask(SIG_SETMASK, &mask, &oldmask) == -1)) {
perror("Failed to block signals to set up handlers");
return 1;
}
printf("This is process %ld waiting for SIGUSR1 (%d)\n",
(long)getpid(), SIGUSR1);

act.sa_handler = showit;
act.sa_flags = 0;
if ((sigemptyset(&act.sa_mask) == -1) ||
(sigaction(SIGUSR1, &act, NULL) == -1)) {
perror("Failed to set up signal handler");
return 1;
}
if (sigprocmask(SIG_SETMASK, &oldmask, NULL) == -1) {
perror("Failed to unblock signals");
return 1;
}
for ( ; ; )
pause();
}



Program 15.8 monitoroneshared.c

A program to monitor one file and send the output to standard output. It keeps track of the number of bytes received by calling add from Program 15.6.



#include <fcntl.h>
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include "restart.h"

int add(double x);
int initshared(int key);

int main(int argc, char *argv[]) {
int bytesread;
int fd;
int key;

if (argc != 3) {
fprintf(stderr,"Usage: %s file key\n",argv[0]);
return 1;
}
if ((fd = open(argv[1],O_RDONLY)) == -1) {
perror("Failed to open file");
return 1;
}
key = atoi(argv[2]);
if (initshared(key) == -1) {
perror("Failed to initialize shared sum");
return 1;
}
while ((bytesread = readwrite(fd, STDOUT_FILENO)) > 0)
if (add((double)bytesread) == -1) {
perror("Failed to add to count");
return 1;
}
return 0;
}



Example 15.13

Start Program 15.7 in one window, using key 12345, with the following command.



showshared 12345

Create a few named pipes, say, pipe1 and pipe2. Start copies of monitoroneshared in different windows with the following commands.



monitoroneshared pipe1 12345
monitoroneshared pipe2 12345

In other windows, send characters to the pipes (e.g., cat > pipe1). Periodically send SIGUSR1 signals to showshared to monitor the progress.








    [ Team LiB ]



    17.6 Sequential Tasks



    [ Team LiB ]






    17.6 Sequential Tasks


    This section describes the behavior of the dispatcher when the child task has both input and output. Although the dispatcher handles only one task at a time, it must monitor two input file descriptors. Complete Section 17.5 before starting this part.


    The dispatcher keeps information about the child task in the tasks array. For simplicity, the discussion refers to members of the ntpvm_task_t array such as readfd without their qualifying structure. Implement the tasks array as an object with appropriate access functions. The tasks array and its access functions should be in a file separate from the dispatcher main program. The array and its access functions are referred to as the tasks object, and an individual element of the tasks array is referred to as an entry of the tasks object. For this part, we only allow one task at a time, so the tasks object does not need an array of tasks.


    Figure 17.8 suggests the structure of threaded NTPVM dispatcher. An input thread monitors standard input and processes the incoming packets. An output thread monitors the readfd descriptor for input from the child task and writes this information to standard output.



    Figure 17.8. Schematic of a threaded NTPVM dispatcher for a single task.






    The input and output threads share the tasks object and must synchronize their access to this structure. One possible approach for synchronizing threads is to use a mutex lock to protect the entire tasks object. This choice cuts down on the potential parallelism because only one thread at a time can access the tasks object. Since mutex locks are low cost, we use a mutex lock for each element of the tasks array.



    17.6.1 The input thread


    The input thread monitors standard input and takes action according to the input it receives. Write an input function that executes the following steps in a loop until it encounters an end-of-file on standard input.





    1. Read a packet from standard input by using getpacket.


    2. Process the packet.



    After falling through the loop, close writefd and call pthread_exit.


    Processing a packet depends on the packet type.



    NEWTASK






    1. If a child task is already executing, discard the packet and output an error message.


    2. Otherwise, if no child task exists, create two pipes to handle the task's input and output.


    3. Update the tasks object, and fork a child. The child should redirect its standard input and output to the pipes and use the makeargv function of Program 2.2 to construct the argument array before calling execvp to execute the command given in the packet.


    4. Create a detached output thread by calling pthread_create. Pass a key for the tasks entry of this task as an argument to the output thread. The key is just the index of the appropriate tasks array entry.




    DATA





    1. If the packet's communication and task IDs don't match those of the executing task or if the task's endinput is true, output an error message and discard the packet.


    2. Otherwise, copy the data portion to writefd.


    3. Update the recvpackets and recvbytes members of the appropriate task entry of the tasks object.




    DONE





    1. If the packet's computation and task IDs do not match those of the executing task, output an error message and discard the packet.


    2. Otherwise, close the writefd descriptor if it is still open.


    3. Set the endinput member for this task entry.



    BROADCAST, BARRIER or TERMINATE





    1. Output an error message.


    2. Discard the packet.




    Exercise 17.9

    When a process that contains multiple threads creates a child by calling fork, how many threads exist in the child?


    Answer:


    Although fork creates a copy of the process, the child does not inherit the threads of the parent. POSIX specifies that the child has only one thread of execution�the thread that called fork.





    17.6.2 The output thread


    The output thread handles input from the readfd descriptor of a particular task. The output thread receives a tasks object key to the task it monitors as a parameter. Write an output function that executes the following steps in a loop until it encounters an end-of-file on readfd.






    1. Read data from readfd.


    2. Call putpacket to construct a DATA packet and send it to standard output.


    3. Update the sentpackets and sentbytes members of the appropriate task entry in the tasks object.



    After falling through the loop because of an end-of-file or an error on readfd, the output thread does the following.









    1. Close the readfd and writefd descriptors for the task.


    2. Execute wait for the child task.


    3. Send a DONE packet with the appropriate computation and task IDs to standard output.


    4. Output information about the finished task to standard error or to the remote logger. Include the computation ID, the task ID, the total bytes sent by the task, the total packets sent by the task, the total bytes received by the task and the total packets received by the task.


    5. Deactivate the task entry by setting the computation ID to �


    6. Call pthread_exit.



    Test the program by starting tasks to execute various cat and ls -l commands. Try other filters such as sort to test the command-line parsing. For this part you should not enter a new command until the previous command has completed.







      [ Team LiB ]



      Chapter 14. Ship It!











       < Day Day Up > 







      Chapter 14. Ship It!







      Philosophy: Chief Crazy Horse called to his men: "Ho-ka hey! It is a good day to fight! It is a good day to die! Strong hearts, brave hearts, to the front! Weak hearts and cowards to the rear."



      From the book Crazy Horse and Custer, by Stephen E. Ambrose (Lt. Cmdr. Worf, the Klingon on the Starship Enterprise would have really like Chief Crazy Horse. They used this quote several times on the show "Qapla!" [translated die well/success]).



      I have always liked this quote and felt that the phrase, "It is a good day to die!" is taken out of context most of the time. So I wanted to make sure that it is presented here, in its entirety and intentions, to make it clear that it is not some kind of suicidal statement but a brave call. In reference to this chapter, this is the type of mentality that you need when getting ready to release your product. Another term that Microsoft has used in the past is death march�not a pretty term, but descriptive. Microsoft doesn't use the term much anymore because people came on board who didn't like the term or softened it up a bit to crunch time or ship mode.



      Several milestones and stages are involved in developing a product, including brainstorming features, coming up with the specifications (or specs), writing and testing code, releasing betas, and finally shipping it. This last stage is the focus of this chapter. More specifically, it's about what should happen just prior to shipping and after the product is out the door.





      Microsoft Sidenote: Jim McCarthy's Rule 21



      I quoted Jim McCarthy in the first chapter and reference him again here because he is one of the most entertaining speakers I have ever seen. Although Jim left Microsoft in 1996, his "21 Rules of Thumb for Shipping Great Software on Time" memo is still floating around and quoted. It's a true classic that includes gems such as "Don't flip the Bozo bit" (basically, don't jump to conclusions on how smart or dumb someone is), "Lucid ignorance" (know what you don't know�one of my favorites), "If you build it, it will ship"�"conversely, if you don't, it won't" (daily builds are the answer).



      What is included here is what I think he elegantly put as Rule 21, "Get the team into ship mode."



      There is a moment on every development project when it is ideal for a team to enter ship mode. Ship mode is a high-performance period characterized by efficiency and determination. It is a period of flow. Before a team can enter ship mode, several prerequisites must be satisfied.



      1. Shipment must be the next milestone.

      2. Everybody (or nearly everybody) must believe that achieving the milestone is possible.

      3. All members of the team must understand precisely what they must do prior to shipping. All unknowns are factored out.

      4. Management must lead the team to ship mode by entering ship mode first. That is, superfluous management hoo-ha is eliminated, the manager's awareness of detail climbs, fire-drills, and other deprioritizing activities are eliminated entirely, and tremendous focus is brought to bear.

      5. The team must desire to ship. Generally, a complete awareness of the effect of shipping (or not shipping) will create desire.

        The team becomes especially vigilant about thinking things through and looking for traps. Check-ins are made with extra precaution. Stabilization of the product is the principle goal. All development is complete but for bug fixing.



      The endgame, the last stage of ship mode, is different yet again. It is conceptually a very simple exercise. There is a list of activities. When every activity on the list is complete, you ship. Though the list might have hundreds or thousands of items, it is still just a list. There is no time for any effort that does not contribute toward completing the items on the list. Everybody is expected to complete their items as promised. As unanticipated items arise, after appropriate resistance, they are put on the list.



      A daily meeting should be established, with final decision makers in attendance. Agenda is ad hoc, assembled at the beginning of each meeting. No item is postponed that can be handled now. The team is aware that all issues can be brought to this meeting for expeditious remedy. Management is involved, leading the team toward their goal.



      The goal is an acceptable quality level at ship time. Only showstopper bugs should be addressed at all. Showstoppers are bugs that will either effect more than a handful of users or will cause unacceptably serious errors. Cosmetic changes, performance enhancements, new functions are not appropriate changes. The purpose of beta feedback during this period is to prove there are no showstoppers, provide advance warning of unanticipated market reaction, and provide input to the next release.



      Understand the range of quality that is acceptable to your customers. How many low-priority bugs did your product ship with last time? Was it a problem? Are the customers better off with this product including this bug? Since destabilizing the software is more of a problem than most bugs, be very careful about which bugs you fix. This is why we have "ReadMe" s and bug lists.



      Ship mode is basically a succession of daily milestones climaxing with the product's shipment.



      If you would like to see or hear more of Jim, he and his wife Michele formed their own consulting firm after they left Microsoft in 1996. Or you can purchase either of their books: Software for Your Head, or my favorite, Dynamics of Software Development.






      Okay, so now that you are in ship mode, what should everyone be working on at this point? The following list is typical but not always the case:



      • Most of the developers have moved on to the next version or other projects, with the exception of any developers who have a showstopper or Severity A, Priority 1 bug assigned to them.

      • The Central Build Team should be taking few code check-ins on the current product source tree because it is close to shipping, and most of the critical bugs should have been already been found.

      • The testing or the quality assurance (Q/A) group is scrambling trying to find critical bugs.

      • Marketing or upper management is putting a lot of pressure on everyone to make a somewhat random date that they decided the product would ship.



      Activities cited in the previous list are a healthy sign of the successful progress of shipping the product. If you follow the suggestions that have been given up to this chapter, you will find yourself in this situation and be capable of going the distance and getting the product out the door. I get concerned when the previous list is convoluted with problems such as the following:



      • Developers are not able to work on future versions because the source trees are not properly set up.

      • The Central Build Team has too many build breaks to get a build out every day.

      • Testing or Q/A does not have an idea of what code they really are testing.

      • Upper management pulls rank and just ships the product when it really isn't ready.



      Because I've spoken about these points throughout this book, let's address how the build team can keep the work flowing after or just prior to shipping.





      Microsoft Sidenote: Small Business Server Tree



      Figure 14.1 is an actual diagram I used when I was on the small business server (SBS) team. A developer (in this example, Jony) came to me (the build team) to let me know that he was working on the next release of SBS and asked how he should proceed.





      Figure 14.1. Small business server tree.













      Jony was one of three developers working on this project. The following text is taken from an e-mail that explained to the SBS team how the source trees were to be set up as they moved forward and to allow Jony to work on the new code with as little inconvenience as possible. It worked well, and the product has been successful for Microsoft.



      Because at this time, we have already shipped the U.S. 4.0a SBS product, any new check-ins from now until we ship all the international versions will be considered QFE check-ins to 4.0a U.S. (Note: We need to document all checkins.)



      After we ship the international versions of 4.0a, this tree will become the NEW QFE tree because it includes all of the 4.0 source.



      We will save the 4.0 source on tape but kill the tree and use the tree for 4.5. I really doubt there will be any 4.0 QFE fixes that we can't get out of the 4.0a QFE tree.



      When we met last, Jony was going to keep a 4.5 tree in his office. If he has been doing this, we need to merge the 4.5 check-ins into the new tree (\\ orville\razzle) after getting that tree in sync with the final 4.0a ship sources.



      Let's meet again to discuss this and make sure everyone is in agreement.



      This sidenote is just an example of how simple setting up source trees and communicating it to everyone can be. The example here really only applies to small teams. Larger teams need to provide more detail and formal announcements.






      Because there should be a lot of down time in the build lab as the check-ins slow down, the build lab should be working on a new build process. The build lab should roll out this process just after the final bits of the product are sent to manufacturing or released to the Web. Part of the process can include the following:



      • New tools and how to use them

      • Explanation of the source tree restructuring

      • Build schedule for the next month

      • Any changes in the check-in policy



      The build team can help accommodate the testers and upper management by being as efficient as possible with communications and delivery. Many of these responsibilities are outside the scope of the build team, so this is all I will say about these two points.



      After the team has completed all the daily milestones and everyone has given the product his blessing or approval, it is time to get it out the door. In the old days at Microsoft, this was a manual process, and we had our own manufacturing plant. Nowadays, the release is outsourced right after the final verification stage, which is the next topic.















         < Day Day Up > 



        17.1 Overview













        17.1 Overview


        Designing tests is creative; executing them should be as mechanical as compiling the latest version of the product, and indeed a product build is not complete until it has passed a suite of test cases. In many organizations, a complete build-and-test cycle occurs nightly, with a report of success or problems ready each morning.


        The purpose of run-time support for testing is to enable frequent hands-free reexecution of a test suite. A large suite of test data may be generated automatically from a more compact and abstract set of test case specifications. For unit and integration testing, and sometimes for system testing as well, the software under test may be combined with additional "scaffolding" code to provide a suitable test environment, which might, for example, include simulations of other software and hardware resources. Executing a large number of test cases is of little use unless the observed behaviors are classified as passing or failing. The human eye is a slow, expensive, and unreliable instrument for judging test outcomes, so test scaffolding typically includes automated test oracles. The test environment often includes additional support for selecting test cases (e.g., rotating nightly through portions of a large test suite over the course of a week) and for summarizing and reporting results.














        Section 10.4.&nbsp; Manage Your Team










        10.4. Manage Your Team



        Many project managersespecially those who have been promoted from technical positionsfeel like their primary job function is to understand the job each team member is doing. Often the best programmer, designer, or tester will be promoted into a management position. And in many cases, this is a very good choice, because it's important for a manager to understand the work being done by the team. Sometimes the project manager is also the direct manager of each team member, and in this case, he is especially vulnerable to certain management pitfalls.


        Understanding the work that the team is doing is very important; however, the primary task of a manager is to have the right people do the correct work and, ultimately, to get the tasks done in the most efficient and least problematic way. The first instinct of a manager who got to where he is by being a good programmer will be to be the best programmer on the team. That's not what the team needsthey need someone to make decisions, provide guidance, and prioritize their tasks. If all he does is "get his hands dirty" by solving programming problems for his team, they will not only sense a lack of direction from their manager, but may also feel demotivated because their work is not valued.


        In contrast, some managers understand that their job is to delegate. But while delegation is an important part of management, it must be done with a good understanding of the work being delegated. The manager may not be the best engineer in the group, or even be able to perform all of the engineering tasks he assigns. He should, however, understand the goals and limitations of each task, and be able to offer real guidance if team members get stuck or need help. That's much harder than delegating: while he's trusted a team member to accomplish the task, he must still understand enough about it to be useful if that team member encounters a problem.


        Good managers usually feel a little guilty about being managers. They know that their people are good, and they want them to succeed. But this necessarily involves riding their coattails. As a manager, you might feel that in some ways you are not making a direct contribution by producing work products. This is a good feeling: embrace it. Recognize that your role is to "grease the wheels" by providing an environment in which work gets done. And the best way to provide that environment is to show the team that you trust them to do the work. Show the team that you are there for them when they need you. When you make decisions about the project, make sure that you are always fair, just, consistent, and predictable. That way, when people disagree with you, they can at least understand why you made that decision and will remain motivated and loyal to the project.



        10.4.1. Avoid Common Management Pitfalls


        Poor managers are distinguished by their poor habits. They tend not to question their own authority, and they frequently don't have much faith in the people who work for them. They distance themselves from their projects, and tend to see their jobs as simple, intuitive, easy, and straightforward. A manager who acts this way projects hubris and arrogance; some people find that reassuring, but most engineers find it condescending. The best way to improve as an engineering project manager is to avoid these pitfalls.


        The best way to avoid these pitfalls is to question each decision that you make:


        • Is the decision based on facts, or are you automatically jumping to a decision based solely on intuition and gut instincts?

        • Are you unfairly questioning your team's honesty, integrity, or skill?

        • Are you making a decision that is politically motivated? Are you simply siding with the person you like better?

        • Are you oversimplifying a task or avoiding its internal details out of laziness?


        By understanding the root cause of many common pitfalls, a project manager can keep his team motivated and avoid bad decisions that lead to serious project problems. It requires constant vigilance to avoid those problems.



        10.4.1.1. Don't manage from your gut

        There is a common belief among many managers that decisions should make intuitive sense. This is generally true. However, the conversethat all ideas that make intuitive sense are good decisionsis definitely not true. Software projects are complex. They involve many people serving in different roles, sometimes with multiple roles per person. They involve numerous tasks and subtasks, where each task may be assigned to several people. As a manager, you can't expect to intuit all of that complexity. Just because you, as the project manager, have the authority to make decisions about the project, that doesn't mean that it's your job to overrule people all the time. It's your job to understand the issues that face the team, and to help the team deal with those issues.


        Think about it rationally: if a team member disagrees with a decision that you have made, and comes up with a well-researched and logical explanation for her disagreement, is it fair to dismiss her opinion simply because it does not immediately make intuitive sense to you? There are many things in the world (especially in complex engineered products) that simply are not intuitive to most people.


        For example, it seems intuitive that doubling the staff on a project should allow them to complete it in half the time. However, in the real world, projects are much more complex: there is overhead in the extra communication, certain tasks on a critical path cannot be split, it takes time for new team members to ramp up, etc. But that doesn't stop many project managers from trying over and over again to throw additional team members at a late project, only to find that it becomes an even later project.[*]

        [*] Fred Brooks pointed this out in his 1975 book The Mythical Man-Month. He referred to it as Brooks' Law: "Adding manpower to a late software project makes it later."


        Unfortunately, software project managers have to make decisions based on complicated information all the time. To make good decisions, you have to understand software engineering concepts and technological concepts that are not intuitive, and remain open to the idea that there have been recent innovations or changes in software engineering and technology that may contradict your current beliefs. This job is about being informed, not about feeling your way through problems.


        A project manager must make many individual decisions: who to assign tasks to, how long they should be expected to take, whether to implement certain features or requirements, the dependency between tasks and software behavior, and many other design, development, and testing decisions. There is no way that even the best project manager can be on top of every detail in an average-sized software project. But these decisions still must be made. So how can you make them without simply relying on your intuition, but also without being overwhelmed by the details?


        Luckily, your project team is staffed by competent software engineers who are capable of building the software. (If your team is not competent, you have bigger problems!) This means that you have at your disposal people who can help you make those decisions. You should enlist their help and work to understand the perspectives of all of the people involved in the project. When you make a decision, you must understand which team members it affects and take their perspectives into account. If you don't know those perspectives yet, ask the team members their opinions. Most people will be more than happy to help you decide the direction of their tasks, and you will almost certainly get better results because they participated in the decision-making process.


        If you try to learn all of the details for every decision that must be made, you will find that your projects will quickly get bogged down, with everyone waiting for you to decide on at least one issue. But if you work with your team to make well-informed decisions, you can share that load...and everybody wins. That's why you have a team: so people can collaborate.




        10.4.1.2. Don't second-guess estimates



        Many managers fall into a common trap when considering their team members' estimates: they automatically cut those estimates, no matter how reasonable or well researched they are. There are generally three reasons this is done.


        One reason is that the organization already committed to an earlier date for the software, and changing that expectation is difficult or impossible for the manager. This means that the project was not planned properly. The solution is to apply the project planning and estimation tools and techniques to bring the project under control. If the estimate does not meet the needs of the organization, the manager has several options: the scope of the project can be scaled back; the software can be released in phases; the deadline can be renegotiated; resources can be added; or some combination of all of these can be done. The team will generally respect the decision, as long as it was clearly based on honest estimates and planning rather than an artificial date.


        The second reason that an estimate may be second-guessed is that this second-guessing is a misguided attempt to motivate the team. For some reasonand nobody is really sure why some people believe thisthere are managers who think that telling somebody that they can do a task in less time than they estimated will cause them to "step up to the plate." Somehow, knowing that their manager believes that they can do it is supposed to increase their productivity. Unfortunately, this sort of second-guessing becomes a self-fulfilling prophecy. As the team realizes that their estimates will always be cut, they will start padding those estimates. This can create an environment of distrust between the team and their manager.


        The third reason managers will second-guess their teams' estimates is to force them to work overtime. By enforcing an overly aggressive deadline, some managers find that they can squeeze five, ten, or more extra hours per week out of all of their team members. This is especially dishonest, and it almost always breeds resentment. If the team is expected to work overtimeand in some cases, this is a valid and realistic expectationthat should be taken into account when the effort estimates are turned into a project schedule. This way, the team is not blindsided by the extra work and can plan their lives around it. (Sometimes managers forget that people have lives outside the organization!)


        In all of these cases, the key to understanding how the team members react to second-guessing is to recognize that they believe their manager is sending them a clear message: he does not trust their estimates. The solution to this is to establish trust by making decisions in a transparent manner. (A good way to do this is to use a repeatable estimation process like Wideband Delphisee Chapter 3.)


        This does not mean that the project manager does not need to understand estimates. It is important for a project manager to not only understand the reasons why the team estimated a certain effort for a task, but to question the estimate if it looks inaccurate, unrealistic, or seems to be based on incorrect assumptions. As long as the questions are reasonable and can be answered with facts, the team will generally respect them and work to answer them. If it turns out that the estimate is, in fact, unrealistic, the team will be glad that the project manager pointed out a potential problem with it!




        10.4.1.3. Don't expect consensus all of the time

        Over the course of almost any project, there will be disagreements between team members. Some project managers make the mistake of expecting the team members to settle all of these disagreements, reaching a consensus on their own. The project manager treats all disagreements as if they were merely petty or politically motivated, rather than based on a genuine difference in opinion over some important issue that affects the project.


        When two team members have a genuine disagreement, it is the manager's job to make a decision. That decision is going to leave at least one of the team membersand possibly bothunhappy. It is important to share the reasoning behind the decision with everyone, and to stand behind the decision. If it turns out to be wrong, it's the project manager's fault, not the fault of the person who originally proposed the solution or of the person who didn't fight hard enough for the alternative.


        To make a good decision, the manager must understand both perspectives. It's not enough to just tell the two people to go decide among themselves: if they could do that, they would not have brought the disagreement up with their manager in the first place. Sometimes a compromise can be reached, but most team members are capable of recognizing when a compromise is available, and implementing it themselves.


        If you treat each conflict as if it were a trivial or petty argument and tell your team members that it's their own responsibility to solve it, you are essentially asking one of them to acquiesce on something that he clearly thinks is important. That is unfair and divisive, and it makes both team members feel as if you do not care about their concerns or the project itself.


        That's not to say that there are no problems that cannot be left to the team. Sometimes a problem really is petty ("Bill stole my stapler!") and the team members really should at least try to work it out between them before involving their manager. But even these problems can escalate, and if that happens, a concrete decision ("Buy another stapler") is the only way to make the problem go away. It's important for a project manager to learn to differentiate between trivial problems ("Someone keeps taking the last donut") and more serious ones ("Tom won't let go of his ridiculous database design").


        Regardless of the magnitude of the problem, if two people on your team care enough about a problem to come to you with it, you should take it seriously. If you dismiss it and tell them that it's their problem to solve among themselves, you are making it clear to them that even though you are their manager, you do not care about the team members' problems (and, by extension, the project itself).




        10.4.1.4. Avoid micromanagement

        When a manager is overly involved in each task to the point that she personally takes over the work of each person on her team, she is micromanaging. From her point of view, there are a lot of benefits to micromanaging her team:


        • It endears her to the people at or above her level, because it seems like she always knows everything there is to know about what's going on with her team.

        • She knows that no mistakes will ever make it out of her team.

        • She does not have to trust the people who work for her to do a good job. Instead, she can review everything they produce to ensure that each work product meets her standardsand she will redo anything that does not meet those standards.

        • It makes her feel very important, because nothing gets done without her.

        • It allows her to feel superior to everyone who works for her.

        • She gets to steal her team's thunder, taking credit for all of their accomplishments while blaming them for any failures.


        Her micromanagement has a devastating impact on the people who work for her. They feel that they have no responsibility whatsoever for what they do. They are not trusted. If they produce poor work, she will fix it, usually without explaining what they did wrong and often without even telling them. They feel like they do not have any impact, positive or negative, on the final product. And they're right.


        Many people will put up with this situation for a long time. They can continue to collect a paycheck. The job that they do is not particularly stressful, because any work that does not meet the organization's standards will be redone for them. They are not trusted to set priorities, make decisions, or do any aspect of their jobs. This is very inefficient for the organization, and very demotivating for the team members. While they will tolerate the situation, the team members are neither challenged nor fulfilled. Meanwhile, their manager is drowning under all of the work. Nobody is happy with this situation.


        There are a few easy rules that will help you avoid micromanagement:



        Don't expect to review everything


        Many people think that to be an effective manager, you have to have read, reviewed, and redone all of the work of the people who work for you. A good manager will spot-check the team's output, but reviewing (and possibly redoing) every piece of work that the team creates is a terrible use of a manager's time. Delegation requires trust; if you do not trust your team to do their jobs, then you should fire them and replace them with people who you do trust (or don't replace them, so the organization does not have to pay their salaries).


        Don't fall into the "hands-on manager" trap


        There is a general perception in the technology world that management is not an actual job. It's often believed that competent engineers manage themselves, while their incompetent "pointy-haired" bosses just get in the way. This is simply untrue. Competent engineers can be trusted to produce good requirements, designs, test plans, and code; their focus is not on prioritizing or managing teams of people. They can't do your job for you, so don't try to do theirs for them.


        Many managers assume that because they are responsible for the work that their team produces, they should be able to do all of it. That's just not truethe individual team members have time to build up their expertise in developing the software, while you only have time to manage the project. Instead of trying to fill in as a technical team member, work on building up your project management skills.


        Use transparency to your advantage



        Some people fall into the trap of thinking that the job of project manager consists of constantly bugging each team member for status reports. Team members have trouble with this. They are surprised and unhappy that their project manager doesn't know enough about the project to glean even the most basic status; they also feel that they are not responsible for reporting their status upward. The project manager always goes to the team members, so they don't ever feel the need to report problems unless directly asked.


        If all project plans and work products are kept as a matter of public record, the team members don't need to deal with a project manager constantly bugging them for status reports. If the project has transparency, each team member is responsible for his own status communication.


        If your team is falling behind, don't just ask them for their statusthis will encourage them to give you excuses. Instead, gather the status yourself using the documents that you have made public, and ask them about specific problems. Transparency only works if you make it clear that there are consequences for poor performance, and that poor performance is evident from public documents. Given this, people will see what's required of them in order to do a good job.


        Don't be afraid to let your team make mistakes


        People learn from their mistakes, and it's important that they be given the opportunity to make them and take responsibility for them. If they are going to grow professionally, it is going to be through their own experiences and through trial and error. Without this, team members will never see that their success takes effort to achieve. If a micromanager simply corrects all of their mistakes and redoes their work, they have no incentive to learn or improve.


        It's okay for your team to make mistakes, as long as you're on top of it. The way to stay on top of mistakes is through the peer review tools. This allows team members to share information and help each other to grow to a standard that is in line with what the project needs and what the organization requires.




        10.4.1.5. Make your mistakes public

        If you make mistakes, you need to communicate them to everyone around you. It's okay to make the wrong call. Good managers recognize when they have made mistakes and correct them. The best managers publicize those mistakes so that everyone can learn from them, and so no one is blindsided by them.


        When a team member makes mistakes, the project manager should share in the responsibility. Many project managers don't realize that they are culpable for the mistakes made by their team members. The reason the manager is culpable is because he assigned the task to the team member, allocated the resources to do it, set the priorities for the project, and set and communicated the deadlinesall of which were contributing factors.


        For example, if somebody makes a bad decision because she failed to understand the project priorities, the project manager shares the responsibility for the error. That doesn't mean the project manager is solely to blameif there were 20 people in the meeting where that priority was communicated, and 19 of those people understood it, the one person who ended up making the mistake should have spoken up at the time. But it still means that the project manager was not entirely clear, and failed to communicate the project priorities to everyone on the team. So when that mistake gets made, it's still partially the project manager's fault.


        Just as it is okay for team members to make mistakes as long as they learn from them and corrective action is taken, it is okay for project managers to make mistakes as well. That's not to say that there are no consequences for mistakesa serious mistake can lead to delays, which could lead to reprimands and even failed projects. As a project manager, if you find that one of your team members has made a mistake, it's your job to figure out what role you played in that mistake. And when you communicate that mistake up to senior management, you should highlight your role in it and try to shield the individual team members as much as possible.


        This is very difficult to do. It's human nature to blame others. But by taking the blame yourself, you protect the team and keep them motivated, and help prepare them to recover from the mistake. If you stick your neck out for the team, they will know it, and they'll be much more loyal to you. On the other hand, if you let the blame roll downhill, the team will resent you and begin to work against you.


        Some project managers don't think this is fair. They feel that if someone makes a mistake, it's that person's responsibility to take the blame for it. But the project manager is in a position of authority, and just as other people are accountable for their individual responsibilities, the project manager is accountable for everything he is responsible for. And he's responsible for the entire project.




        10.4.1.6. Avoid lists

        Some managers do little more than hand their team members lists of tasks or action items. When a team member is handed a list of tasks, but has no understanding of the rationale behind each action, he does what he can to complete the task. But without a real understanding of the needs that drive the project and the rationale behind each task, he will often make basic mistakes that would be obvious if he were given the proper context. There will always be decisions that a manager cannot predict and put on a list; without context, a team member has little chance of making those decisions correctly.


        It's easy for a team to feel comfortable working from a list. It means that they are not responsible for anything other than this list of tasks. They don't have to think about overall project goals or the bigger picture. Most importantly, they don't have to make decisions. Accomplishing everything on a list of tasks is gratifyinga team member can go home at night knowing his job is 100% complete. But someone who feels responsible for the project, and not just his own tasks, knows his job is not really complete until the software is delivered and accepted. Sadly, once a team member understands the big picture, he feels like he's never done.


        Your job as a manager is to get everybody on the team to see the big picture. The vision and scope document is a valuable tool for this, as are the rationale sections of the use cases and requirements. These tools allow the team members to more fully understand the context that surrounds the work that they are doing.


        Software project teams are made up of smart people. It's far better to leverage their minds than to treat them like robots. Many people like to throw around the term "grunt programmer," as if there were a lot of programming tasks that were little more than cutting and pasting program recipes. But even the lowest impact programming tasks involve decision-making on the part of the programmer.





        10.4.2. Accept Criticism


        There are two ways that managers encounter criticism from a team member. One way is when the team member disagrees with the way a manager wants work done. The other is when the manager disagrees with how the team member is doing the work. Dealing with criticism is a potentially demotivating situation, but it's also a potentially encouraging one if handled well.


        Sometimes the team members solve a problem differently than you would. As a manager it is important to recognize the work that has gone into these solutions, even if they contradict your preconceived ideas about that work. Being able to accept the team member's criticism of your solution means that you are making decisions in the project's and organization's best interests, and motivates your team to keep thinking their way through such problems in the future.


        Everybody solves problems differently, and it's a fact of life in software engineering that most problems have many correct solutions. When you ask a team member to solve a problem, it's likely that she will come up with a correct solution that is different than the way you would solve the problem. If a member of your team offers a solution that is technically correct, but you don't accept it because you would do the work in a different (but equally valid, or even slightly more efficient) way, the team member will feel crushed.


        A good manager's default action should be to accept, not reject, ideas. You must take it very seriously when you reject somebody's work, and when you do, you should always explain and defend your decision. There are many good reasons to reject a team member's solution. Sometimes it's incorrect, and sometimes it's not well thought out. But people will become very attached to such solutions, even when they are dead-on wrong. In those cases, you must be willing to stick to your guns. When you do, it must be for the right reasons, and you must take the time to explain your reasoning.


        Criticism goes both ways. Sometimes a manager will want the team to do their work one way, and some team members will disagree. In fact, sometimes the entire team will disagree with a decision and come to the manager en masse. In this case, it is very tempting to just roll over and give in; it is equally tempting to refuse to even consider their opinions. Neither of these options is good, because no real discussion takes place in either case. Instead, a good manager will come up with a real justification for why he wants the work done that way, and will expect the team to do the same. If there is a real, verifiable reason for going with one alternative, everyone should be able to see it. And most importantly, the manager should show that he considered the argument, even if he essentially rejects it.


        Ultimately, you won't be able to make everyone happy. It's always better if everyone can agree, but there are many times when there is a genuine difference of opinion. In this case, the manager is within his rights to pull rank. However, if he just rejects an argument outright or ignores a valid argument just to get his way, he is abusing his power, and his team will resent him and try to figure out ways to work around him. They will also avoid coming to him in the future, opting to apologize later rather than ask permission now.


        Another way to help team members accept your decisions is to have written guidelines that they can follow. If you can point to a published document that guides the way that your team does their work, your team members will recognize that and respond to your consistency. It's much easier to work with a manager who is consistent and predictable than with one who may randomly reject ideas with no real justification. The tools in this book are examples of the kinds of guidelines that a team can adopt. For example, a manager may have a written guideline that says that every programmer should follow the Subversion basic work cycle (see Chapter 7). Then, even if a programmer feels that it's not her responsibility to merge changes that occurred since a file was checked out, the manager can refer back to the guideline and show that he is being consistent in his decision to have her merge her changes.




        10.4.3. Understand What Motivates Your Team Members


        Talk to your team about their goals. If an employee's goals are incompatible with his company's goals, he should not be working for that company. However, each person's goals go beyond simply finishing the current project: people want to move ahead in their careers, and part of your job as project manager is to help them achieve their professional development goals. The organization gains when employees improve, because a more experienced employee will deliver superior work. A team with people who have more experience can take on more complex projects and come up with more creative solutions.


        People work for money. For some reason, many bosses feel uncomfortable with thisthey pretend that people work out of loyalty, love of the job, or blind devotion to the organization's goals. This leads to an unfortunately pervasive attitude where managers act like their employees are lucky to have jobs. Compensation also comes in many forms: in addition to money, some organizations will give flexible hours, training, books, stock options, free lunches, senior titles, access to new technology, or other perks in place of money. But in all of these cases, people need to feel that they are being fairly compensated for the effort that they are putting in.


        Another motivator is loyalty. Many people naturally develop some loyalty to the organizations where they workit is human nature. This is why teams of people who are poorly managed and undercompensated will still work 80-hour weeks on projects that are clearly doomed to failure. Unfortunately, it's very easy to redirect loyalty, especially through dishonesty. In some cases, a poor manager can keep secrets from the team and lie to them about organizational decisions, in order to redirect the team's loyalty from the organization to him. In other cases, senior management themselves can, through lying, incompetence, and obvious lack of appreciation, lose the team's loyalty.




        10.4.4. Don't Be Thrown by Dishonesty


        People lie. They will say that they have completed things that they haven't, or that they understand things that they don't. They will make commitments, and then claim they never made them. Having a dishonest person working on a project is possibly the most difficult situation a project manager can face.


        There are some things a project manager can do to discourage dishonesty. By keeping all work out in the open and admitting your own mistakes, you can create an environment where people are more honest. But this only goes so farsometimes people lie, and you'll have to deal with it.


        The best-case scenario is one in which you have evidence that directly contradicts the lie. If you find that somebody is lying, you need to present him with that evidence. The purpose is not to make him feel bad; rather, it is to help him understand that it's wrong, and that he shouldn't do it again. Don't get caught up trying to understand why someone is being dishonestit could be a misunderstanding, it could be malicious, or it could be something else entirely. Sometimes the person doesn't even realize that he's lying. The key is to have enough information available so that you can set the situation right and keep it from threatening the project.


        Unfortunately, in some cases, there is no evidence to counter the lie. When this occurs, there may be nothing that you can do about the situation. If you think that someone is lying and you don't have evidence, you can set about collecting that evidence. Usually a lie is about a commitment that was made: the person may have agreed to do a certain task in a certain way, and is now claiming that she never made that commitment. Information about the commitment may be in an email, a project document, or a task definition in the project plan. But if the commitment was less formal (such as a verbal agreement), there may simply be no record of it.


        If there is not enough evidence, you may have to let the lie pass and live with the consequences. This is a very frustrating situation. In this case, your job is to improve the way you manage your commitments and those of your team, in order to prevent problems like this from happening in the future. You can collect better information, change your expectations, help people feel more comfortable letting you know if there are problems, and, in extreme situations, avoid working with people who have trouble being honest.



        Note: More information on commitment management can be found in Managing the Software Process by Watts Humphrey (Addison Wesley, 1989).



        10.4.5. Address Performance Problems Early


        It is difficult to effectively manage teams without defining their goals up front. The best way to do that is to involve each person in the definition of his or her own goals. Each of these goals should be specific, measurable, should pertain to their work, and should be attainable. One effective way to do this is to work with each team member to develop a performance plan, which is simply a list of goals that the manager and team member agree to.


        People need to feel that they understand what is expected of them. The purpose of the performance plan is to set standards that are fair and attainable, and that are developed with the involvement of the team member (when possible). Your team members will feel more comfortable with their jobs if they feel they are being asked to meet reasonable goals and perform within their abilities. On the other hand, when someone does not know what is expected of him, you may feel he is doing a poor job when, in reality, he simply does not know what you expect of him. (You may not know, eitherwhich is another reason a performance plan is useful!)


        The manager should measure each team member's progress in meeting the goals listed in the performance plan. If the organization's operating environment changes, the manager should work with the team members to change those goals.


        In many organizations, team members do not report directly to project managers; rather, they report to people who manage development or QA groups in the organization. However, a project team member whose goals are poorly defined or in conflict with the objectives of the project can threaten the project's success. When the success of the project is threatened, it is the project manager's responsibility to remove the threat. This may require that the project manager help the direct manager establish a performance plan.


        You may find that your project team members' professional goals, set by their direct managers, conflict with the objectives of your project. For example, a programmer on your team may feel that meeting the deadlines for delivering code is more important than carrying out code reviews and unit testing, which he sees as optional, "extraneous" activities. By failing to do code reviews and build unit tests, he meets his personal deadlines, but he causes the project to be late because it spends more time in testing. If the programmer reports to a development manager, for example, it is your job to bring this up with that manager. One way that you can suggest that he fix the problem is by building a performance plan that includes goals that are quality related.


        It is important to correct performance problems as early as possible. Many project managers make the mistake of waiting until the end of a project to try to address performance issues. If a team member is not doing his job properly, the project manager may not have the authority to fire or discipline the team member. But he can have that team member removed from his project, if he is unable to correct that person's behavior by either dealing directly with the team member or going to the direct manager. By addressing the problem as early as possible, the project manager limits the risk to the project.













        Scope Rules and Name Resolution Under Inheritance




        I l@ve RuBoard


        Scope Rules and Name Resolution Under Inheritance


        In C++, class scope can be viewed as nested under derivation. From this point of view, the scope of a derived class is enclosed by the scope of its base class.



        According to the general theory of nested scopes, whatever is defined in the inner scope is invisible in the outer, more global scope. Conversely, whatever is defined in the outer scope is visible in the inner, more local scope. In the next example, variable x is defined in the outer function scope, and variable y is defined in the inner block scope. It is appropriate to access the variable x in the inner scope. It is futile to access the variable y from the outer scope.





        void foo()
        { int x; // outer scope: equivalent to base class
        { int y; // inner scope: equivalent to derived class
        x = 0; } // ok to access the name from outer scope
        y = 0; } // syntax error: inner scope is invisible outside



        In this example, the outer scope plays the role of the base class and its members. The inner scope plays the role of the derived class and its members. From the derived class, you can access the members of the base class, but the members of the derived class cannot be accessed from the base class.



        This means that the derived class members are invisible in the scope of the base class. This should agree with your intuition because the base class should be designed, implemented, and compiled before the derived class is written. So it is only natural that the base class member functions cannot access the derived class data members or member functions.



        Conversely, base class members are in the outer scope and hence are visible in the derived class methods. Again, this concurs with your intuition because the derived class object "is a" base class object and has all member functions and data members that the base class has. From this point of view, the scope model of the relationship between the base and derived classes is not particularly helpful because it does not add much to your intuition. However, this model is very useful if the derived and base classes use the same names. Different languages use different rules to resolve these name conflicts, and the nested scope model, which is employed by C++, might be helpful in developing your intuition for writing C++ code.



        The derived class scope is nested within the base class scope, which means that the derived class names hide base class names within the derived class. Similarly, the derived class names hide base class names in the derived class client code. This is a very important rule that should become part of your programming intuition: If the derived and base classes use the same name, the base class name does not have a chance, as the meaning of the derived class name will be used.



        Let us clarify this rule. If a name without a scope operator is found in a derived class member function, the compiler tries to resolve the name as a name local to that member function. In the next code example, there are four variables that use the name x. All these variables are of the same type, but this is not important. They could be of different types, or some of these names could denote a function; the general rule I am discussing will stand anyway.





        int x; // outer scope: can be hidden by class or function
        class Base {
        protected: int x; // base name hides global names
        } ;

        class Derived : public Base {
        int x; // derived name hides base names
        public:
        void foo()
        { int x;
        x = 0; } } ; // local variable hides all other names

        class Client {
        public:
        Client()
        { Derived d;
        d.foo(); } } ; // using object d as a target message

        int main()
        { Client c; // define the object, run the program
        return 0; }



        In this code, you see a local variable in the member function foo() in the class Derived, a data member in the class Derived, a data member in the class Base, and a global variable in the file scope. The statement x = 0; in Derived::foo() sets the local variable x to zero. The derived data member Derived::x, the base data member Base::x, and the global name x are all hidden by this local name because the local name is defined in the most nested scope.



        Comment out the definition of the variable x in the method foo(). The statement x = 0; now cannot be resolved to the local variable because this name will not be found. If the name is not found in the scope of the statement (in this case, the derived class member function), the compiler looks up the derived class scope among class data members or member functions, depending on the syntax of the reference to the name. In the above code example, if the local variable x in Derived::foo() were absent, it would be the derived data member Derived::x that would be set to zero by the statement x = 0; in the derived member function Derived::foo().



        If the name mentioned in the member function is not found in the class scope either, the compiler searches the base class (and ancestor classes of the base class if they exist and if the name is not found in the base). The first name found in this search would be used to generate object code. In the code example, if both variables x in the Derived class were absent (the local variable and the data member), it would be the data member Base::x that would be set to zero by the statement x = 0;.



        Finally, if the name is not found in any of the base classes, the compiler searches for the name declared in the file scope (as a global object defined in the file scope or an extern global object declared in this scope but defined elsewhere). If the name is found in this process, it is used; if not, it is a syntax error. In the code example, if neither class Derived nor class Base used the name x, the global variable x would be set to zero by the statement in Derived::foo().



        Similarly, if a client of a derived class sends a message to a derived class object, the compiler searches the derived class first, and only after that does it look up the base class definition (or the base ancestor definition). If the derived class and one of its base classes use the same name, the derived class interpretation is used. The base names are not even looked up by the compiler if the name is found in the derived class. The derived class name hides the base class name, and the base name does not have a chance.



        A modified example with two classes, Base and Derived, is shown next. There are two functions foo() in this example: One is a public member function of class Base, and the other is a public member function of class Derived. Similar to the previous example, the client code defines an object of the Derived class and sends the foo() message to that object. Since the Derived class defines the member function foo(), the derived member function is called. If the Derived class did not define function foo(), then the compiler would generate a call to the Base class function foo(). The Base class function has a chance only if the same name is not used by the Derived class.





        class Base {
        protected: int x;
        public:
        void foo() // Base name is hidden by the Derived name
        { x = 0; } } ;

        class Derived : public Base {
        public:
        void foo() // Derived name hides the Base name
        { x = 0; } } ;

        class Client {
        public:
        Client()
        { Derived d;
        d.foo(); } } ; // call to the Derived member function

        int main()
        { Client c; // create an object, call its constructor
        return 0; }



        Notice that in this example I do not introduce the global scope. If neither Derived nor Base class (nor any of its ancestors) has a member function foo(), then the function call to d.foo() is a syntax error. If a function foo() were defined in the global scope, the function call d.foo() would not call this global function anyway.





        void foo()
        { int x = 0; }



        This global function is not hidden by the foo() member function in the Derived (or the Base) class because it has a different interface. The member functions are called with the use of a target object, and the global function is called using the function name only:





        foo(); // call to a global function



        The function calls we are discussing have a different syntactic form:





        d.foo(); // call to a member function



        This syntactic form cannot be satisfied by a call to a global function梚t includes a target object and hence can be satisfied only by a class member function.




        Name Overloading and Name Hiding


        Notice that in the previous discussion, the function signature was not mentioned as a factor to consider. This is not an omission. The function signature is not a factor. The signature does not matter.



        Of course I am being facetious. The function signature does matter when the compiler decides whether the actual argument matches the function's formal parameters. However, it does not matter for the resolution of nested inheritance scopes. If the name is found in the derived class, the compiler stops its search of the inheritance chain. What happens if the function found in the derived class is no good from the point of view of argument matching? Too bad梱ou have a syntax error. What if the base class has a better match, a function with the same name, and with the signature that matches the function call exactly? Too bad; it is too late: The base function does not stand a chance.



        Unfortunately, this is quite counterintuitive for many programmers. Please try to work with these nesting rules and on the examples to make sure you hone your intuition accordingly. Next is an example from my experience. I have pruned everything not related to the issue of hiding in nested scopes and left only a small part of the code.




        Listing 13.14 shows the simplified part of the hierarchy of accounting classes. I use class Account and class CheckingAccount only. The derived class overwrites the base member function withdraw(), but this is not going to play any role in the discussion. The client code defines CheckingAccount objects, sends them messages that belong to either the base class (getBal() and deposit()) or the derived class itself (withdraw()), and everything is fine. The output of the program run is presented in Figure 13-10.





        Figure 13-10. Output for the program code in Listing 13.14.










        Example 13.14. Example of inheritance hierarchy for Account classes.


        #include <iostream>
        using namespace std;

        class Account { // base class
        protected:
        double balance;

        public:
        Account(double initBalance = 0)
        { balance = initBalance; }

        double getBal() // inherited without change
        { return balance; }

        void withdraw(double amount) // overwritten in derived class
        { if (balance > amount)
        balance -= amount; }

        void deposit(double amount) // inherited without change
        { balance += amount; }
        } ;

        class CheckingAccount : public Account { // derived class
        double fee;

        public:
        CheckingAccount(double initBalance)
        { balance = initBalance; fee = 0.2; }

        void withdraw(double amount) // it hides base class method
        { if (balance > amount)
        balance = balance - amount - fee; }
        } ;

        int main()
        {
        CheckingAccount a1(1000); // derived class object
        a1.withdraw(100); // derived class method
        a1.deposit(200); // base class method
        cout << " Ending balances\n";
        cout << " checking account object: " << a1.getBal() <<endl;
        return 0;
        }


        Although the client code here is only a few lines long, in real life, it was about 200 pages long. The program evolved to reflect the changes in business conditions. One of the changes required was to add to class CheckingAccount yet another function deposit(), which could be used for international wire transfers. In these transfers, a transaction fee would be imposed depending on the amount and source of the transfer. This fee could be computed by the client code and sent to the CheckingAccount class as an argument. Hence, a simple way to support this change was to write another function deposit() with two parameters.





        void CheckingAccount::deposit(double amount, double fee)
        { balance = balance + amount - fee; }



        The client code for processing international transfers and for computing the fee required only a few pages to be added to the program. Here is an example of the new client code that calls this new deposit() function.





        a1.deposit(200,5); // derived class method



        So far, so good. The change went well, the new code ran fine. There was, however, a problem during system integration. These 200 pages of code that used to work so well before the change now did not work as well. Actually, the code did not work at all and would not even compile.



        Now, let me assure you that I used many languages before using C++, and I had never seen anything like this. I also suspect that whatever languages you used before C++, you never saw anything like this either. This is yet another contribution of C++ to software engineering that you should be aware of.



        Of course, we all have been in situations where adding some new code breaks the existing code, which no longer works correctly. Usually this happens because the new code interferes with the data that the existing code relies upon. But the existing code always compiles. In traditional languages, when you add new code, you do not get syntax errors in existing code.



        In C++, a program consists of classes that are linked to each other, not only through data but also through inheritance. Of course, the new code can make the existing code semantically incorrect by handling data incorrectly. This is possible in any language. But the new code can also make the existing code syntactically incorrect through the inheritance links! This is only possible in C++. This is why I press this point about programming intuition, needing to know the rules and developing a feel for correct and incorrect C++ code.



        Let us take a look at the reason for this "innovative" kind of programming trouble. This is how my new class CheckingAccount looks.





        class CheckingAccount : public Account {
        double fee;
        public:
        CheckingAccount(double initBalance)
        { balance = initBalance; fee = 0.2; }
        void withdraw(double amount) // it hides base class method
        { if (balance > amount)
        balance = balance - amount - fee; }
        void deposit(double amount, double fee) // new method
        { balance = balance + amount - fee; } // hides base method
        } ;



        When the compiler was processing the existing 200 pages of client code, the calls to the member function deposit() were aiming at the base class member function Account::deposit() with one parameter.





        a1.deposit(200); // base class method?



        According to the rules for the name resolution you just saw, the compiler analyzes the type of the message target, finds that the object a1 belongs to class CheckingAccount, and searches the class CheckingAccount for a member function whose name is deposit(). The compiler finds this function and stops the search through the inheritance chain. The next phase is, of course, signature matching. The compiler discovers that the method CheckingAccount::deposit() found in the derived class has two parameters. Meanwhile, the client code (which wanted to call the base class method) supplies only one parameter. The compiler tells me in no uncertain terms that I have a syntax error.



        I probably should have saved the joke about driving the tank for this discussion. It was clear to me that my code was correct and yet I found another bug in my compiler. (It does not matter what compiler it was. When learning a new language, you always find quite a few bugs in your compiler until you know the language better.)



        I would have liked very much if my compiler had treated this situation as function name overloading. I had the existing deposit() function with one parameter in the base class. I had the new deposit() function with two parameters in the derived class. But the object of the derived class was also an object of the base class! It had the inherited deposit() function with one parameter as well. My intuition was that the derived class had two deposit() functions, one with one parameter and the other with two parameters. And I would have liked very much if the compiler had used the rules for function name overloading and had picked up the right function, the one with only one parameter. However, as I said before, when a base method is hidden by a derived class method, the base method does not stand a chance. The overloading applies to several functions in the same scope. Hiding takes place between functions in nested scopes. Finally, I gave up and changed my thinking. It takes time, but I am sure you will be able to do the same.



        ALERT



        C++ supports function name overloading in the same scope only. In independent scopes, function names do not conflict, and you can use the same name with the same or different signatures. In nested scopes, the name in the nested scope hides the name in the outer scope, whether or not these names have the same signatures. If classes are related through inheritance, the function name in the derived class hides the function name in the base class. Again, the signature is not important.






        Figure 13-11 shows an object of the derived class with these two functions, one coming from the base class and the other coming from the derived class. The vertical arrow from the client code shows you that the compiler starts the search in the derived class. The compiler stops as soon as the name match is found (with any signature), and no attempt is made to get to the base class using the rules for name overloading. If the concepts of nested scopes for inheritance sound too abstract to you, use this picture to remind yourself that the search stops at the first match.





        Figure 13-11. How a derived class method hides a base class method in a derived class object.










        Calling a Base Method Hidden by the Derived Class


        There are several remedies for this situation. One remedy is to indicate in the client code what function should be called. The scope operator does the job well.





        int main()
        { CheckingAccount a1(1000); // derived class object
        a1.withdraw(100); // derived class method
        // a1.deposit(200); // syntax error
        a1.Account::deposit(200); // solution to the problem
        cout << " Ending balances\n";
        cout << " checking account object: " << a1.getBal() <<endl;
        return 0; }



        Please make sure you do not get excited about this solution. The obvious drawback of this solution is that it requires making changes to the existing code. The advantage of the object-oriented approach is that it favors adding to the existing code rather than modifying it. This solution, however, is labor extensive and error prone. To use this solution is to ask for trouble.



        From the software engineering point of view, this solution contradicts the principles of writing C++ code I discussed earlier. Which principles? Well, who bears the burden of the work in this solution? The client code. Who should carry the burden of the solution according to the principles of writing code? The server code. This solution fails to push responsibility down to the server classes. Instead, it brings responsibility up to the client code: you need to make sure that the base function is called梚ndicate explicitly that the base function should be called. This is a brute force solution.



        Make sure that you use the criterion of pushing responsibility to the server classes in your work. It indicates in what direction you should search for a good solution. Let us look at the Account inheritance hierarchy. Our goal should be to add to these classes a method (or methods) that would make the problem go away. Why would I want to add a method? Because I do not want to change existing methods. Why would I want to add methods to the inheritance hierarchy? Because these classes serve the client code, and I want to push responsibility to the server classes.



        One remedy is to overload the deposit() method in the base class rather than in the derived class. Since both functions belong to the same class and hence to the same scope, you have a case of legitimate C++ function name overloading. Both functions are inherited by the derived class and can be called through the derived class object as the message target. Here is the example of this solution.





        class Account { // base class
        protected:
        double balance;
        public:
        Account(double initBalance = 0)
        { balance = initBalance; }
        double getBal() // inherited without change
        { return balance; }
        void withdraw(double amount) // overwritten in derived class
        { if (balance > amount)
        balance -= amount; }
        void deposit(double amount) // inherited without change
        { balance += amount; }
        void deposit(double amount,double fee) // overloads deposit()
        { balance = balance + amount - fee; } } ;

        class CheckingAccount : public Account { // derived class
        double fee;
        public:
        CheckingAccount(double initBalance)
        { balance = initBalance; fee = 0.2; }
        void withdraw(double amount) // hides the base class method
        { if (balance > amount)
        balance = balance - amount - fee; } } ;

        int main()
        { CheckingAccount a1(1000); // derived class object
        a1.withdraw(100); // derived class method
        a1.deposit(200); // existing client code
        a1.deposit(200,5); // new client code
        cout << " Ending balances\n";
        cout << " checking account object: " << a1.getBal() << endl;
        return 0; }



        This is a good workaround. Notice that the solution is found in the form of adding code to the server class, not in the form of modifying the client code. This solution pushes the work to the Account class, and this is good. However, this solution requires opening and changing the base class and not the derived class. This is not desirable for configuration control reasons. The higher a class is in the inheritance hierarchy, the more we want to guard this class against change because the change can affect other derived classes. The lower a class is in the inheritance hierarchy, the safer it is to open and to change.



        Another problem with this solution is that the scope rules allow a base class member function to access base class data members only, not the derived class data. In my example, this is not a problem; both deposit() methods need only the base class data. Often, however, this is not so. The new method might need data that is defined in the derived class and is not available in the base class. For example, the standard withdrawal fee might be imposed on the deposit transaction as well. Then the new method deposit() could be implemented in the derived class only.





        void CheckingAccount::deposit(double amount, double fee)
        { balance = balance + amount - fee - CheckingAccount::fee; }



        However, putting the new method deposit() into the derived class takes us back to square one with the problem of the nested name scopes梩his function hides the base class deposit() function and renders the existing code, with the calls to deposit() with one argument, syntactically incorrect.



        A better remedy to this problem is to bite the bullet and place the new deposit() method where it belongs: in the derived class. To make the existing calls to the deposit() function with one legitimate parameter, you can overload the deposit() function in the derived class rather than in the base class. Again, the derived class is a server class for the client code, and this solution pushes responsibility to the server class.



        TIP



        Always look for ways to write C++ code such that the responsibility is pushed from the client code to the server code; thus the client code expresses the meaning of computations, not details of computations. This is a very general principle. It will serve you well.






        Listing 13.15 shows this solution. The derived class has two member functions deposit() with two different signatures. Since they both belong to the same class, the rules for name overloading stand. Both new code and existing code now call the member functions of the derived class using different signatures. All that the member functions with one argument should do is call the base class member function with the same name (push the work to the server). The output of the program run is presented in Figure 13-12.





        Figure 13-12. Output for program in Listing 13.15.










        Example 13.15. Example of inheritance hierarchy for Account classes.


        #include <iostream>
        using namespace std;

        class Account { // base class
        protected:
        double balance;
        public:
        Account(double initBalance = 0)
        { balance = initBalance; }
        double getBal() // inherited without change
        { return balance; }
        void withdraw(double amount) // overwritten in derived class
        { if (balance > amount)
        balance -= amount; }
        void deposit(double amount) // inherited without change
        { balance += amount; }
        } ;

        class CheckingAccount : public Account { // derived class
        double fee;

        public:
        CheckingAccount(double initBalance)
        { balance = initBalance; fee = 0.2; }

        void withdraw(double amount)
        { if (balance > amount)
        balance = balance - amount - fee; }

        void deposit(double amount) // hides the base class method
        { Account::deposit(amount); } // call to a base function

        void deposit(double amount, double fee) // hides base method
        { balance = balance + amount - fee - CheckingAccount::fee; }
        } ;

        int main()
        {
        CheckingAccount a1(1000); // derived class object
        a1.withdraw(100); // derived class method
        a1.deposit(200); // existing client code
        a1.deposit(200,5); // new client code
        cout << " Ending balances\n";
        cout << " checking account object: " << a1.getBal() <<endl;
        return 0;
        }


        Make sure that you are not intimidated by the use of scope operators in this example. The function deposit() with one parameter in class CheckingAccount could have been written this way:





        void CheckingAccount::deposit(double amount) // hides base method
        { deposit(amount); } // infinite recursive call



        When the compiler processes the body of this function, it first looks for a match between the name deposit() and a name local to the function. There are no names local to the function, so the compiler looks for a match among class members. It finds the name CheckingAccount::deposit() and generates a call to it. As a result, the call is interpreted as an infinite recursive call.



        The scope operator in Listing 13.15 directs the compiler to generate a call to the base function Account::deposit() and to avoid the trap of recursion. Notice that the responsibility to deal with the class hierarchy and decide to which class the deposit() function belongs is pushed down to the server class and not up to the client code as in my first remedy.



        The function deposit() with two parameters in class CheckingAccount could have been written this way.





        void CheckingAccount::deposit(double amount, double fee)
        { balance = balance + amount - fee - fee; }



        When the compiler processes the body of this function, it looks for a match between the name fee and a name local to the function. This name is the name of the function's second parameter. Even though the class CheckingAccount has a data member fee, this data member is hidden by the name of the function parameter. To access the class data member fee, the code in Listing 13.15 has to use the scope operator that overrides the scope rules.





        Using Inheritance for Program Evolution


        Often a good way to handle this kind of program evolution is to avoid the problem and its remedies altogether. The source of my difficulties with international wire transfers in Listings 13.14 and 13.15 was that I was trying to change existing code (classes Account and CheckingAccount) to accommodate new conditions.



        This is a natural way of thinking梖rom the traditional programming point of view. Object-oriented programming supported by C++ offers you an opportunity to think differently. Instead of looking for ways to change existing code, you could look for the ways to inherit from existing classes to support new requirements.



        Make no mistake桰 am talking about a new way of thinking about writing code. Using inheritance means that you are writing new code instead of changing existing code. Everyone who has ever tried to change existing code knows there is a world of difference between these two approaches. C++ offers a new approach to this little international wire transfer problem: leave the existing 200 pages alone, leave classes Account and CheckingAccount frozen, and introduce yet another derived class to support the new client code:





        class InternationalAccount : public CheckingAccount { // great!
        public:
        InternationalAccount(double initBalance)
        { balance = initBalance; }
        void deposit(double amount, double fee) // hides base method
        { balance = balance + amount - fee - CheckingAccount::fee; }
        } ;




        Listing 13.16 shows this solution. The classes Account and CheckingAccount are the same as in Listing 13.14. Yet another derived class, InternationalAccount, introduces no additional data members and only one member function, the function deposit(), which satisfies new client requirements. Since the objects, which are the targets of the deposit() messages with different numbers of parameters, belong to different classes, the issue of hiding or overloading does not arise. The object a1 is a target of the message with one parameter, and the compiler calls the function of the base class. The object a2 is a target of the message with two parameters, and the compiler calls the function of the class InternationalAccount derived from the class CheckingAccount. The output of the program run is presented in Figure 13-13.





        Figure 13-13. Output for the program in Listing 13.16.










        Example 13.16. Example of enhanced inheritance hierarchy for Account classes.


        #include <iostream>
        using namespace std;

        class Account { // base class
        protected:
        double balance;
        public:
        Account(double initBalance = 0)
        { balance = initBalance; }
        double getBal() // inherited without change
        { return balance; }
        void withdraw(double amount) // overwritten in derived class
        { if (balance > amount)
        balance -= amount; }
        void deposit(double amount) // inherited without change
        { balance += amount; }
        } ; // no changes to existing class

        class CheckingAccount : public Account { // derived class
        protected:
        double fee;

        public:
        CheckingAccount(double initBalance = 0)
        { balance = initBalance; fee = 0.2; }

        void withdraw(double amount) // hides the base class method
        { if (balance > amount)
        balance = balance - amount - fee; }
        } ; // no changes to existing class

        class InternationalAccount : public CheckingAccount { // great!
        public:
        InternationalAccount(double initBalance)
        { balance = initBalance; }

        void deposit(double amount, double fee) // hides base method
        { balance = balance + amount - fee - CheckingAccount::fee; }
        } ; // work is pushed to a new class

        int main()
        {
        CheckingAccount a1(1000); // derived class object
        a1.withdraw(100); // derived class method
        a1.deposit(200); // base class method
        InternationalAccount a2(1000); // new server object
        a2.deposit(200,5); // derived class method
        cout << " Ending balances\n";
        cout << " First checking account object: "
        << a1.getBal() << endl;
        cout << " Second checking account object: "
        << a2.getBal() << endl;
        return 0;
        }


        This is a very useful technique of program evolution. Instead of butchering existing classes and dealing with the dangers of invalidating existing client code, you derive another class from existing classes, which is responsible only for new program functionality. The use of C++ inheritance is the cornerstone of this new approach to software maintenance: writing new code instead of modifying existing code.



        Actually, class CheckingAccount does need some modifications. The first modification is making the private data member fee protected to make sure that the new derived class, InternationalAccount, is able to access this data member. Another approach would be to add to class CheckingAccount a member function that retrieves the value of this data member; the client code (in this case, InternationalAccount) would call this function to access the base class data. As I mentioned earlier, I prefer to make a few data members accessible to one or two derived classes than to create a set of access functions that will be used only by these new derived classes (in this example, just one derived class).



        Another way to avoid this modification to the existing class CheckingAccount is to exercise more foresight at the time of the class design. Why do you make class data members private? According to the principles of object-oriented programming, you do it for several reasons:





        • You do not want the client code to create dependencies on server class data names.





        • You do not want to complicate the client code with direct operations over data.





        • You do not want the client code to know more about server design than is necessary.





        • You want the client code to call server methods whose names explain the actions.





        • You want the client code to push responsibility for lower level details to servers.





        Notice that all these goals can be achieved by making server class data members protected rather than private. As I mentioned earlier, the protected keyword works like other access right modifiers, private and public, relative to different categories of class users. For derived classes, which are linked to the class by inheritance, the keyword protected works exactly as public works. It allows the derived classes direct access to the base class members. For client classes, which are not linked to the class by inheritance, the keyword protected works exactly as private does. There is no difference. If you think that program evolution through inheritance is possible, use protected access rights rather than private.



        TIP



        Always look for ways to use C++ inheritance for program evolution. Push responsibility from the client code to new derived classes. Weigh this approach against the drawbacks of creating too many small classes.





        I am careful to say that the issue here is program evolution rather than initial program design. During program design, some key base classes might wind up at the top of a tall inheritance hierarchy of classes that includes many derived classes. With a large number of potential class users, the issues of data encapsulation, information hiding, pushing responsibilities to servers become important. For these key classes, you might want to use the private modifiers to force even derived classes to use access functions. For program evolution, the classes you will be using for further derivations are themselves at the bottom of the inheritance hierarchies (class CheckingAccount is a good example). They will not have a large number of derived classes dependent on them, and the issues of data encapsulation, information hiding, and pushing responsibilities to servers lose their importance with the decrease in the number of dependent classes.



        The second modification is in the class CheckingAccount constructor. I added the default parameter value to avoid a syntax error in the client code when the object of the class CheckingAccount was created. This is similar to the issues I discussed for composite classes in Chapter 12. In the next section, I will discuss these issues as applied to the creation of C++ derived objects.








        I l@ve RuBoard