Best Practices

Procedural Programming


The following are general guidelines to proper coding practice in procedural programming.

The following applies to all programming courses unless otherwise noted. Note that COSC 2325 Computer Organization / Machine Language and COSC 3301 Programming Languages do not count as programming courses for purposes of best practices and version control usage. Non-programming courses will have class specific guidelines provided by the instructor.

[ companion video ]


Format, Spacing, Bracketing

  • Python formatting is defined by the language itself and is not optional. You must follow the formatting guidelines for Python code. Failure to do so will result in a syntax error and your code will not run.
  • For C-style code, follow the formatting guidelines given on this site.

Naming Conventions

  • All names (variables, functions, objects, files, etc.) must be named with descriptive names. You may not use cryptic names like "x" or "variable" or "a1", "a2", etc. An exception to this are the accepted counter variables such as i, j, k.
  • Use underscore naming convention such as "calculate_grade" or "linked_list" for procedural programming.
  • Use camel case naming convention such as "calculateGrade" or "linkedList". for object-oriented programming.
  • Constants must be capitalized such as:
    • C/C++: const int MAX = 10;
    • C/C++ defines: #define MAX 10
    • Python: MAX = 10
    • PHP: const MAX = 10;
    • PHP defines: define('MAX', 10)
    • JavaScript: var const MAX = 10;
  • Only constants should be capitalized, with one exception. In data science applications and languages such as Python and R, matrix and dataframe variables may be capitalized to distinguish them from other variable types.
  • Class names and other ADT definitions (e.g. struct and unions in C/C++) must have a capital first letter. No other names should have a capital first letter.

Namespaces

  • Never import and entire namespace. For example, in C/C++, never do this
    using namespace std;
  • The preferred method is to specify namespaces per usage. Such as
    std::cout << "hello world" << std::endl;
  • Alternatively, an acceptable, but not ideal, general form is
    using std::cout;
    using std::endl;
  • You may not use using namespace std; for class assignments. You may use either of the acceptable methods.
  • In Python and other languages which import whole namespaces by default, it is sometimes better to use a partial import. For example, instead of import math, you may do from math import sqrt, pi if you only need those two functions/constants. This is not required, but is sometimes better for readability and performance.

Literals

  • Do not use literals in your code without good reason (there is rarely a good reason).
  • Place literals that are used in multiple scopes in a global constant (see below).
  • Place literals that are used only in local scope at the top of that scope.
  • Exceptions to this rule are the literals -1, 0, 1, which are usually used in such as way that they will not change. For example using 0 to start a for loop or using -1 as an error condition. There are almost no other exceptions to this rule.

Boolean Expressions

  • Compound boolean expressions should not generally be more than 3-4 expressions long (there are exceptions which will be discussed in class).
  • Compound booleans that are 5+ expressions long are not necessarily bad, but it’s probably an indication you are checking/doing too many things at once and should rethink your logic.
  • Checking 6+ booleans is usually an indication your current module is too complex.

Branching (i.e. If/Else Statements)

  • Nested if/else should not be more than 2-3 if/else statements long.
  • Nesting 4+ if/else is usually an indication your current module is too complex.
  • An exception to this is the strict if/else/if statement. By definition those can be as long as you need them to be.

Repetition Statements (i.e. Loops)

  • Nested loops should not be more than 2-3 loops long.
  • Nesting 4+ loops is usually an indication your current module is too complex.
  • Do not write purposeful infinite loops like while True: or while(1){}. There is almost always a better way to accomplish what you are trying to do.
  • Do not call functions in loop headers. This is a common mistake that can cause infinite loops and other problems. For example, do not do this: for (int i = 0; i < getCount(); i++) Instead, do this: int count = getCount(); for (int i = 0; i < count; i++) This is because the function getCount() may return a different value each time it is called, which can cause the loop to behave unpredictably. Also, it is more efficient to call the function once and store the result in a variable, rather than calling the function multiple times for no reason.
  • Do not use break or continue. Using break or continue usually indicates your logic is faulty. This is also a rule for the same reason as above. Loops formed correctly and logically can usually be optimized by the compiler/interpreter. Break and continue can disrupt logical loop flow and slow execution. Plus, it makes it look like you don't know what you're doing. Click on examples below.
  • Do not place if statements inside a loop if you can help it. Sometimes you have to, but not usually. If statements inside a loop usually indicates your logic is faulty and slows program execution for no reason. Click on examples below.
  • Use determinant loops (i.e for loops) when you know the number of things being counted. Do not use determinant loops (i.e for loops) when you don't know the number of things being counted.
  • Use indeterminate loops (i.e. while loops) when you don't know the the number of things being counted. Do not use indeterminate loops (i.e. while loops) when you know the the number of things being counted.
Theory Discussion

There are reasons for using determinant and indeterminate loops correctly which matters both for performance and readability. For loops can easily be optimized by the compiler/interpreter if-and-only-if they are formed as determinant loops. While loops cannot usually be optimized (or at least not well), even if they are determinant. In addition, when you use for and while loops properly it makes your code more readable. When others read your code and see for or while, and you have used them correctly, they instantly know if the loop is determinant or indeterminate. When you use them incorrectly, your code is confusing and indicates you probably don't understand computer science or best practices. Click on examples below.

There is a relatively new (2011) "for loop" in C++ which is known as range-based for loop. It is a for loop that iterates through a collection of items. It is not a determinant loop even though it is called a for loop. It is a special type of indeterminate loop. You may use it where you would use a while loop, but do not confuse it with a determinant for loop or where you should use a determinant for loop. The performance of the range-based for loop is similar to a while loop. Study this code to understand the difference.

Code Examples




Modularity

  • Don’t repeat yourself. If you repeat code, it’s an indication your logic is faulty. Move repeated code into a function or into a repetition structure. Repeated code is usually wrong code.
    • Follow the idea of "write once, use many," which means create functions to do simple useful jobs, and then call that function whenever needed to do that job.
    • If there is a function to do some job (whether you wrote it or not), use it, don't rewrite code to do the same thing.
    • In C/C++ do not name parameters in prototypes, this is a minor form of repeating yourself. Point being, don't write code where if you need to change something, you need to change it in two or more places. Changes should always be "single source."
  • Do not use exit() or quit() anywhere in your program except possibly at the very end (i.e. a final return from main or your driver module). Even then, it is not necessary.
  • Function should perform one and only one logical self-contained task. This is usually very small.
  • Functions should be no more than 20 lines long. This is not a hard and fast rule, but it’s a very important guide. If your function is longer than this, you are likely breaking the one and only one job rule.
  • Do not write 1-2 line functions. A function should be at least a 3+ meaningful lines long. There are a few exceptions to this rule:
    • getter/setters which are needed to protect attributes, but require very little code to do so
    • a function needed to centralize some value processing that is also used in multiple places; this centralizes logic so if rules change, updates are only in one place
    • academically, you may be required to make 1-3 line functions as part of the learning process since our in-class assignment are simplified for learning purposes
  • Functions do not print. A function should almost never print unless it is a specific printing function. For example, a function that prints could be a print_results(results) function where the only job is printing. Another exception to "functions do not print" rule is implementing a verbose mode within a function for debugging or informational purposed. This should be done with a boolean parameter such as verbose that is set to false by default. If the parameter is set to true, then the function can print information about what it is doing. This is not ideal, there are better ways to handle this, but it is an acceptable exception to the "functions do not print" rule.
  • One and only one return statement per function/method. All programs written for programming courses are allowed one and only one return to teach proper architecture, logic, modularity, and elegant, clean code practices.

Driver Files and Functions

In all programming you will have what is known as a "driver file" and in it will be the "driver function." In most programming this is known as "main." Driver files and functions have special rules and exceptions to general rules. In particular:
  • Applications have one and only one driver (except JavaScript and other client-service style architectures, see below).
  • One and only one function should be in the driver file: main(). Do not place other functions in your main file.
  • In some languages / applications it is okay to have no function in your driver file and the code is not in a function. In this case, the file itself is the "function."
  • Driver functions are generally exempt from the 20 lines long rule. Driver functions may sometimes need to very long, and it's generally considered okay. This does not get you out of the "do not repeat code" rule, but it softens that rule a bit.
  • In JavaScript (and similar microservices and client-server architectures) the driver is harder to see and judge, but it is generally any function that is called as a response to an outside actor. In this case, the "application" may have many drivers, one for each outside call.

Parameters, Variables, and Scope

  • A variable used to store a value for the sole purpose of being used in the next line is known as an intermediate variable. Do not use intermediate variables unless there is a very good reason, and there usually isn't.
  • Do not declare variables in loops! This is sometimes okay in dynamic languages like Python where for some reason (good reason) you purposefully want to create and destroy a variable over-and-over, but it's never acceptable in a static language like C/C++.
  • All variables must be local which means:
    • Variables exist only inside their function (scope).
    • Variables cannot be accessed outside their function (scope).
    • Variables in different scopes may have the same name but are not the same variable.
  • Do not use global variables without very good reason. There is almost never a good reason. "It was easier" is always a bad reason.
  • An acceptable and proper use of globals are global constants. Use global constants instead of literals.
Local Scope Example

Global Scope Example