Introduction
Today, any software implementation is talking to external or internal systems (e.g., SOA), there are lots of entities, lot of code and of course lot of mappings. This article focuses on improving the maintainability of your code thus decreasing the cyclomatic complexity and lines of code, using existing features of .NET.
Background
Few days ago I was implementing a new functionality wherein I found the existing code consumes loads of entities and has huge code for
just one to one mapping. I ran the code metrics (from VS 2010) and found the code is not maintainable. If this code is so difficult to read and understand for me
as a developer then what about the person who will maintain it later?
This made me to search about few small tweaks to improve the code quality.
Beauty of code
There are many simple ways (which we usually forget or ignore) while coding which can make our code look beautiful and maintainable.
- Inline Initialization
So instead of :-
PersonalInfo entity = new PersonalInfo();
AddressEntity nameAndAddress = new AddressEntity();
nameAndAddress.DayPhone = Convert.ToString(orderEntity.dely_phone_day);
nameAndAddress.EveningPhone = Convert.ToString(orderEntity.dely_phone_eve);
nameAndAddress.AddressLine1 = orderEntity.dely_address1;
nameAndAddress.AddressLine2 = orderEntity.dely_address2;
nameAndAddress.AddressLine3 = orderEntity.dely_address3;
nameAndAddress.AddressLine4 = orderEntity.dely_address4;
nameAndAddress.AddressLine5 = orderEntity.dely_address5;
nameAndAddress.ZipCode = orderEntity.Postcode;
nameAndAddress.FirstName = orderEntity.NickName;
nameAndAddress.Title = orderEntity.dely_address_no.ToString();
entity.Address = nameAndAddress;
We can write:
AddressEntity nameAndAddress = new AddressEntity()
{
DayPhone = Convert.ToString(orderEntity.dely_phone_day),
EveningPhone = Convert.ToString(orderEntity.dely_phone_eve),
AddressLine1 = orderEntity.dely_address1,
AddressLine2 = orderEntity.dely_address2,
AddressLine3 = orderEntity.dely_address3,
AddressLine4 = orderEntity.dely_address4,
AddressLine5 = orderEntity.dely_address5,
ZipCode = orderEntity.Postcode,
FirstName = orderEntity.NickName,
Title = orderEntity.dely_address_no.ToString()
};
This way you can achieve:-
- Better readability
- Better maintainability
- While calculating code metrics this will be considered as one single line.So, this will reduce the lines of code (for this example from 13 lines of code to 1)
- Will prevent duplicate assignment (for entities with large no. of properties this will be very useful)
- Faster to code
2. Mapping Functions
When we do one to one mapping, what's the first thing we do? We write a method, something like this
public static MyEntity MapServiceEntityToMyEntity(ServiceEntity response)
This is how it is called
ServiceEntity response = GetServiceResponse(params);
MyEntity entity = Mapper.MapToMyEntity(response);
The mapper class gradually grows and grows.
The problem comes when there are multiple developers working on same piece of code and one of your peers while implementing a new functionality is not able to find this function properly and decides to write his own mapping method. Something like this
public static MyEntity MapToMyEntity(ServiceEntity response)
What happens next? The code complies and the mapping also works. But this increases lines of code, it has duplicate code and most importantly, the person who will maintain this code will pray for your death
What can we do about it? Always remember one simple thing. When you convert a Type "A" to Type "B" then your mapping class/method should always have one and only one function doing this. We can ensure this by creating extension methods. Also we can agree upon a naming convention for it.
I chose to overload all functions in the name of "Map". So my "Mapper.cs" contains all methods named as "Map". Below is one example.
public static MyEntity Map(this ServiceEntity response)
This way you can achieve:-
- Prevent duplication of mapping functions
- Easy to code
ServiceEntity response = GetServiceResponse(params);
MyEntity entity = response.Map();
You can make it even easier
MyEntity entity = GetServiceResponse(params).Map();
3.
Mapping Lists [this section requires knowledge of Linq and PLinq]
When we map a list, I have seen many doing something like:-
List<AddressEntity> listOfAddresses = new List<AddressEntity>();
foreach (ServiceResponse serviceAddress in response)
{
AddressEntity address = new AddressEntity();
address.DayPhone = serviceAddress.DayPhone;
address.EveningPhone = serviceAddress.EveningPhone;
address.HouseNumber = serviceAddress.HouseNo;
address.Road = serviceAddress.Road;
address.City = serviceAddress.City;
address.Country = serviceAddress.Country;
address.ZIpCOde = serviceAddress.ZipCode;
listOfAddresses.Add(address);
}
We can straightaway improve this by using Linq
List<AddressEntity> listOfAddresses =
new List<AddressEntity>(response.Select<ServiceResponse,AddressEntity>(serviceAddress =>
new AddressEntity()
{
DayPhone = serviceAddress.DayPhone,
EveningPhone = serviceAddress.EveningPhone,
HouseNumber = serviceAddress.HouseNo,
Road = serviceAddress.Road,
City = serviceAddress.City,
Country = serviceAddress.Country,
ZipCOde = serviceAddress.ZipCode
}));
If the internal mapping requires some heavy operations (like calculating total or checking a property) and you are not bothered about the order of the elements in the list.
You can further use parallel Linq or PLinq (remember-make sure the operations/variables/methods used under parallel loop are all thread safe)
List<AddressEntity> listOfAddresses =
new List<AddressEntity>(response.AsParallel().Select<ServiceResponse,AddressEntity>(serviceAddress =>
new AddressEntity()
{
DayPhone = serviceAddress.DayPhone,
EveningPhone = serviceAddress.EveningPhone,
HouseNumber = serviceAddress.HouseNo,
Road = serviceAddress.Road,
City = serviceAddress.City,
Country = serviceAddress.Country,
ZipCOde = serviceAddress.ZipCode
}));
More on parallel programming - http://msdn.microsoft.com/en-us/library/ff963553.
When you refactor your code this way make sure that the performance is intact. Though it is out of scope of this article, but I would
suggest do some performance tweaks especially on loops and use the best possible option (for, foreach, Linq,Plinq, Parallel for and foreach) for a particular logic.
One good link is -
http://www.codeproject.com/Articles/6759/FOREACH-Vs-FOR-C
Another very useful link - http://geekswithblogs.net/BlackRabbitCoder/archive/2010/08/26/c.net-five-little-wonders-that-make-code-better-1-of.aspx