Best Practice
Object Oriented Programming
The following are general guidelines to proper coding practice
in object oriented 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 object oriented programming. Python is used here only because
Python is used in COSC 1336 Programming I where you are first
introduced to best practices.
Naming Conventions
- Class names (and all ADT names) must be named with
descriptive names. You may not use cryptic names like "X" or "MyClass" or "A1", "A2", etc.
-
Class names and all abstract data type (ADT) names must be named with a capitalized first character. Note
this applies to classes and ADT definitions, not the variable names of the objects
made from the class or ADT definitions. No other names should have a capital first letter.
- You should use camel case naming convention such as "calculateGrade" or "linkedList".
This is the typical naming convention for object oriented programming.
- You may (but probably shouldn't in OOP) use underscore naming convention such as "calculate_grade" or "linked_list".
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 class or other ADT must be completely self contained and
capable of being used as-is in any program. For example, when you make
a class and use it in your program, ask yourself this question, could you give
just that class to another programmer to use in their
program with no modifications or special instructions? If "yes," then
you've done it right. If "no," then you have a tightly coupled ADT which is usually
bad practice.
-
The previous point does not negate the possibility of dependencies. Your class or ADT
may have dependencies but those dependencies should generally not be something special to your
program where you use the class. The dependency itself should be another library, class,
ADT, etc. that is also self contained and needs nothing unique to your program. If the
dependency also has dependencies, the same rules apply, and so on.
-
Each class (or any sufficiently complex ADT) must be in it's own file named
appropriately for that class or ADT. For example in C++ if you have a "Dog" class
then you should have a dog.cpp and dog.h that completely encapsulates
the Dog class and is not tightly coupled to your program. In languages
without header files, the same rule applies, but you would have one
code file (e.g. dog.py, dog.js, dog.php) and no header file.
Remember, one-and-only-one ADT per module (file).
-
An exception to the above rule are simple ADTs not worth placing in their own file.
For example, in C/C++ it is often acceptable to make an ADT file with collections
of things like simple structs which are logically similar or related. Another example,
if a class needs another simple ADT (not another class) like a struct, and only that
class uses the struct, it is acceptable to place that ADT in the class header with the
class prototype. Simple rule of thumb, if the ADT has functionality (i.e. it can do
something), it must have its own file. If the ADT is small and data only (i.e. it
can only be used to store data by something else), it may be placed with other ADTs.
However, remember, classes are almost always in their own file. One class per file without
exception.
- In C/C++, header files (.h or .hpp) can exist without a corresponding .cpp but
every .cpp must have it's own .h or .hpp.
Attributes / Properties (i.e. class variables)
- Attributes are strictly for describing the class as a whole. It is never
acceptable to use attributes as global class variables to avoid using proper scoping.
For example, assume multiple methods return an integer for various purposes. It is not
acceptable to create a generic integer attribute for all the methods to use as
their return value. Each method must make its own local variables.
- Class variables (attributes) should be kept to an absolute minimum.
Attributes are not to be used to get out of proper modularity and passing/returning.
- All class variables must be private or protected unless there is good reason
to make them public. There is almost never a good reason, and there will
never be a good reason in class assignments.
- Every variable that is read (returned) or written (assigned) from the outside
of the class must have an appropriate getter (reading) and setter (writing)
for that variable (see below).
- All class variables must conform to proper naming conventions.
Methods (i.e. class functions)
- Class methods should be public if-and-only-if it is
necessary
to call that method from outside the object. All other methods
should be private.
-
A class method (or any ADT) should almost never print unless it is a specific
printing method. For example, a getter should not print
it's variable's value, it should only return it. An exception
to this rule is a specific print method, for example printObject()
which could be a method to print out all the object's variables
for debugging purposes. Simple rule: unless printing is a method's
sole purpose, almost no method should ever print. There are exceptions
but they are rare and for specific well-established reasons such as
console logging or enabling a "verbose" mode for your object.
- Methods should conform to all the guidelines for
functions.
Setters / Getters (aka Mutators / Accessors)
- Each setter and getter must correspond to one-and-only-one attribute.
- A getter and setter should not print.
- A getter should only return its attribute's value, and is an exception to the
"do not write one line functions" rule (i.e. it usually only has a return line).
The attribute being returned should never be modified, just return it.
A getter should not modify any other attributes (there are rare exceptions).
- A getter should not return the memory location of any attribute's value.
This would effectively make the attribute modifiable by the caller thus breaking
encapsulation. This does not apply to temporary (dynamically allocated) memory locations
you want to return for some reason, it applies specifically to an object's
attributes.
- A setter must include error checking and correction
for its attribute. A setter should never leave an attribute
or the object in an ambiguous or inconsistent state.
- Not all attributes need a setter or getter. Use them only where
they are needed, not by default.
- Use your setters and getters internally to your class. Do not
read or write your own attributes without using the setter and getter for
that attribute. This is not a hard rule and applies more to setters than getters
(i.e. this is a 'loose' rule, and should be applied intelligently).
The point being, the setter is where your attribute's protections lay.
Think of a setter as an attribute's "gateway."
You should not be "talking" to an attribute directly, you should be going
through it's "gateway" where the attributes protections
will keep that attribute from being set incorrectly.
- There is a semi-valid argument in modern programming that you should not
have traditional setters or getters at all. This argument does NOT mean you should give direct
access to attributes, it means that direct outside access should not be given at all.
This is too harsh and restrictive, but the argument that setters and
getters violate OOP and encapsulation has some logic and validity. As such,
the more reasonable guideline is; do not make public
setters or getters unless they are absolutely needed and there is no other way
to achieve what you are trying to do.
In other words, try to design your class so that just using
it properly sets and gets values as needed, while keeping the actual properties of
your class abstract. This will be discussed more in class.
Constructors (aka initializers)
- When a constructor completes, your object or ADT should be in
a viable and ready to use state. No further "set-up" should be needed
to use your object or ADT.
- Use your own setters in your constructor. This does not mean you have
to have public setters, but it does mean you should have
one-and-only-one "gateway" to an attribute where protections
and error correction are centralized for that attribute.
- Constructors should almost never print. If it is necessary to get
information back from a constructor, then use some other method
such as throwing an error, writing to a file or database, etc.
There are exceptions such as console logging or enabling a
"verbose" mode for your object, but these are rare and should only
be done in well-understood and necessary cases.