Monday, November 8, 2010

Designing For Change - Part 1 of 3 (Code Qualities)

Software Development is a vast field which has evolved enormously in last few decades in terms of complexity of software and its fields of application. As the business needs of users is changing and becoming more and more complex with time so is the complexity of software. Hence a great deal of emphasis is being paid now to engineering processes that are followed by product development teams throughout the product cycle. Another matter of fact closely related to this is that requirements given by customers(users) keeps on changing and with the fact, that a software product is shipped in incremental-versions built on the top of previous version, one of the challenges that software developers face is - How to design for changing requirements?

The Problem of Requirements

If we ask experienced software developers, with certain level of expertise, what they know to be true about requirements they get from customers. The most frequent answers we will get to hear are:
  • Requirements are incomplete and usually wrong.
  • Requirements and users are often misleading.
  • Requirements do not tell the complete story.
As Alan Shalloway mentions in his book 'Design Pattern Explained', one of the things that we will never get to hear is "Not only were our requirements complete, clear and understandable, but they laid out all of the functionality we were going to need for the next five years!"

So the bottom line is that requirements always change and for very simple set of reasons:
  • As users interact and discuss with developers and see new possibilities for the software, their view of their requirements change.
  • As the developers become more familiar with the software, their understanding or user's problem domain changes.
  • The environment in which the software is being developed changes. Fifteen years back the trend was towards developing desktop applications but with advent of internet (and its associated benefits) the shift is now toward web based application to the extent of trying to get an online operating system.
With that said, it does not means that we can give up on collecting good and relevant requirements. Rather than complaining about changing requirements, we should change our development process to address it more effectively. We can design our code in such a way that impact of changing requirement is minimum. The code can evolve and new features can be bolted on while incurring least possible TCO (total cost of operation).

The first step towards a good design is coding practices that ensure high code qualities.

Code Qualities & Coding Practices

First Principles of Coding Rules
As a developer, following factors should be kept in mind while coding down the software:
  • Objects must be testable.
  • There must be no redundancy.
  • Objects should be understandable.
  • Objects should not be excessively entangled with each other.
All of these principles maps to particular code quality.

Cohesion
Cohesion refers to the fact that how closely methods in a class or operations within a method are related to each other. A very good definition for Cohesion has been given by Steve McConnell, in his book 'Code Complete: A Practical Handbook of Software Construction' (Redmond: Microsoft Press, 1993, p. 81), as follows:

"Cohesion refers to how 'closely the operations in a routine are related.' I have heard other people refer to cohesion as clarity because the more that operations are related in a routine (or a class), the easier it is to understand the code and what it is intended to do."

Weakly cohesive classes and methods are those that do many unrelated tasks. The code often appears to be a confused mass.

Strong cohesion is related to clarity and understanding. A strongly cohesive class is one that has single responsibility and everything in it is about fulfilling it. Commonality-Variability analysis can help in determining this. And a strongly cohesive method is about fulfilling one functional aspect of that responsibility. Programming by intention can help us to code with better method cohesion.

I will briefly describe what Commonality-Variability analysis and Programming by intention mean. These topics need separate blog to do justice with them. I will be writing about them in a separate blog. In a nutshell, Commonality-Variability analysis refers to process where we have a set of given entities. We examine each of the entities and try to find out what is common among them. There are different methods for figuring out commonalities between a given set of entities. These commonalities become our abstract class and interfaces. Once we are done with finding commonalities, we look for variations within these commonalities. These variation then become the concrete implementations of commonalities it belongs to.

The term Programming by intention was coined by the XP (Xtreme Programming) group. The idea behind it similar to what is called top-down programming. The best way to understand Programming by intention would be to work through an example.

public void PrintReport (string custID) { //..... }

Say I want to code up a routine to print report for all employees who handled a given customer. As I start, first thing I will need to do to is to get all the employees from the database. Now, what I do is, I pretend that I already have a method called 'GetEmployees()'.

public void PrintReport (string custID)
{
Employee[] emps = GetEmployees(custID);
//....

}

Note that this method does not really exist, so in effect what I am doing here is making a prediction as to how I would like that method to work. Next, I am thinking what parameters I would pass it. Well, it should be cutomerID and it will return an array of employee. The point to be noted here is that I cannot possibly be influenced by the way 'GetEmployee()' is implemented because actually it is not.

So the message is, what we need is influence in the right direction when figuring out the interface of a class or method. Normally, what we tend to do is, code up the routine first, and then think what kind of interface could it have. Usually this produces interface that are not very stable as implementation changes over time. However if we think of the interface from the perspective of purely how it will be used, that is a more stable perspective as use does not change as much as implementation does.

public void printReport (string custID)
{
Employee[] emps = GetEmployees(custID);
If(SortReq(emps)) SortEmployees(emps);
PrintHeader(custID);

PrintFormattedEmployees(emps);

PrintFooter(custID);

Paginate();

}

So proceeding this way, we pretend that methods 'SortEmployee(...)', 'PrintHeader(...)' etc already exist. Now all these methods that I pretended to be present will off course be private methods on this object. That way I have made the object easy to use by breaking it into nice cookies of small pieces. We call the bigger method here as 'sergeant' method, which basically calls other private methods and marshaling them. It may have bits and pieces of code here and there. Each of these private methods is about one particular thing. This promotes cohesion and makes sergeant method read like comments. Just one pass through the method will tell both the logical flow of method and what it is trying to do. And note that there is no extra work involved here. We are writing same amount code (that we would have written otherwise) but we have higher degree of cohesion here.

Coupling
Steve McConnell defines Coupling, in his book 'Code Complete: A Practical Handbook of Software Construction' (Redmond: Microsoft Press, 1993, p. 81), as follows:

"Coupling refers to 'the strength of a connection between two routines [or classes]. Coupling is a complement to cohesion. Cohesion describes how strongly the internal contents of a routine [or classes] are related to each other. Coupling describes how strongly a routine [or class] is related to other routines [or classes]. The goal is to create routines [and classes] with internal integrity (strong cohesion) and small, direct, visible, and flexible relations to other routines (loose coupling).'

We can broadly classify Coupling into four types:
  • Identity: One type coupled to the fact that another type exists.
  • Representational: One type coupled to the interface of another.
  • Inheritance: Subtypes are coupled to their super class, in that any change in the super class will propagate downward.
  • Subclass: One type coupled to the fact that there are subclasses to other type (polymorphic), and what specific subclasses exist.

No Redundancy
No Redundancy basically means "One rule in one place" i.e. a piece of code doing one particular thing should occur once and only once throughout the product code. Redundancy is not just redundant functions or redundant state. It can also be redundant relationship between classes/interfaces, design etc.

Anything that is redundant will cause maintenance problems and wastes developer's time and effort during bug fixing as any fix will be required to be repeated as many times as the code is duplicated.

Avoiding Redundancy
  • “Copy and paste” is a common source of redundancy.
  • Before doing copy and paste always ask yourself - What is the code I want to reuse? Can I make a new method that can be used by both the current method and the new place where I need this? If two entities need this, how likely will there be a third, fourth, etc… May be I need a service anyway.
  • However, “Cut and paste” is okay :)

Testability
Testability of an object refers to fact how easily and cleanly an object can be tested. Testability of an object is very critical as it directly determines the ability of test code to test the product and hence it affect the overall quality of final product. Testability directly depends on Cohesion, Coupling and Redundancy. Testing is hard and expensive when code is:
  • Tightly Coupled – “I cannot test this without instantiating half the system.”
  • Weakly Cohesive – “This class does so many things, the test will be enormous and complex!”
  • Redundant – “I will have to test this in multiple places to ensure it works everywhere.”
Unit testing is a good thing. As software developers, whether we agree or not, while writing down the product code we should always ask ourselves “If I were to test this, how would I do it?” If we find the design would be very hard to test, ask ourselves “Why isn’t this more testable?” Thinking this way is very powerful and produces results.

Standards for Good Code
Summarizing what we discussed till now, standards of good code in order of importance is:
  • Be testable.
  • Contains no redundancy (once and only once)
    • Deal with each bug, change or extension once
    • Need to look fewer places to figure out an issue
  • Loose coupling.
    • No unexpected side effects to search for
    • Program flow is more logical, predictable
  • Strong cohesion
    • Cohesive classes and methods do one thing
    • Finding bugs is easier
  • Clarity, Confidence, Maintainability.
  • Make changes with greater confidence.

Coming Up Next
In part2 i will discussing in detail 'Approaches to design' and how does code qualities we discussed here tie up with them.


Click here to go to part2.

1 comment: