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