Introduction
It has been a long period since I have been developing software for different clients. I always try my best to write good software that fulfills all the customer requirements
and most importantly it satisfies me that I delivered a good work to the customer. I always try to define what good software means and how I
can deliver good work every time.
But the challenges and customer requirements are different for each client. The expectation level also varies
from customer to customer.
It is never easy to figure out where to start a project and how to deliver software that satisfies the
customer, developer, and architect. We have studied a lot about
good software and programming, but while programming, most developers and architects ignore the rules and
that puts them in trouble.
In this article we will study about what is good software and how we can apply Object Oriented Analysis and Design rules to write good software. We have studied
about encapsulation, delegation, decoupling, and other object oriented practices, but how would we use
them in actual programming?
What is good software?
It is a vague term to ask about it, because different people have different definitions and meanings for it.
Customer says good software does what the customer wants and if he uses it in
a new way, it should still work and will not crash and give unexpected results.
Mostly customers do not worry about the architecture and other best programming practices for developing
the software. Instead he is more conscious about the result
of the software that it is supposed to give. Crashing applications and weird results irritate customers and
affects his confidence on programmers.
Object oriented programmer says good software is code written in
an object oriented way, it does not duplicate code in it. Each object
is responsible for handling its own behavior. Apply proper object oriented rules on your application to make it solid and easily extendable.
Your structure should be more flexible for handling new changes.
Architect says good software is software in which you use proper tiers and design patterns. Your software has reusable components in it,
because it is not better to solve the same problem over and over again. Your application is open for extension but closed for modifications. The components should be loosely coupled.
Quality assurance team says no software is good software because if they think software is good then how can they find bugs in it? It was
a funny
and interesting answer given to me by my colleague. But the thoughts of the customer and testing person are almost
the same. Good software gives expected results
and does not crash, and gives proper result if they want to use it in different ways. And most importantly it fulfills all the customer requirements.
The thoughts of others like project managers, MD, CM, and CEO etc., are almost
the same and those are already being covered in the above definitions.
We have heard about good software from different people. How can we satisfy all these people when we deliver software? The answer is not easy at this stage,
but you will learn in this article about good software and it will increase your brain power to deliver good software.
Mobile shop project
One of my friends has a mobile shop and he sells new and used mobile phones. He was managing his inventory manually on papers. One day he decided to throw out his paper
based inventory system and wanted to use an automated system for his shop to manage his inventory and search functionality to help customers to select
their dream mobile phone.
He was moving to a new system because he wanted to increase his sales and manage his inventory. He gave the project to
a renowned firm to build the automated system.
After a few months the programming firm gave him a new computerized system that they built for him. Let’s see how the firm designed the new system for
the mobile shop.
New application for mobile shop
The programming firm completely replaced the handwritten notes into a new system to search
for a mobile for customers. They gave this UML class diagram to show what they did.
They created two classes: Mobile
that contains information of the device,
and Inventory
that contains all the devices and
methods for add, search, and get mobile.
Let’s have a look at the actual code written by the programming firm.
Mobile code
The Mobile
class as it is depicted in the UML diagram contains all the properties for the mobile device and it has
a constructor which takes all the data while
creating the object and it only exposes the getter method of the property.
public class Mobile
{
private string _iemi;
private double _price;
private string _brand;
private string _model;
private string _color;
public Mobile(string ieme, double price, string brand, string model, string color)
{
_iemi = ieme;
_price = price;
_brand = brand;
_model = model;
_color = color;
}
public string IEMI{ get{ return _iemi; } }
public double Price{ get{ return _price; } }
public string Brand{ get{ return _brand; } }
public string Model{ get{ return _model; } }
public string Color{ get { return _color; } }
}
Inventory code
The Inventory
class contains the list of all the mobiles and exposes methods to add mobile, get mobile, and search mobile.
public class Inventory
{
List<mobile> _mobiles = new List<mobile>();
public void AddMobile(string ieme, double price, string brand, string model, string color)
{
var mobile = new Mobile(ieme, price, brand, model, color);
_mobiles.Add(mobile);
}
public Mobile GetMobile(string ieme)
{
foreach (var mobile in _mobiles)
{
if (mobile.IEMI == ieme)
{
return mobile;
}
}
return null;
}
public Mobile SearchMobile(Mobile mobile)
{
foreach (var m in _mobiles)
{
if (!string.IsNullOrEmpty(mobile.Brand) && mobile.Brand != m.Brand)
continue;
if (!string.IsNullOrEmpty(mobile.Model) && mobile.Model != m.Model)
continue;
if (!string.IsNullOrEmpty(mobile.Color) && mobile.Color != m.Color)
continue;
return m;
}
return null;
}
}
The SearchMobile
method that will be used in our further discussion later in this article is the most important method for the mobile shop because
the customer app will use it to find mobiles for customers. Customer can perform search on
brand, model, and color. This method contains the logic for comparing
first suitable device for the search.
Software is having search issue
When the mobile shop started using the new computerized system for searching for mobiles, sometimes
the search system did not return any results but the shopkeeper knew he
had devices in his stock. So he started losing his customers because of the weird results of the system.
The software delivered by the programming firm could not satisfy the customer and he comes back to them with complaints. So the software delivered by the programming firm is not a good software.
How can we deliver good software every time?
The question arises in the mind how can we write good software every time. Is there any set of rules by following
which we can write good software?
Read the definition of the customer, programmer, and architect again and try to define
a set of rules for good software. A good software must satisfy the customer,
it must have object oriented programming prionciples applied on it, and it should be loosely coupled. We can break the definition of good software in three steps.
Keeping the view of different people in our mind we figure out these three main steps to write a good software.
Now we will apply these three on the mobile shop project to write good software.
Software does what customer wants
The project delivered by the programming firm does not satisfy the customer. They sit together and start finding the reasons of the broken search. When they analyzed
and test the application by writing a test tool they come to know that the issue is because of the letter casing. The search module matches the product
in a case sensitive way.
E.g., Mobile shop has “Nokia” in his inventory and customer is searching device using
the string “nokia”.
They start a discussion on the different solutions. One developer says just fixing the issue and delivering it again and
not to worry about the design yet.
Another says we cannot deliver the project that causes some other problems again, so we must be smart enough to fix the issue with
a better design.
They all agreed on the “Don’t create a problem to solve problem”. It is the smart and right way to do and handle things. They finalized the following solution for the problem.
- Every string comparison should use
ToLower
to avoid the problem
the next time. - Use a constant or enum for a fixed range of values to avoid misspellings and case issues.
First improvement in the software
The first improvement they did in the software to avoid the string comparison issue
was adding ToLower()
in the compare statement and using the enum for the color.
The new Search method contains string comparison in lower case and also uses enums where required.
public Mobile SearchMobile(Mobile mobile)
{
foreach (var m in _mobiles)
{
if (!string.IsNullOrEmpty(mobile.Brand) && mobile.Brand.ToLower() != m.Brand.ToLower())
continue;
if (!string.IsNullOrEmpty(mobile.Model) && mobile.Model.ToLower() != m.Model.ToLower())
continue;
if (mobile.Color != MobileColor.None && mobile.Color != m.Color)
continue;
return m;
}
return null;
}
The new search is solid and now it is working at it is supposed to. Customer is happy now, so at this stage we have completed our first task "Software does
what the customer wants". After a few days the mobile shop thought, the application should return all the mobiles that match the search criteria. With this new enhancement
he can increase his sale and give the customer the choice to pick a suitable device for him
from multiple ones.
He comes up with a new requirement for his application. The search tool must return all mobiles that match the search criteria. The programming firm easily puts that logic in it.
public List<mobile> SearchMobile(Mobile mobile)
{
List<mobile> toReturn = new List<mobile>();
foreach (var m in _mobiles)
{
if (!string.IsNullOrEmpty(mobile.Brand) && mobile.Brand.ToLower() != m.Brand.ToLower())
continue;
if (!string.IsNullOrEmpty(mobile.Model) && mobile.Model.ToLower() != m.Model.ToLower())
continue;
if (mobile.Color != MobileColor.None && mobile.Color != m.Color)
continue;
toReturn.Add(m);
}
return toReturn;
}
The programming firm delivers the software with very small effort and that makes
the customer very happy, because it is behaving exactly the way he wants it to.
It increases sales and he is satisfied with the software now.
Now the software is behaving as it is supposed to. So our first task is achieved.
Try to apply some Object Oriented Principles
The customer is happy and the software is doing what it is supposed to. Now let’s try to apply some OO principles to add some flexibility to handle new changes.
When the programming firm starts looking, they notice that the search is taking Mobile
as a parameter, but it does not use IEMI
and
Price
for filtering records. That means there is something wrong with
the design and we have to rethink about it for improvements.
They come up with the new solution. The mobile object is not doing what its name suggests. And it is also not representing
a single concept. Let’s try to remove
extra properties that do not directly belong to mobile. We create a new class MobileSpec
and add all those properties in it that are related to
the specification.
The new UML of the system is given below.
After applying some basic object oriented concepts the code is much clear now, it looks a bit flexible. The new Mobile
class is now this:
public class Mobile
{
private string _iemi;
private double _price;
private MobileSpec _mobileSpec;
public Mobile(string ieme, double price, string brand, string model, MobileColor color)
{
_iemi = ieme;
_price = price;
_mobileSpec = new MobileSpec( brand, model, color);
}
public string IEMI{ get{ return _iemi; } }
public double Price{ get{ return _price; } }
public MobileSpec Spec{ get{ return _mobileSpec; } }
}
The search is also much cleaner now.
public List<mobile> SearchMobile(MobileSpec mobileSpec)
{
List<mobile> toReturn = new List<mobile>();
foreach (var m in _mobiles)
{
var specs = m.Spec;
if (!string.IsNullOrEmpty(mobileSpec.Brand) &&
mobileSpec.Brand.ToLower() != specs.Brand.ToLower())
continue;
if (!string.IsNullOrEmpty(mobileSpec.Model) &&
mobileSpec.Model.ToLower() != specs.Model.ToLower())
continue;
if (mobileSpec.Color != MobileColor.None && mobileSpec.Color != specs.Color)
continue;
toReturn.Add(m);
}
return toReturn;
}
Now just compare the new code with the old one. You will notice that the after applying some basic OOP rules the new code is well designed and more flexible.
Reusable and loosely coupled components
After a few months the customer comes back to the programming firm and asks him to add some extra fields for the mobile. Because now there are varieties
of smart phones in the market, they have internal memory, screen resolution, and operating systems.
The programming firm takes it up for an amendment and starts thinking of adding these new fields in the MobileSpec
file. But certainly they notice that the change
is required
in three classes in different areas.
- Add fields in the MobileSpec file.
- Add parameters on the Constructor of the
Mobile
class and also pass these parameters to
MobileSpec
in the code. - In the Search method of the
Inventory
class add new fields for compression. And change the
Add
method of the Inventory
class.
Is it a good design?
Good software has reusable components and it is open for extension and closed for modifications. But the existing structure of the Mobile project does not have both features
in it. It is means whenever we add a new specification in the MobileSpec
class, we will need to change all other classes again. It is a mess.
How can we make it loosely coupled? Each class should do its duty by itself and the other classes should not do work on behalf of any class.
Observe the code of the Search method in the Inventory
class. It is comparing the
MobileSpec
object in it. That means whenever we change the MobileSpec
class,
we will need to change the Search method. So our design is tightly coupled.
The first thing we do, they remove the MobileSpecs
comparison from the Inventory
class and write
a new method Equal
in the MobileSpec
class and put all the comparison logic in the method.
public class MobileSpec
{
public bool IsEqual(MobileSpec mobileSpec)
{
if (!string.IsNullOrEmpty(mobileSpec.Brand) && mobileSpec.Brand.ToLower() != Brand.ToLower())
return false;
if (!string.IsNullOrEmpty(mobileSpec.Model) && mobileSpec.Model.ToLower() != Model.ToLower())
return false;
if (mobileSpec.Color != MobileColor.None && mobileSpec.Color != Color)
return false;
return true;
}
}
The Search method in the Inventory
class is much cleaner and it is not doing any extra work that is the responsibility of the MobileSpec
class.
Changes in MobileSpec
will not affect the Search method of the Inventory
class.
public class Inventory
{
.
.
.
public List<mobile> SearchMobile(MobileSpec mobileSpec)
{
List<mobile> toReturn = new List<mobile>();
foreach (var m in _mobiles)
{
if (m.Spec.IsEqual(mobileSpec))
{
toReturn.Add(m);
}
}
return toReturn;
}
}
Now they think there is no need to take a separate parameter for the MobileSpec
class in the constructor of
the Mobile
class. Simply take MobileSpec
as a parameter to remove the dependency and to make the design as loosely coupled
as possible.
public class Mobile
{
private string _iemi;
private double _price;
private MobileSpec _mobileSpec;
public Mobile(string ieme, double price,MobileSpec spec)
{
_iemi = ieme;
_price = price;
_mobileSpec = spec;
}
.
.
.
.
}
With this change the AddMobile
method of the Inventory
class will also change. Also pass MobileSpec
as a parameter here.
public void AddMobile(string ieme, double price,MobileSpec spec)
{
var mobile = new Mobile(ieme, price, spec);
_mobiles.Add(mobile);
}
Now if you look into the solution, the design is flexible and reusable. You can use
the MobileSpec
class independently and it has encapsulated
all its properties and methods. The design has a loosely coupled component and changes in
the Mobile
class will not affect the other classes.
Now let’s add new fields in the MobileSpec
class. You will notice that there will be no change required for other classes. You just need to add new fields,
properties, and comparison statements in the IsEqual
method.
public class MobileSpec
{
private string _brand;
private string _model;
private MobileColor _color;
private int? _internalMemory;
private string _operatingSystem;
public MobileSpec(string brand, string model, MobileColor color)
: this(brand, model, color, null, "")
{
}
public MobileSpec(string brand, string model, MobileColor color,
int? internalMemory, string operatingSystem)
{
_brand = brand;
_model = model;
_color = color;
_internalMemory = internalMemory;
_operatingSystem = operatingSystem;
}
public string Brand{get{ return _brand;}}
public string Model{get{ return _model;}}
public MobileColor Color{get{ return _color;}}
public int? InternalMemory{get{ return _internalMemory;}}
public string OperatingSystem{get{ return _operatingSystem;}}
public bool IsEqual(MobileSpec mobileSpec)
{
if (!string.IsNullOrEmpty(mobileSpec.Brand) && mobileSpec.Brand.ToLower() != Brand.ToLower())
return false;
if (!string.IsNullOrEmpty(mobileSpec.Model) && mobileSpec.Model.ToLower() != Model.ToLower())
return false;
if (mobileSpec.Color != MobileColor.None && mobileSpec.Color != Color)
return false;
if (!string.IsNullOrEmpty(mobileSpec.OperatingSystem) &&
mobileSpec.OperatingSystem.ToLower() != OperatingSystem.ToLower())
return false;
if (mobileSpec.InternalMemory.HasValue && mobileSpec.InternalMemory != InternalMemory)
return false;
return true;
}
}
Congratulations! You have written good software
These are the some basic rules and by following them you can write Good Software every time. Just make it your habit and enjoy your work.