Best Practice

Object Oriented Programming


The following are general guidelines to proper coding practice in object-oriented programming. All procedural programming best practices also apply to object-oriented programming.

The following applies to all programming courses unless otherwise noted. Note that COSC 2325 Computer Organization / Machine Language and COSC 3301 Programming Languages do not count as programming courses for purposes of best practices and version control usage. Non-programming courses will have class specific guidelines provided by the instructor.

[ companion video ]


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.

Modularity

  • A class or other ADT must be 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.
  • There are real-world examples and exceptions to the self-contained rule, sometimes you have to tightly couple for application specific reasons. However, we always strive for maximal loose-coupling.
  • 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 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.
  • In C/C++, header files (.h or .hpp) can exist without a corresponding .cpp but every .cpp must 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)

  • Each setter and getter must correspond to one and only one attribute.
  • Getters and setters should not print.
  • A getter should only return its attribute's value. 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.
  • 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 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.