CPS 445 Programming Style Guide


It has been said that `programs must be written for people to read, and only incidentally for machines to execute' (ref. H. Abelson and G. J. Sussman, Structure and Interpretation of Computer Programs, MIT Press, preface to the first edition). Therefore, as discussed in class, it is important to follow some basic guidelines for writing source code. Follow the guidelines below for all programming assignments. Note: We may evolve this set of guidelines as the course progresses.

Remember, assignments provide you with an opportunity to show us that you care enough to submit a professionally-prepared submission. Practice good programming habits early and you will be rewarded with effective and efficient programs. Following this guide will improve the readability, writeabiliy, and maintainability of your programs and therefore reduce the likelihood of costly errors which will save you time in debugging. A portion of your grade for all work will be evaluated for style.
  • Begin each source file with the following header filled-in appropriately.
      /*******************************************************************************
      /
      /      filename:  doenv.c
      /
      /   description:  Implements the UNIX env utility.
      /
      /        author:  Linus Lowgator
      /      login id:  cps445-n1.xx
      /
      /         class:  CPS 445
      /    instructor:  Perugini
      /    assignment:  Homework #1
      /
      /      assigned:  January 18, 2006
      /           due:  January 25, 2006
      /
      /******************************************************************************/
      
  • Do not allow any line of code to exceed 80 characters in length. Most text editors have an option to give you column position. Find an appropriate place to break long program statements to continue them on the following line. Break long character strings using string concatenation.

  • Indent all code within a block.

  • Do not use tabs anywhere in your code. For each level of indentation, use three spaces. Tabs cause different amounts of horizontal spacing on different systems. By using spaces (and a fixed-width font), you guarantee your code will be properly indented for every system, editor, and printout.

  • Align corresponding opening and closing braces, begin or ends, or any other program unit delimiters. My preference for curly braces { } (or similar delimiters) is to always place the opening brace on the same line as the block it opens. This makes it easy to see where blocks of code, such as loops, begin and end, and does not waste a line of code. An alternate style is to place each brace on line by itself. You may use either of these styles, but do not mix them. Always be consistent.

  • Use descriptive (variable, constant, procedure, function) identifiers and use appropriate naming conventions for variables (total_sold) and constants (OUNCES_PER_TON). Remember, syntax should imply semantics.
      Descriptive: int dollars, average, weight
      Cryptic: int x, y, z
  • Initialize variables (to a value of the appropriate type) before you use them to avoid garbage. This can be done when you declare the variable or with an assignment statement before the variable is used.
      Incorrect: double radius = 3;
      Correct: double radius = 3.0;
  • Avoid type mismatches. Following this guideline will make your programs more portable.
      Consider:
      char c;
      
      while ((c = getchar()) != EOF) {
         ...
      }
      
  • Do not assign a variable or literal of one type to a variable of another, even if our compilers/interpreters permit it. Following this guideline will make your programs more portable.
      Consider: double avg_score = 76.7; int exam1 = 86;

      Incorrect: avg_score = exam1;
      Correct: avg_score = static_cast (exam1);

      Consider: double average = 0.0; int total = 967, num_students = 10;

      Incorrect: average = total/num_students;
      Correct: average = static_cast (total)/num_students;
  • Do not use goto.

  • Only use break in switch statements.

  • Avoid the use of global variables.

  • Avoid the use of static variables.

  • Use comments to explain critical subsections or any ambiguous parts of your programs (e.g., a tricky expression). If supported, do not use multi-line comments where they are likely to make your program less readable.

  • Use named constants rather than "magic" numbers. This gives you a single point of modification which will save you time and reduce bugs.
      Examples:
      #define SIZE 76
      
      const int NUMBER_OF_RECORDS = 101;
      
      const char A = 'a';
      const char E = 'e';
      const char I = 'i';
      const char O = 'o';
      const char U = 'u';
      
      switch (character) {
          case A:
          case E:
          case I:
          case O:
          case U:
      }
      
  • Always use enumerated types where they make your code more readable.

      Example:
      typedef enum { JAN = 1, FEB, MAR, APR, MAY, JUN, JUL, AUG, SEP, OCT, NOV, DEC } months;
      
         main() {
            months my_months;
      
            switch (my_months) {
               case JAN:
                  ...
                  break;
               case FEB:
                  ...
                  break;
      
               ...
      
               case NOV:
                  ...
                  break;
               case DEC:
                  ...
                  break;
            }
         }
      
  • Enforce the principle of least privilege.

  • Do not use local variables with same name in different scopes (they are different variables).

  • Always exit from main with a 0 exit status to indicate success and a non-zero status to indicate failure. Use exit rather than return to make your program more uniform. Use an int as a return type for main.

      Example:
      int main() {
         FILE* fp = NULL;
         char* filename = "input.txt";
      
         if ((fp = fopen (filename, "r")) == NULL) {
            fprintf (stderr, "cannot open %s\n", filename);
            exit (1);
         } else {
              ...
              exit (0);
           }
      
  • Always initialize pointer variables.

      Examples:
      Node* node_ptr = NULL;
      
      char* filename = "input.txt";
      
      FILE* myinstream = fopen (filename, "r");
      
  • When allocating memory, always verify that the memory was allocated successfully.

      Example:
      if ((node_ptr = malloc (sizeof (Node))) == NULL) {
         fprintf (stderr, "out of memory!");
         exit (1);
      } else {
           ...
           exit (0);
         }
      
  • Once finished, always free memory that you explicitly allocated.

      Example:
      if ((node_ptr = malloc (sizeof (Node))) == NULL) {
         fprintf (stderr, "out of memory!");
         exit (1);
      } else {
           ...
           free (node_ptr);
           exit (0);
         }
      
  • When opening a file, always verify that the file was opened successfully.

      Example:
      if ((fp = fopen (filename, "r")) == NULL) {
         fprintf (stderr, "cannot open %s\n", filename);
         exit (1);
      } else {
           ...
           exit (0);
         }
      
  • Always close files that you explicitly opened.

      Example:
      if ((fp = fopen (filename, "r")) == NULL) {
         fprintf (stderr, "cannot open %s\n", filename);
         exit (1);
      } else {
           ...
           fclose (fp);
           exit (0);
        }
      
  • Always print error and debugging messages to stderr (output written to stdout is buffered)

      Example:
      if ((fp = fopen (filename, "r")) == NULL) {
         fprintf (stderr, "cannot open %s\n", filename);
         exit (1);
      } else {
           ...
        }
      
  • Functions:

    • Always use a procedure/function prototype.
    • Use parameter names in procedure/function prototypes.
    • Use different identifiers for formal parameters and actual parameters to reinforce that they are different variables.
    • Precede every procedure/function with the following header explaining its purpose, the meaning of each parameter, precondition, postcondition, and the general strategy of its implementation, if applicable.
        /*******************************************************************************
        /
        / purpose: To compute the factorial of a non-negative integer.
        /
        /******************************************************************************/
        int factorial (int n) {
           if (n == 0) then
              return 1;
           else
              return n*factorial (n-1);
        }
        
    • No routine/subprogram, block, procedure, function, or method (or message) should exceed 50 lines of code.

  • Be consistent in your application of the above guidelines.

  • Overall, write your programs such that they are self-documenting. In other words, structure your code such that the program itself provides its own documentation. Self-documentation means using descriptive identifiers and a consistent, aligned format.


Return Home