Best Practice
Object Oriented Programming
The following are general guidelines to proper coding practice
in object-oriented programming and are to be followed for all programming courses.
Some are industry standard practices and some are specifically for
academic purposes and training. The goal is to teach you to write clean, elegant, and
efficient code that is easy to read and understand. Not all of these standards
directly translate to industry standards, but they are all good practices
that will help train you become a better programmer. Note: All
procedural programming best practices
also apply to object-oriented programming.
[ companion video ]
The following applies to all programming courses unless otherwise noted.
Non-programming courses will have class specific guidelines provided by the instructor.
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.
- Use camel case naming convention such as "calculateGrade" or "linkedList".
This is the typical naming convention for object-oriented programming.
Python is an exception to this since the PEP 8 specification recommends underscore naming.
Modularity
-
A class or other ADT should be a modular unit with a clear interface and minimal knowledge of the program that uses it.
A useful test is: Could you give this class to another programmer and have them use it in a different program
without modifying the class itself or reproducing special assumptions from your application?
If so, the class is reasonably modular. If it depends on application-specific globals, control flow, UI code,
file locations, or other hidden assumptions, it is tightly coupled to that application.
-
Loose coupling does not mean having no dependencies.
A class may legitimately depend on other classes, libraries, or services. Those dependencies should be explicit,
well-defined, and themselves usable independently of the particular application whenever practical.
Ideally, the class depends only on the interfaces and behavior it actually needs, rather than on unrelated details
of the surrounding program.
-
Good OOP design pushes application-specific coordination outward.
Individual classes should concentrate on their own responsibilities, while higher-level application code connects
those classes together. This makes classes easier to understand, test, replace, reuse, and modify without forcing
changes throughout the rest of the program.
-
There are legitimate exceptions.
Some software components exist specifically to integrate with one application, framework, device, or external system
and therefore cannot be completely independent. The objective is not zero coupling; it is to avoid
unnecessary coupling and to make necessary dependencies deliberate and visible.
-
One class per file. Each class (or any sufficiently complex ADT) must be in its 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). See next point for exceptions.
-
An exception to the one and only one ADT per module 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 should 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.
- In C/C++, header files (.h or .hpp) can exist without a corresponding .cpp but
every .cpp should have its 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. As exception to this rule is
public variables or #defines that are constants (i.e. they never change).
- Every variable that is read (returned) or written (assigned) from the outside
of the class must have an appropriate getter/accessor (reading) and setter/mutator (writing)
for that variable (see below).
- All class variables must conform to proper naming conventions.
- Never return the memory address of an attribute.
Returning the memory address of an attribute effectively allows
the caller to modify the attribute directly, thus breaking encapsulation.
This is a strict rule. This is not the same as returning a pointer
to a dynamically allocated memory location, which is acceptable.
The difference is that a pointer to a dynamically allocated memory location
is not an attribute of the class, it is a temporary memory location
outside the memory scope of the class.
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 or protected.
-
Do not print from within a class method unless it is a specific printing method.
The same rules for printing in functions apply to methods.
- Methods should conform to all the guidelines for
functions.
Setters / Getters (aka Mutators / Accessors)
- Setter/Mutators are anything that modifies the object, not just things that target a specific attribute for assignment.
- Getters/Accessors are anything that returns any information about the object, not just things that return a specific attribute
- A getter/accessor should never modify the object's state in any way. If it does, it's not a Getter/Accessor.
- Getters and setters should not print.
- 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.
- A setter/mutator that accepts a value for storage internal to the object must include error checking
and correction. A setter should never leave an 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 internally to your class. Do not write to your own attributes
without using the setter for that attribute. This is because, if you have done
it correctly, the setter is where the protections and error checking for that
attribute are located.
- It is usually not necessary nor advisable to you use your getters within the class.
Just use the attribute directly.
Theoretical Discussion: 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 validity.
The 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.
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.
assets/images/