Table of Contents
“Requirement changes!!!” This is a very common statement used in software development, but to be honest, this is a fact and must be always present in the minds of software-developers and software-architects. Although we tend to think of software development as an engineering exercise, the analogy breaks down very quickly. When was the last time someone asked the designers of a Grand Hotel to add ten new floors at the bottom, put a pool on the top, and have all of this done before Monday morning?
Doesn't the above paragraph correlate to our own story? I take it for granted; the answer is “Yes”. So we all know the requirements are flexible in nature, despite this, with requirement changes our product doesn't match to remarkable quality. Who is responsible for this? Well, wrong question!!! The answer to this question will differ according to specific role assigned to people and that is a real game. If I consider my own experience, the answer will be something like this:
Developer: “Requirements are varying”. “Estimations are wrong”. “Dependency on other modules”.
Team Lead: “Unplanned tasks have taken more time”. “Status and time-sheets are not filled properly”. “Technology is not familiar to team”.
Quality-Auditor: “Processes are not followed”. “Documents are not in place.”
Well, fair enough!! Nobody should be blamed for this because that is the role assigned to them. But one very basic question when we all know requirements are variable in nature, what preparation did we do? Shouldn't we be flexible enough to accommodate the requirement changes? We will explore more in the following sections.
Although there are no specific mandatory prerequisites to follow the topics in this article, but to grasp the code-snippets enclosed, prior knowledge to any OOP (C++, C# or Java) language is required. Familiarity of design concepts (abstraction, encapsulation, polymorphism and inheritance) is an added advantage.
While this article touches the common problem and solution faced during development, it focuses more on requirement changes. This article also emphasizes the principles which must be taken into consideration while writing each line of code. Alright, enough of beating around the bush, let's dig into the main contents.
In this ever changing world, nothing is immortal, so is software. As time goes by, software also undergoes changes, while changing the software, we often come across different problems. I am sure as a part of software development, we all encounter the following situations:
- Requirement changes
- Dependency on other modules
- Lack of technical knowledge
- Not acceptable to customer
We all have to agree on this... we can't build an empire on an infirm foundation. The same analogy holds well in software development too. While developing the software, we must stick to software-principles. In all the phases of SDLC (software development life cycle), we have to follow the software principles but need to take a special consideration during analysis, design and coding phases. When requirement changes, we need to deliberately analyze the code and make sure basic software principle shouldn't be violated.
- The Single Responsibilities Principle (SRP)
- Open and Close Principle (OCP)
- DRY (Don't repeat yourself)
- Dependency Inversion Principle (DIP)
- Interface Segregation Principles (ISP)
- Liskov Substitution Principle (LSP)
- YAGNI (You aren't gonna need it)
Well, if I count my entire experience of all code reviews, I have never seen a code which doesn't violate this principle. But I can assure you even if 70% of your code follows this principle; I am forced to say your code is 15% complete :). The single responsibility principle of object oriented states:
"A class should have only one and one reason to change”.
In other words, each object should have single responsibility. If more than one responsibility is assigned to a class, then responsibilities are coupled. Here, a responsibility refers to “a reason for change”.
Let's consider this example of SquareOperations
class which has two methods; one to calculate the dimension of square and the other draws the square on the form.
class SquareOperations : IShape
{
#region IShape Members
public Rectangle CalculateDimension(Form frm)
{
Rectangle rect = frm.ClientRectangle;
rect.Height -= 50;
rect.Width = frm.Height;
return rect;
}
public void DrawShape(Rectangle rect, Form frm)
{
if (frm != null)
{
Graphics grph = frm.CreateGraphics();
grph.Clear(Color.Teal);
grph.FillRectangle(new SolidBrush(Color.Tomato), rect);
grph.DrawString("Square", new Font("Arial", 20),
new SolidBrush(Color.Snow), 40, 40);
}
}
#endregion
}
Everything works like champ! If we have to deal only with Square shape
. But consider the two diverse applications which make use of our solution, one application is a geometric-application and another is UI- application. Geometric-application calculates the area of geometric shapes for mathematical purpose whereas UI-application draws the shapes on Form
. Geometric-application doesn't need the Draw
functionality so a UI-application may not require the CalculateDimension
method of our solution. This clearly depicts two responsibilities are assigned to a single class (“SquareOperations
”). The better solution is both the responsibilities are assigned to different classes, one class takes care of computing dimension of different kind of shapes and another class is responsible to draw the shapes on the form.
Open and close principle can be best remembered as “O”-Open for extensions and “C”-Close for modification. Definition of OC principle states:
“Software entities (class, modules, functions etc.) should be open for extensions but close for modification.”
Let’s take an example of UI-application which draws different kind of images based on user-preference. If we take a closer look at the code, we come across scattered switch
-case
statements based on selection. Code snippets for the same go like this:
public void DrawImages(string shapeType, Form frm)
{
IShapes shapes = null;
switch (shapeType)
{
case "SQUARE":
shapes = new Square();
break;
case "RECTANGLE":
shapes = new Rectangle();
break;
case "ELLIPSE":
shapes = new Ellipse();
break;
}
if (shapes != null) shapes.DrawShapes(frm);
}
The fall back in the preceding code-snippets is, later if we decide to add more shapes, existing code will be changed. This clearly violates OCP. Open and close principle adds more level of abstractions and complexity to code but it doesn’t mean we ignore this principle. We must follow open and close principle for the code which is most likely to change. My weightage to this principle is 10%.
Calling this as a design principle will be ill treatment to this principle because it is fundamental to software engineering. The replicated code always leads to more costly affair and maintainability issue and I personally give 15% weightage to this principle. To make sure that this principle is followed, please make use of SIMIAN tool.
Many times, fixing a bug in one module can lead to unexpected behavior in other modules. Generally cause for this problem is violation of Dependency Inversion principle, which states.
“Higher level modules should not depend on the details of the low level modules. Both should depend upon abstractions.”
Consider the normal two-tier layered architecture containing UI-Layer and Database-Layer. If UI-Layer is directly making calls to Database-Layer functions, then any changes to database-layer methods have adverse effect on UI-Layer. The best solution for this kind of problem is lower modules and higher module must interact with each other through interface. If we take a closer look at the DIP and OCP principles, both force implement abstraction. With the help of abstraction, details of the systems get isolated from each other. Since DIP and OCP principles both force for abstraction, my percentage for the implementation of this principle is 10%. One of the closely related terms with DIP is Law of Demeter (only talk to your immediate friends). For further information about Law of Demeter, please check the reference section.
While most of the principles force to implement abstraction and communication between different modules must be through interfaces, ISP principle defines the guidelines for designing interfaces.
The essence of Interface segregation principle is as follows:
“Clients should not be forced to depend upon interfaces that they do not use.”
OR
“The dependency of one class to another one, should depend on the smallest possible interface.”
The basic idea for this principle is if two modules are communicating with each other through interfaces. Interfaces must define minimal criteria for communication. My percentage for this principle is 5%.
Have a look at the following code snippets. It will be a huge pain to implement such an interface.
interface IShape
{
string ImageType {get; }
string ImageText { get; }
string ImagePath { get; set; }
bool IsImageLoadedFromPath { get; set; }
bool IsDoubleBufferingRequired { get; set; }
long ShapeArea { get; }
Rectangle CalculateDimension(Form frm);
void DrawShape(Rectangle rect, Form frm);
void ClearImage(Form frm);
void RedrawImage(Rectangle rect, Form frm);
}
The importance of LSP comes handier whenever we violate this principle. Definition of this principle is very easy to follow.
“In class hierarchies, it should be possible to treat a specialized object as if it were a base class object.”
OR
“If you have base class BASE and subclasses SUB1 and SUB2, the rest of your code should always refer to BASE Not SUB1 or SUB2”.
One of the common and classic examples of this principle is Square
class deriving from Rectangle
. Code snippets for the same follows like this:
class CRectangle
{
public int Width { get; set; }
public int Height { get; set; }
public virtual void SetWidthHeight(int width, int height)
{
Width = width;
Height = height;
}
}
class Square : CRectangle
{
public override void SetWidthHeight(int width, int height)
{
base.SetWidthHeight(width,height);
Height = width;
}
}
public partial class LSP : Form
{
CRectangle shape = new Square();
private void RectangleArea(object sender, System.EventArgs e)
{
shape.SetWidthHeight(8,7);
if((shape.Width * shape.Height)!= 56)
MessageBox.Show( "Violation of LSP.Incorrect Result!!");
}
private void SquareArea(object sender, System.EventArgs e)
{
shape.SetWidthHeight(8, 7);
if ((shape.Width * shape.Height) != 64)
MessageBox.Show("Correct Result!!");
}
}
In other words, this principle basically wants us to think clearly about the expected behavior of a new class before we derive it from other class. Wrong hierarchy of classes can lead to unexpected result as in the mentioned code snippets. My percentage to this principle is 10 %. General problem of LSP can be overcome through use of “Visitor” or “Repository” patterns. LSP is closely related to “Design by contract” methodology, but it is difficult for me to brief the same here and is out of scope of this article.
The intention of this principle is:
“Do the simplest thing that could possibly work.”
During the study of design principles and applying these principles with a conscious mind, many times I got the feeling YAGNI and OCP principles contradict each other. If we follow the OCP principle then, to remove a simple switch case
statement, we have to overdo things (i.e., by applying factory pattern or factory method pattern or abstract factory pattern). Whereas YAGNI states don’t predict the future and write code that is necessary at this moment. After a lot of discussion with my friends, I came to the conclusion that not in all the places YAGNI should be followed whereas it is really a healthy practice to follow OCP where code is more liable to change. YAGNI principle must be followed in the places which are not frequently changed. My personal rating to this principle is 5%. YAGNI principle is very widely used in Agile/ XP team.
Having followed the above course of action, beware of the common threats to a good code quality: violation of coding guidelines, complexity, duplication, performance and so on. Do make use of couple of tools like FXCop, source-monitor (for complexity and depth), SIMIAN (for duplicated code) and Ndepend (for complexity and depth) to examine the quality of code after applying these principles. But I would say a conscious mind is the best tool for a good coding style.
Last but never the least, I give major weightage of 30% to architecture discussions and reviews from your mentors/peers during design and coding phase. More importantly, always keep yourself open to reviews from your mentors/peers. Well if I sum-up the weightage of all the percentages given to principles (SRP- 15%, OCP-10%, DIP-10%,DRY-15%, ISP-5%, LSP-10, YAGNI-5%) = 70%, and include reviews and discussions, my code is 100% complete :).
- http://www.microsoft.com
- http://www.oodesign.com
- http://en.wikipedia.org/wiki/Open/closed_principle
- http://c2.com/cgi/wiki?LiskovSubstitutionPrinciple
- http://ifacethoughts.net/2006/03/07/the-law-of-demeter-and-object-oriented-programming/
- https://lostechies.com/chadmyers/2008/03/12/ptom-the-liskov-substitution-principle/
- http://www.agilemodeling.com/essays/changeManagement.htm
- http://www.eventhelix.com/RealtimeMantra/Object_Oriented/
- http://www.ndepend.com/
- https://www.harukizaemon.com/simian/
- http://www.campwoodsw.com/sourcemonitor.html
This member has not yet provided a Biography. Assume it's interesting and varied, and probably something to do with programming.