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.
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.
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.
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.”
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.
Ultimate and enlightening ....keep it up...
ReplyDelete