A good program is like a well-written essay. It should be well-organized, clear, and where possible, concise. The logic should be straight forward and easy for another programmer to follow. Remember that your program will almost always be read by another human. In academia it will most likely be read by a TA and in industry it will most likely be extended and modified by other programmers. Like well-written papers, well-written programs go through multiple drafts, although the multiple revision process is infeasible in industry and has never been adopted as a teaching strategy in Computer Science. Nonetheless I have often observed that I could write a much cleaner piece of code if I had the time to re-write it.

I do not want to repeat what the other two essays on good program writing had to say so I will instead present a laundary list of items that contribute to better-written programs:

  1. Well chosen variable names: Too often I see one or two character variable names, like t1 or r, that provide no descriptive value. Even though it is a pain in the neck to choose and type longer names, it is important that your variable names be descriptive. For example, here is the classic code for swapping two variables:
    int temp;
    temp = a;
    a = b;
    b = temp;
    
    temp is a very non-descriptive name because it does not describe its purpose, which is to save the value of a. It is true that temp informs the reader that it represents temporary storage but it would be so much better to describe what it stores. Hence I would much prefer a variable name like save_a which is very descriptive of the variable's purpose.

    I think that it is sometimes okay to use short names, like i and j, for variables used in iterative loops. However, even in these cases I prefer more descriptive names when possible. For example, when looping through the rows and columns of a matrix, it is much more descriptive to write:

    for (row = 0; row < max_row; row++) {
      for (col = 0; col < max_col; col++) {
        printf("%d\n", matrix[row][col]);
      }
    }
    
    than to write:
    for (i = 0; i < m; i++) {
      for (j = 0; j < n; j++) {
        printf("%d\n", matrix[i][j]);
      }
    }
    
    The first block of code is self-explanatory; the second block is not. Since we all hate writing comments and never write enough of them, the least we can do is choose meaningful variable names.

  2. Place variable declarations at the beginning of a function only if they will be used throughout the entire function. If a variable is going to be used in only one location in the function, then place the variable's declaration right before its first use. That way someone reading your program won't have to break their focus by flipping to the top of the function to figure out what is the type of a variable.

  3. Use booleans for flags when the boolean type is provided by language designers. Many students get accustomed to using integer flags in C and never make the transition to using boolean flags when they learn C++ or Java. However, boolean variables can be more efficient than integers, both in terms of space and operations, and there is no danger of accidentally mis-using them in a non-boolean expression.

  4. Only allocate memory that you use and make sure you free it before it loses its last pointer. Too often I see students initialize a pointer variable with a block of memory when it is declared, and then "throw away" those blocks of memory when they are finally ready to use the variable. For example, consider the following piece of code:
    typedef struct node {
      int value;
      struct node *next;
    } list_node;
    
    list_node *new_node = (list_node *)malloc(sizeof(list_node));
    int current_item;
    
    // insert each new item at the head of the list
    while (scanf("%d", ¤t_item) != EOF) {
       new_node = (list_node *)malloc(sizeof(list_node));
       new_node->value = current_item;
       new_node->next = head_of_list;
       head_of_list = new_node;
    }
    
    Look at the two lines that I've bold-faced. The first line allocates a block of memory for new_node and the second line makes sure that 1) the block of memory is never used, hence wasting its allocation, and 2) the block of memory is not returned to the storage manager, thereby introducing a memory leak into your program. The bottom line: don't allocate memory until you're ready to use it. That way you won't accidentally write the type of inefficient code shown above.

  5. Make sure that the conditions in if statements do not duplicate conditions in an outer loop. Too often I see if statements that must be true because we would not be in the loop if they were false. Here's one of my favorites:
    // find the node whose value is equal to search_key
    for (current_node = head_of_list; 
         current_node != NULL; 
         current_node = current_node->next) {
      if ((current_node != NULL) && (current_node->value == search_key))
        return current_node;
    }
    
    The first test in the if statement is a complete waste of time. You wouldn't be in this loop if current_node were NULL because that's the continuation test for the loop. Don't repeat checks that must be true!

  6. Do not use temporary variables when 1) they are used only once, and 2) they are used to make a statement shorter. Here is an example:
    boolean EOF_p = read_next_line(&line);
    if (EOF_p)
      return;
    
    All the programmer has accomplished with EOF_p is to obfuscate the code and increase its cognitive load on the reader of the code. Now the reader of the code must keep track of EOF_p in short-term memory and remember that it's been assigned the return status of read_next_line. It's so much better to simply write:
    if (read_next_line(&line))
      return;
    
    Now I immediately understand the pre-requisite for returning and I don't have to struggle to remember the meaning of a temporary variable.