R.S. Riley's Software Sense

  • Increase font size
  • Default font size
  • Decrease font size
Home Articles Design & Development Heuristics, Refactoring and Maintainable Software Part I

Heuristics, Refactoring and Maintainable Software Part I

E-mail Print PDF

computer-key-smallAlmost all developers are familiar with design patterns, however many are unaware some of their less formal brethren known as ‘design heuristics’. Design heuristics are ‘rules of thumb’ that are used when implementing and designing software and can serve as a natural complement to patterns. Design heuristics often take into account fuzzy factors such as maintainability which is often underplayed or outright ignored by the patterns camp.

It is important to consider developer-related factors when designing software and this is one area where heuristics can help. They implicitly take into account questions such as “How easy will this be to reuse?”, “How easy will this be for the team to maintain and fix when bugs arise?”, “How much overhead will this introduce in the long run?”, or even “How easy is this to understand, picture in your head, and explain to others?”.

I’d like to acknowledge much of the inspiration and some of the specifics of this article have come from the spectacular book 'Object-Oriented Design Heuristics' by Arthur J. Riel. It’s an older book but I have found it to be an invaluable addition to my library. I also refer to some concepts initially put to paper by Martin Fowler in his classic book 'Refactoring: Improving the Design of Existing Code'. Finally, I recommend that you check out the article The Maintainable Software Mindset which serves as a complement to this article.

Class Design

Heuristic: Classes Should Maintain a Single, Clear Focus

When designing a class, ensure that it has a clearly defined, single focus. The reason for a class’ existence should be easily understood and maintained in such a way so that the implementation doesn’t stray from this original focus. Additionally, the class should fulfill one role and not contain a hodge-podge of methods belonging to numerous functional area of the system.

Too often a class that was intended to perform one job gets a bit of extra functionality tacked onto it that really doesn’t align with the original intent. After this first mini-kludge, there tends to be a bit more tacked on as subsequent individuals fail to properly understand where the class fits into the architecture because class purpose had already been muddled.

The less focused a class is, the harder it is to reuse and test. If you get enough classes trying to do too much, the overall architecture becomes cluttered and confused. Additionally, unfocused classes often end up becoming massive, god classes – classes that are thousands of lines long which drive simpler classes that contain very little logic themselves. The presence of such a structure indicates a movement from an objected-oriented design to a system more procedural in nature.

What to Do

If you see a class with a muddle focus, group functionality and employ the Extract Class refactoring, ensuring that each resultant class is clearly structured around a single core concept.

By ensuring clear class focus, not only will the classes be easier to maintain but the overall system architecture will become cleaner.

Heuristic: Adhere to Clear, Consistent Naming

If you name classes, variables, and methods in such a way as to read more like non-technical English, the code tends to be more maintainable and can actually decrease the need for a large number of comments.

Although important, comments are not directly tied to the code so not uncommon for them to become out of sync with the code, thus adding to confusion. Remember that the only thing that is always 100% in sync with the code is the code itself, so it’s highly desirable to make it as self-explanatory as possible.

Heuristic: Maintain Consistent Method Focus and Split up Big Methods

One’s use of methods can either improve or diminish maintainability. Many times methods grow to a size large enough that they become a pain to understand and debug. Additionally, some methods contain a hodge-podge of unrelated logic that’s difficult to wade through. When you see either of these conditions, use the refactoring technique Extract Method.

Typically you can split out methods according to a natural functional grouping of statements or by a statement complexity level. What I mean by complexity level is that within a method you usually have various lower level calls such as if blocks, loops etc, and then you have method calls. It's desirable to have your lower level statements contained in one set of methods and have another set of methods primarily call other methods. This pattern tends to be naturally arrived at with consistent method extraction which improves readability and reusability.

When you read a block of code that contain calls to clearly named methods, the method calls end up describing exactly what logic is being performed with minimal commenting required. Thus proper naming coupled with clean organization of methods is not only aesthetically pleasing, but ends up making class internals more modular, easier to understand and thus more maintainable.

The following example may seem a bit obvious, but the point of it is to point out that that simple techniques can render a system more maintainable when repeated throughout the project. It includes a few different types of statements - both in their inherent complexity and functional purpose:

if(_weNeedToInitialize)
{
// Set the variables associated with Subsystem Alpha
_coolantLevel = InitialCoolantLevel;
_coolantLevelThreshold = CoolantLevelThreshold;

// Initialize very important components
InitializeImportantComponentGamma();
InitializeImportantComponentZeta();
InitializeImportantComponentOmega();

// Finally loop through the thing-a-ma-jigs to start the entire system
for(int tmjCounter = 0; tmjCounter < thingaMaJigs.Count; tmjCounter++)
{
thingamajigs[tmjCounter].Start();
}
}

After extracting various methods by logic purpose we arrive at:

if(_weNeedToInitialize)
{
SetSubsystemAlphaVariables();
InitializeVeryImportantComponents();
StartTheThingAMaJigs();
}

protected void SetSubSystemAlphaVariables()
{
_coolantLevel = InitialCoolantLevel;
_coolantLevelThreshold = CoolantLevelThreshold;
}

protected void InitializeVeryImportantComponents()
{
InitializeImportantComponentGamma();
InitializeImportantComponentZeta();
InitializeImportantComponentOmega();
}

protected StartTheThingAMaJigs()
{
for(int tmjCounter = 0; tmjCounter < thingaMaJigs.Count; tmjCounter++)
{
thingamajigs[tmjCounter].Start();
}
}

Again note that although the initial code state was already quite simple, the example's purpose is to illustrate the idea that by properly grouping logic and extracting methods you increase maintainability and decrease the need for comments. Applying this basic concept over thousands of lines of your own code can make a big difference.

Containment

Heuristic: Have One Way Class Dependence

Users of a class should depend on its interface but the class shouldn’t be dependent on its users. Another way of putting this is to make child classes ignorant of their parents. Following this heuristic promotes reuse and allows for easier testing.

Additionally, adhering to this heuristic throughout the code moves the architecture into a clean layering of logic. You’ll end up with classes at the lowest level of the containment hierarchy being rather simple and only dependent on the most elemental pieces of the system such as direct system library calls. For instance at the lowest level you may have a utility class that provides some very basic services. This class shouldn’t depend on anything other than the system library.

Then there would be classes above the utility class that depend on it and perhaps its peers. Above the utility class may be several other users of the class including classes in the data access layer. The DAL in turn only depends on the utility class, system library and the database itself, not the users of the DAL, this philosophy pervades through the architecture.

By strictly sticking with this design philosophy you end up creating discrete, independent objects and subsystems that can easily be reused and perhaps more importantly, easily debugged and separated from other parts of the system. For reuse, you can simply lift out an entire tree starting with a parent class since you do not have complex entanglements with the rest of the system

Conversely, if you have been coupling child classes to parents, you can’t cleanly reuse or test these classes.

What to Do

In the event that you do need to communicate to the parent class from the contained class, there are a couple techniques allowing you to perform this decoupling. One way is to extract an interface from the parent class, have the parent implement that interface and then pass the interface reference to the child class.

When the child class needs to contact the parent it simply utilizes the reference to the interface. Note that because this isn’t a concrete reference but a reference to an interface, there is no direct coupling from child to parent, which promotes reuse. Additionally, testing is easily achieved by creating test classes that also implement the interface.

So, instead of this:
parent_child_codependence
You get this:
interface_refactored

You can also use an event model such as the one built into .NET. In this model, the child class makes one or more event variables public which can then be registered by the parent. When certain conditions are present, the child triggers the event which automatically causes any methods registered by the parent to get called while allowing the child to be ignorant of the method specifics. This ignorance enables the child to be reused independent of the parent.

parent_child_event

Inheritance

Heuristic: Make Sure Inheritance Relationships Really Represent ‘Is-A’

When implementing inheritance ensure that that all public and protected members of the base class truly pertain to all derived classes. One tell-tale sign of a problem is a derived class nulling out the implementation of a base class member. This is a clear sign that there isn’t a true ‘is-a’ relationship present.

Improperly utilizing inheritance can really start to create a maintenance nightmare. The inheritance hierarchy is supposed to allow for easier reuse and gives an easy explanation with how the program is structured. If you hack the way the hierarchy works you cause a lot of headaches for future developers needing to maintain the code and you also diminish the reusability of the system.

Instead of settling for an inheritance hierarchy that has been shoe-horned to 'work', come up with a better structure. Either pull out the derived class since it isn’t truly a ‘is-a’ relationship or trim back the base classes public/protected interface to make sure that all members truly make sense for derived classes to include. Also, determine if a containment relationship is more appropriate since it is not as strong a form of coupling as inheritance and could be easier to reuse down the road.

It Comes Down to Keeping Code Simple and Focused

The heuristics are all about keeping software clean, simple and easily maintainable. Too often engineers and architects lose focus, employ fancy tricks and utilize the complex patterns they've picked up from various books and magazines. These can be valuable if maintainability is considered, but if they are committed to without first considering future upkeep, overly complex solutions can cause a project to become a maintenance nightmare. So in the end, always strive toward making the code simpler because it will be much easier to debug, easier to enhance, and will generally be much less frustrating to deal with.

Share Link:
Bookmark Google Yahoo MyWeb Del.icio.us Digg Facebook Myspace Reddit Ma.gnolia Technorati Stumble Upon Slashdot Yahoo Bookmarks