Best Practice
Procedural Programming
The following are general guidelines to proper coding practice
in procedural programming. All programming assignments must follow
these good practice guidelines (and so should real-world code).
Most examples are in Python but are valid
for any procedural programming. Python is used here only because
Python is used in COSC 1336 Programming I where you are first
introduced to best practices, however these guidelines apply to
all languages.
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.
- You may use underscore naming convention such as "calculate_grade" or "linked_list".
This is the typical naming convention for procedural programming.
- You may use camel case naming convention such as "calculateGrade" or "linkedList".
This is the typical naming convention for object oriented programming.
- Your 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;
- 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.
Spacing and Bracketing
- Use proper indenting and spacing to show code hierarchy and
logical blocks. Python forces this convention, and all other languages
should be done similarly regardless of whether the language requires it.
- Use brackets in languages that use brackets for logical grouping.
Use brackets even if it's a single statement that does not require brackets.
- Use consistent bracketing and spacing.
Whatever your preferred (and accepted) style, use it consistently
for all code. Do not change styles mid-code
or module to module. If you want to change styles, do it in the next
application.
- Spaces. Not Tabs. This does not refer to whether or not you actually press
the tab key, it refers to what happens when you press the tab key. Your editors should
be set to replace tab with four spaces.
- Use four spaces per indent.
Literals
- Do not use literals in your code without good reason (there usually is
not 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.
- 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.
- 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.
- 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.
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.
Modularity
A modularized program is a program wherein each task within the program
is in its own function. All applications should be modularized. It is a primary difference
between real software engineering and simply "scripting."
- Don’t repeat yourself. If you repeat yourself, 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++ Don't 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 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.
- Functions: Modularity is accomplished mostly through functions (aka procedures, methods, sub-routines, etc.)
A function is a group of statements within a program that perform
one-and-only-one specific task.
Functions are one task of a larger program. Programs will almost always be collections of functions (in procedural programming).
Functions can call other functions.
Functions can call themselves (i.e. recursion).
- call: Using a function to perform its task.
- return: The result of calling a function.
- return statements Functions should have one-and-only-one return statement.
Rarely it is okay to have two returns when you have two obviously opposite returns
like returning True or False at the end of your function.
More than two returns indicates you are not structuring your
function or your logic correctly.
All programs written
for programming courses are allowed one-and-only-one return to teach proper architecture,
logic, modularity, and elegant, clean code practices.
- one-and-only-one task: A function has
one specific purpose and that purpose should be singular and completely self-contained.
- length: Functions should be no more than a couple dozen 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.
- printing: Do not print from functions! A function should almost never print unless it is a specific printing function.
For example, a print_results(results) function whose only job is printing something.
Other than that, never print from functions.
Benefits of Modularity
- Simpler code: All code is grouped into logical “blocks.”
- Code reuse: Write once, use many.
- Better testing and debugging: You can test, debug, fix, and improve functions
by themselves without necessarily testing and debugging the entire program.
- Faster development: Multiple people can work on multiple functions simultaneously.
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." For example, in
Python you may have main.py and in inside that file is
def main(): In C/C++
you will have main.c or main.cpp and inside them you will have
int main(){} or
int main(int argc, char **argv){} In web programming you will have index.html or
index.php or something similar. Driver files and functions have special rules and exceptions
to general rules. In particular:
-
Applications have one-and-only-one driver (except JavaScript, see below).
-
One-and-only-one function should be in the driver file, which is your main function.
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 24 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.
This does not get you out of the "do not use literals" rule. Nothing gets you out
of that.
-
In JavaScript the driver is harder to see and judge, but it is generally any
function that is called as a response to a user action or the one called on
a page load. In this case, your application may have many drivers, one for
each user action or one for every page load.
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++.
- You can pass values into functions as well as return values from functions.
- Those variables passed are called parameters (the value in the parameter
is an argument) and they are local in scope.
- Any variable created inside a function is local to that function.
- Local scope means:
- Variables exist only inside their function (scope).
- Variables cannot be accessed outside their function (scope).
- Variables in different scopes can have the same name but are not the same variable.
Global Scope
- Global variables are ones that are available everywhere in your program.
- Do not use global variables without VERY good reason.
There is almost never a good reason.
"It was easier" is almost always a bad reason.
- An acceptable and proper use of globals are global constants. Use global constants
instead of literals.
- All variables should be local to some function (in procedural programming).
No variables should be global unless they are constants or there is
a specific architectural requirement that
demands the use of a global
(very rare and there is almost always a better way to do it).