Make existing code more cohesive

If you’ve ever worked on long existing complex software – a software which has been maintained by several different software developers– you may notice some very long classes (>2000 LOC) with a great potential for refactoring.

With regard to object orientation a long class may indicate that the single responsibility principle isn’t fulfilled. Single responsibility results through high focused and highly cohesive classes. One class has one responsibility. Those classes usually have a small number of instance variables, whereby all methods and variables form a logical unit. The single responsibility principle shall enable short, readable and very structured classes that are easy to maintain in the future. Apart from very large classes, method signatures with a high number of input parameters indicate dependency between these parameters and the methods and can also signal low cohesion. Furthermore it is simply not recommended as the code is less readable and more difficult to maintain ([1], p.141ff).

But how can you make yourself more aware to low cohesive classes, and what can you do to increase cohesion? I gathered the following guidelines by my daily work especially by observing and listening to experienced programmers. First of all you have to always keep in mind whenever working on code leave the code (you are now working at) cleaner than you found it. This is exactly what my colleagues advise me to do. Therefore always consider small refactorings like extracting code from a large method to a new one. By breaking large code blocks down to small ones, you may notice redundant code. By splitting code you may thereby recognize some code which doesn’t quite fit this class but has potential to form a new class which will be highly cohesive. If you have a method with several parameters ask yourself if these parameters are often shared in this class and if they shape a logical unit? If your answer is yes, you may consider a new class. A new class containing all those instances in order to form that logical unit. So the signatures of those methods will use that new class instead of all those parameters. Keep in mind when working with different developers on classes which are further developed the class will be enlarged through several years. And then different methods may seem to do different things, but on a closer look, some methods have things in common. And you may recognize a pattern. Everybody tries to write readable code, but when creating a new feature no matter how small, you may think, hey this code fits perfectly into this class and then the class just gets longer. And sometimes there will be some methods which will have a little tiny thing in common. It was such a thing that a very experienced developer showed me recently. Four methods which process diverse variables totally different. Here is a simplified example of the ancient code.

public boolean detectCycle(MyProject project, Set<PathEntry> visitedEntries) {
  if (visitedEntries.contains(this)) {
    return false;
  }
  visitedEntries.add(this);
  for (PathEntry entry : getEntries()) {
    if (entry instanceof MyProjectRefEntry) {
      MyProject refProject = (MyProject)((MyProjectRefEntry)entry).getReferencedProject();
      if (project.equals(refProject)) {
          return true;
        }
      if (refProject.getObjectPath().detectCycleInternal(project, visitedEntries)) {
         return true;
      }
    }
  }
  return false;
}

But they had one thing in common. They searched for an element and the search structure was quite similar. Oversimplified we decided to use a specific search Structure whereby each element depending on the called method can be processed differently. For each processing mechanism we created a new specific class.

So basically we extracted the different types to process the elements and extracted the search mechanism to a whole new method. A new class for the search part can be even more appropriate to achieve higher cohesion. The search methods shall be called with the specific Searchclass who will process the elements adequately. Note that on each element processEntry() is called.

private void searchPath(AbstractSearch search) {
  for (PathEntry entry : getEntries()) {
    searchEntry(search, entry);
  }
    //some specific code
}
private void searchEntry(AbstractSearch search, PathEntry entry) {
  if (searchContext.visitAndConsiderContentsOf(entry)) {
    search.processEntry (entry); 
  } 
  //some other code 
}

We used inheritance, therefore the signature in the method searchPath (…) AbstractSearch as input parameter. As the user can call this method with the specific SearchClass the right implementation of the abstract method processEntry() will be called. The example code changed dramatically. Now it looks more like this:

public boolean detectCycle() {
  CycleSearch cycleSearch = new CycleSearch(getMyProject());
  searchPath(cycleSearch);
  return cycleSearch.isCycleDetected();
}

So by composing different methods and extracting similarities we removed redundant code and could make classes more cohesive by just splitting an existing class. Furthermore the code just got easier to read and especially to test by small cohesive classes with co-dependent instance variables.

 

References

[1] Martin, R.C. (2008): Clean Code: A Handbook of Agile Software Craftsmanship, publisher Pearson Education

Read more about (high) cohesion

http://en.wikipedia.org/wiki/Cohesion_%28computer_science%29 (get to know basic knowledge of cohesion)

https://pragprog.com/magazines/2010-12/cohesive-software-design (very interesting article written by very interesting programmers)

Advertisements

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s