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.
  • Source code files must be readable by vi and contain only UNIX newlines (only line feeds). In other words, source code files must not contain non-UNIX newlines (line feed and carriage return pairs, e.g., ^M s).
  • Assignments must be prepared exclusively using UNIX systems.
  • Begin each source file with the following header filled-in appropriately.
      /*******************************************************************************
      /
      /      filename:  env.c
      /
      /   description:  Implements the UNIX env utility.
      /
      /        author:  Last, First
      /      login id:  FA_14_CPS444_03
      /
      /         class:  CPS 444
      /    instructor:  Perugini
      /    assignment:  Homework #1
      /
      /      assigned:  September 6, 2014
      /           due:  September 13, 2014
      /
      /******************************************************************************/
      
  • Begin each shell script file with the following header filled-in appropriately.
      #*******************************************************************************
      #
      #      filename:  filter
      #
      #   description:  Implements a filter script.
      #
      #        author:  Last, First
      #      login id:  FA_14_CPS444_03
      #
      #         class:  CPS 444
      #    instructor:  Perugini
      #    assignment:  Homework #1
      #
      #      assigned:  September 6, 2014
      #           due:  September 13, 2014
      #
      #*******************************************************************************
      
  • 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. Investigate the use of the UNIX utility indent. You can define your own indent profile, named .indent.pro, and place it in your home directory. Running indent onyour source code files using this profile is an easy way to ensure consistencyin your coding conventions.

  • 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.
      Cryptic: int x, y, z
      Descriptive: int dollars, average, weight
  • 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 (getchar() returns an int}):
      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;
  • Avoid the use goto unless necessary.

  • Avoid the use of global variables.

  • Use comments to explain critical subsections or any ambiguous parts of your programs (e.g., a cryptic or obfuscated expression).

  • Use named constants rather than magic numbers, and use the #define preprocessor directive to create named constants. This gives you a single point of modification which will save you time and reduce bugs.

      Original:
      const int SIZE 76
      const int NUM_OF_RECORDS = 101;
      const double RATE = 3.188;
      
      const char JOB_ARRIVAL = 'A'
      const char IO = 'I'
      const char JOB_TERMINATION = 'T'
      
      ...
      
      switch (event) {
          case JOB_ARRIVAL:
          case IO:
          case JOB_TERMINATION:
      }
      
      Recommended:
      #define SIZE 76
      #define NUMBER_OF_RECORDS 101
      #define RATE 3.188
      
      #define JOB_ARRIVAL 'A'
      #define IO 'I'
      #define JOB_TERMINATION 'A'
      
      int main() {
         ...
         switch (event) {
            case JOB_ARRIVAL:
            case IO:
            case JOB_TERMINATION:
         }
         ...
      }
      
  • 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.

  • Avoid using 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");
      
  • Avoid allocating more memory than necessary for anything.

  • When allocating memory by calling sizeof in a call to malloc always pass a variable to sizeof rather than a datatype.
      Original:
      int* array = NULL;
      
      array = (int*) malloc(sizeof(int)*10);
      
      Recommended:
      int* array = NULL;
      
      array = malloc(sizeof(*array)*10);
      

      The approach is recommended because if you decide later to change the type of ptr, then you only have to change the type in the declaration (i.e., the line containing the call to malloc need not change at all). This style is an aid to program modification because the definition(s) may be a few hundred lines of code below the declaration. Using the original approach, if the type changes, it must be changed in three places: the declaration, the type cast, and the argument to sizeof. See here for more information. Note also that there is no need to cast the result of malloc (see here for details; though other opinions exists here).

  • When allocating memory, always verify that the memory was allocated successfully.

      example:
      if ((node_ptr = malloc (sizeof (*node_ptr))) == 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_ptr))) == 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 line buffered).

      example:
      if ((fp = fopen (filename, "r")) == NULL) {
         fprintf (stderr, "cannot open %s\n", filename);
         exit (1);
      } else {
           ...
        }
      
  • Avoid buffer overflows.
      Consider:
      char password[17];
      printf ("Please enter your password: ");
      
      Incorrect: scanf ("%s", password);
      Correct: scanf ("16%s", password);
  • Use perror (errno.h) and/or strerror (string.h) to display error messages where appropriate.

  • Make appropriate use of qualifiers such as const, restrict, volatile, and register on function parameters and elsewhere (in the case of const, volatile, and register).

  • 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.

  • The following guidelines are from UNIX Systems Programming: Concurrency, Communication, and Threads by K.A. Robbins and S. Robbins. Prentice Hall, 2003 (pp. 29-30):

    ``Error handling is a key issue in writing reliable systems programs. When you are writing a function, think in terms of that function being called millions of times by the same application. How do you want the function to behave? In general, functions should never exit on their own, but rather should always indicate an error to the calling program. This strategy gives the caller an opportunity to recover or shut down gracefuly.

    Functions should also not make unexpected changes to the process state that persist beyond the return from the function. For example, if a function blocks signals, it should restore the signal mask to its previous value before returning.

    Finally, the function should release all the hidden resources that it uses during its execution. Suppose a function allocates a temporary buffer by calling malloc and does not free it before returning. One call to this function may not cause a problem, but hundreds or thousands of successive calls may cause the process memory usage to exceed its limits. Usually, a function that allocates memory should either free the memory or make a pointer available to the calling program. Otherwise, a long-running program may have a memory leak; that is, memory "leaks" out of the system and is not available until the process terminates.

    You should also be aware that the failure of a library function usually does not cause your program to stop executing. Instead, the program continues, possibly using inconsistent or invalid data. You must examine the return value of every library function that can return an error that affects the running of your program, even if you think the chance of such an error occurring is remote.

    Your own functions should also engage in careful error handling and communication. Standard approaches to handling errors in UNIX programs include the following.

    • Print out an error message and exit the program (only in main).
    • Return -1 or NULL and set an error indicator such as errno.
    • Return an error code.
    In general, functions should never exit on their own but should always report an error to the calling program. Error messages within a function may be useful during the debugging phase but generaly should not appear in the final version. A good way to handle debugging is to enclose debugging print statements in a conditional compilation block so you can reactivate them if necessary.''


  • The following guidelines are from UNIX Systems Programming: Concurrency, Communication, and Threads by K.A. Robbins and S. Robbins. Prentice Hall, 2003 (pp. 30-31):
    ``Most library functions provide good models for implementing functions. Here are some guidelines to follow.
    1. Make use of return values to communicate information and to make error trapping easy for the calling program.
    2. Do not exit from functions. Instead, return an error value to allow the calling program flexibility in handling the error [Explicitly set errno for all errors, and do not rely on the fact that a function which fails may set errno automatically for you. Common errors include exceeding available memory or file I/O open/close, read/write errors. See the GNU webpage for libc for a list error codes which are #defined in error.h (e.g., use ENOMEM for the former and EIO for the latter errors above)].
    3. Make functions general but usable. (Sometimes there are conflicting goals.)
    4. Do not make unnecessary assumptions about sizes of buffers. (This is often hard to implement.)
    5. When it is necessary to use limits, use standard system-defined limits, [e.g., MAX_CANON, #defined in limits.h] rather than arbitrary constants.
    6. Do not reinvent the wheel -- use standard library functions when possible.
    7. Do not modify input parameter values unless it makes sense to do so.
    8. Do not use static variable or dynamic memory allocation if automatic allocation will do just as well.
    9. Analyze all the calls to the malloc family to make sure the program frees the memory that was allocated.
    10. Consider whether a function is ever called recursively or from a signal handler or from a thread. Functions with variables of static storage class may not behave in the desired way. (The error number can cause a big problem here.)
    11. Analyze the consequences of interruptions by signals.
    12. Carefully consider how the entire program terminates.''

  • Do not use a system call (e.g., open) where a library call (e.g., fopen) will suffice.

  • Shell scripts:

    • Always terminate with a proper exit statement (0 for success and non-zero for failure).
    • Always start with a proper interpreter directive.
        #!/bin/ksh
        #!/bin/bash
        
        or
        #!/usr/bin/env ksh
        #!/usr/bin/env bash
        
  • 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