Click here to Skip to main content
Click here to Skip to main content

10 Golden Rules Of Good OOP

, 8 May 2014 CPOL
Rate this:
Please Sign up or sign in to vote.
A good architecture means money saved in learning, maintaining, testing, fixing, extending and scaling source code. The following guidelines are not exhaustive and are meant to be applied on top of the SOLID principles and proper use of OO Design Patterns.

Introduction

A good architecture means money saved in learning, maintaining, testing, fixing, extending and scaling source code. This requires more time and care for the initial development, but quickly returns the investment with great interests.

Inevitably, even the best designed architectures need some adjustment and refactoring in time.

Patches, modifications and last minute changes are part of the IT business, therefore, ‘quick and dirty’ solutions, when needed, should always try to confine themselves in an isolated place where it will be easy to refactor later on, and that will affect as minimum as possible all the other components.

The following guidelines are not exhaustive and are meant to be applied on top of the SOLID principles and proper use of OO Design Patterns.

1. Avoid Almighty Classes, Use Meaningful Names

When designing a new class or refactoring an existing one, a developer should make a list of all of the tasks performed by that class and come up with a name that easily and shortly represents what the class does.

If the class does too many things, a representative name is usually something monstrous like: PageInfoBuilderAndConfigurationLoaderAndLinkAnalizerAndCacheManager. That means the class has too many responsibilities and should be broken into multiple components, ideally, with one responsibility each.

When using OO Design Pattern, it is much easier come up with meaningful names, since the design pattern itself frequently identifies the type of responsibility.

For example:

  • If the class creates objects that use the suffix Factory or Builder
  • If the class is responsible for coordination and communication between other business classes, then use the suffix Mediator or Façade
  • If the class is used to control the use of a resource class, then Proxy would be a good suffix
  • If a class is wrapping another in other to adjust its use for a consumer class, then use the Adapter suffix

2. Avoid Too Many If or Switch Statements

Excessive conditional logic makes the head of the developer spin like Regan in The Exorcist. Things become uglier when the same conditional logic is applied in many places in the application. It means that different business behaviors are managed with ifs and switches instead of being properly managed using inheritance or composition, separating the different behaviors into different implementations of a common abstract class or interface. For instance, there is a configuration variable that is telling if data should be stored in a database or in a file. Then everywhere in the code wherever we have to save something, we use the conditional logic:

If (ConfigParameters.SaveTo == StorageType.DataBase) 
           [… Save something in the db]
Else 
           [… Save something to file]

Imagine that we need to add a different behavior (for instance, save to a distributed cache): now we have to extend the conditional logic everywhere in the code. Imagine then, multiple configuration variables determining multiple behaviors and leading to a jungle of unmaintainable and intricate conditional statements.

The way to go, in the above example, should be:

  1. Create an abstract class or interface representing a Storage object (e.g., IStorage), with save and load methods.
  2. Create different implementations if the abstraction such as DBStorage, FileStorage, CacheStorage, etc.
  3. Create a factory that will instantiate the right storage implementation based on configuration and return it as abstraction (IStorage)
  4. When in need of saving data just write: myStorage.SaveSomething(…) where myStorage is a IStorage variable created through the factory

Sure, it’s a lot of work but still much less work than maintaining conditional jungles. It also has one big advantage that is explained in the following guideline.

Developers that are abusing conditional logic are digging their own graves, and usually end up being overstressed.

3. Use Seams: Extending Is Better Than Changing

A seam is an area of source code where the behavior can be changed without editing the code. Seams leverage flexibility and they are great for one simple reason: They encourage extending the architecture instead of changing it.

Why is extending better than changing? Pretend that you are a new developer and you are asked to add new storage options to an application.

Would you prefer to go through the code and change it with the new options wherever something is saved? Finding your way around the code that someone else has written, that you do not fully understand yet, risking of breaking something or missing one of the hundred places that need to be changed?

Or would you rather create a new class that you will write from scratch that would implement the IStorage interface and be done without even looking at someone else’s code, risk free?

It is not a surprise if the degree of confidence of a developer is higher in this last case.

4. Avoiding Global State and Non-Deterministic Behaviors

COBOL developers in the ‘80s knew already that abusing global variables is a bad, bad practice. Global variables are rarely justified, and their damning side effects can lead to mental insanity. That wise old rule is still extremely valuable also in modern object-oriented languages, such as C#.

One of the many issues is that they create a global state of the application that compromises the deterministic behavior of functions/methods; in other words, calling the same function twice with the same parameters can give completely different results. Hence, the code is fragile, difficult to debug, and extremely difficult to run on multiple threads.

Use of any sort global variables/objects is therefore highly discouraged.

Ideally a good architecture is stateless. Practically speaking though, almost every architecture need some sort of state (e.g. a database, files, and etcetera).

The solution is state isolation and consists in two simple rules:

  1. Keep the life and scope of a state as short as possible (which means, for instance, that even the class members should also be contained).
  2. Isolate the state by wrapping into a separate layer that presents itself through abstractions (base classes or interfaces).

For instance, a database can present data in the form of business entities through a well-defined interface of a Data Access Layer. The isolation will make life much easier when it comes to testing or troubleshooting a problem.

5. Avoid Static Classes for Helpers, Utilities & C.

Developers are frequently tempted to create static classes, especially when it comes to helpers, utilities and so on. The reason why a developer resorts to a static class is not much different from the reason a COBOL developer would use a global variable in his code: it gives immediate access to data from anywhere in the code, no effort needed.

Unfortunately, static classes act frequently as a global state and create the non-determinism that should be avoided. But it gets worse. Since static classes can be used everywhere in the code without being passed explicitly as parameters, they create secret dependencies that are not revealed by the API documentation. The code behavior then becomes less and less declarative and both the clarity and maintainability of the code drops dramatically. Static classes come with global variables and dependencies creating tight coupling in all the consumers (coupling is transitive).

Last, but not least, code using static classes is not testable in isolation, making unit testing a nightmare.

Unless strictly needed for performance reasons, the use of static classes should be avoided. Static variables are still OK for constant objects (although a static property without setter would be better) or to hold private references to objects inside factory classes.

6. Separate Creational Logic from Business Logic

One of main principles of a good architectural design is that the logic of creating an object and the business logic (what the object really does) are two different concerns that should be kept as far apart as possible.

The creation of objects is a concern that belongs to specialized classes, such us factories or builders. They only and exclusively should have the monopoly of creating objects.

The objects, on the other side, should only be concerned about performing business logic (ideally, a single business concern only) and not to worry about creating other objects (dependencies).

For instance, an object called HTMLAnalyzer (designed to analyze HTML links) needs a LinkAnalyzer in order to work. That means it depends upon the LinkAnalyzer class and therefore, an abstraction of LinkAnalyzer should be explicitly passed as parameter in the constructor of HTMLAnalyzer or as parameter of the methods that are using it. A developer may instead think of creating the LinkAnalyzer inside the HTMLAnalyzer using the new statement.

Double mistake:

  • First of all, the new statement creates a dependency to a specific type (no abstraction); therefore, the behavior is carved in stone and impossible to change without major refactoring.
  • To test HTMLAnalyzer in isolation is now impossible because we will have to test LinkAnalyzer at the same time.

The correct way is to use dependency injection to explicitly and declaratively inject all the needed dependencies when and where they are needed, for instance, in the constructor.

In this case, the object that uses a HTMLAnalyzer will invoke a factory to obtain an abstraction (interface or base class) of a LinkAnalyzer object and inject it into the HTMLAnalyzer constructor.

At any time, the behavior of LinkAnalyzer can be changed by creating alternative implementations and the only change required will be in the factory, not in the actual code of the business logic where changes are expensive and dangerous.

One simple implication of this is that the Singleton design pattern is intrinsically wrong and should never be used. It mixes in itself the creational logic (it creates itself) and the business logic, not to mention that it holds itself as static global object that it will never make it to the garbage collector (a singleton, like love, is forever) and constitutes a nasty global state of which we already spoke.

Factories are the long, simple, easy to control and boring pieces of code where all the changes are simple.

Business objects contain all the magic tricks and complexity, so the less we change here, the less we break.

Keeping them separated will lead to less business logic modification and more business logic extension.

7. Keep Constructors Out Of Business

As a corollary of the previous point, business logic should never be coded into constructors of an object. The purpose of constructors is only to assign some properties, initialize variables and eventually hook up events. If constructors are actively doing something business-related, then, we will never be able to separate the creational logic from the business, and the architecture will always be messy.

8. Law of Demeter

This principle is mostly unknown among developers and, of course, one of the most useful. Without going into formal definitions, the principle states that a class should depend only and strictly upon what it is using; therefore, only what is really used should be injected into a class.

A great explanation that I have found in a Google TechTalk (http://www.youtube.com/watch?v=RlfLCWKxHJ0) is the following:

In an eCommerce system, each user has a Wallet object containing a collection of CreditCard objects and other financial information. The class responsible for the online payment has a method like this:

bool Pay(Wallet wallet, string ccNumber, double amount) {
                CreditCard cCard = wallet.GetCreditCards().GetCard(ccNumber);
                return ProcessPayment(cCard, amount);
}

How would you consider a payment method like this? When you are paying for your new shirt at Macy’s, would you give your entire wallet full of credit cards and cash to the cashier and let him/her pick the right Credit Card or bills from it? Of course not. So if a payment class needs only a CreditCard object, a CreditCard shall be given--nothing more, nothing less. No need to create a dependency by passing a huge container object (the Wallet) full of unnecessary objects that creates tight coupling and security risks.

Container objects (usually named with vague suffixes like Container, Context, Service or ServiceLocator, Portal, Environment, etc.) should not be passed as parameters of constructors or methods (unless the whole content is needed).

9. Keep Complexity Level Low

The innermost level of nested loops, switches and ifs is the measure of how complex is a method. For instance, if inside a foreach loop inside another foreach loop is scored as a complexity of 3. The complexity of a method is the maximum complexity of its code and should never be above 4. If it is more than 4, then it is refactoring time!

10. No Long Methods

As a rule of thumb, every method should fit in a screen without vertical scrolling. If it does not, it is time to refactor. Refactoring long methods will make a developer very appreciative about the absence of global variables.

References

Martin Fowler
Refactoring - Improving the Design of Existing Code
http://martinfowler.com/books/refactoring.html

Misko Hevery
Clean Code - Google Tech Talks
http://www.youtube.com/watch?v=RlfLCWKxHJ0

Robert C. Martin
Clean Code: A Handbook of Agile Software Craftsmanship
http://www.amazon.com/Clean-Code-Handbook-Software-Craftsmanship/dp/0132350882

Dominic Betts, Grigori Melnik, Fernando Simonazzi, Mani Subramanian
Dependency Injection with Unity
http://www.microsoft.com/en-us/download/details.aspx?id=39944

Chad Parry
Do-It-Yourself Dependency Injection
http://blacksheep.parry.org/wp-content/uploads/2010/03/DIY-DI.pdf

Kent Beck
Test Driven Development: By Example
http://www.barnesandnoble.com/w/test-driven-development-kent-beck/1111349772?ean=9780321146533

Erich Gamma, Richard Helm, Richard Helm, John Vlissides
Design Patterns: Elements of Reusable Object-Oriented Software
http://www.amazon.com/Design-Patterns-Elements-Reusable-Object-Oriented/dp/0201633612

License

This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)

Share

About the Author

Giovanni Scerra
Technical Lead
United States United States
Technical Director at AFS Technologies, New York, NY
 
More info about my skills, projects, presentations, etc.:
http://www.linkedin.com/pub/giovanni-scerra/10/940/348
Follow on   LinkedIn

Comments and Discussions

 
GeneralMy vote of 5 Pinmemberjamicore16-Jul-14 12:03 
GeneralMy vote of 5 PinprofessionalPrasad Khandekar3-Jul-14 20:58 
GeneralCtrl+D! Pinprofessionalcsakii1-Jul-14 23:33 
I'm a student programmer, and my comment's title says it all. Smile | :) Thanks!
GeneralMy vote of 1 [modified] PinmemberSteve Conlan1-Jul-14 4:08 
GeneralRe: My vote of 1 [modified] PinmemberGiovanni Scerra1-Jul-14 9:27 
GeneralRe: My vote of 1 PinmemberSteve Conlan2-Jul-14 19:19 
GeneralRe: My vote of 1 [modified] PinmemberGiovanni Scerra3-Jul-14 0:53 
QuestionStatic methods Pinmemberirneb1-Jul-14 4:01 
AnswerRe: Static methods [modified] PinmemberGiovanni Scerra1-Jul-14 9:22 
GeneralMy vote of 5 PinprofessionalRenju Vinod16-Jun-14 0:40 
QuestionUse of static ... Pinmember_Noctis_5-Jun-14 22:53 
AnswerRe: Use of static ... PinmemberGiovanni Scerra7-Jun-14 3:43 
GeneralRe: Use of static ... Pinmember_Noctis_7-Jun-14 3:47 
Question5 star indeed PinmemberJ. Wijaya3-Jun-14 0:42 
Question5 starts ! Question about Avoiding Global State and Non-Deterministic Behaviors? PinmemberAnthony Morera1-Jun-14 16:31 
AnswerRe: 5 starts ! Question about Avoiding Global State and Non-Deterministic Behaviors? PinmemberGiovanni Scerra2-Jun-14 1:37 
GeneralRe: 5 starts ! Question about Avoiding Global State and Non-Deterministic Behaviors? PinmemberAnthony Morera3-Jun-14 4:07 
GeneralStatic classes are testable in isolation Pinmembermarkmnl20-May-14 19:31 
GeneralRe: Static classes are testable in isolation PinmemberGiovanni Scerra21-May-14 2:00 
GeneralGood article PinprofessionalJagbir Saini16-May-14 5:47 
QuestionVery Nice-A vote of 5. PinprofessionalRahul Dhammy13-May-14 20:41 
GeneralMy vote of 5 PinprofessionalPrasad Khandekar13-May-14 1:38 
GeneralExcellent Article PinprofessionalSumanth Madireddy11-May-14 17:12 
GeneralMy vote of 5 PinmemberTạ Bá Trung9-May-14 17:53 
Questionmissing one rule Pinmemberyalenap9-May-14 12:02 

General General    News News    Suggestion Suggestion    Question Question    Bug Bug    Answer Answer    Joke Joke    Rant Rant    Admin Admin   

Use Ctrl+Left/Right to switch messages, Ctrl+Up/Down to switch threads, Ctrl+Shift+Left/Right to switch pages.

| Advertise | Privacy | Mobile
Web03 | 2.8.141022.2 | Last Updated 8 May 2014
Article Copyright 2014 by Giovanni Scerra
Everything else Copyright © CodeProject, 1999-2014
Terms of Service
Layout: fixed | fluid