Best Practices
Procedural Programming
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