While working on reviewing and refactoring code for applications to improve quality, many times, I found that the code bases were written to address functional requirements but lost focus on technical quality of the code. In some cases, the code was good during initial releases but slowly became more complex and erroneous.
These issues and problems can be avoided without putting much efforts and code can be maintained with expected quality for long term. The issue with most of the applications is, the focus, which is on the functional requirements and scope to be delivered, but not much on, how to keep the code base simple and maintainable for years to come. Where we have to understand that requirements might change and functional issues can be fixed in the code base easily than fixing the issues with code quality and complexity, which impact the life of the application by reducing maintainability. Here, we should also understand that on an average maintenance/production lifetime is 5+ times than the development time of most of the business applications.
Now a days, we generally follow agile or similar methodologies to develop and maintain applications, where we write once and change many times to develop and maintain the code base through multiple iterations. This extends the need of an approach which is simple and effective in not only writing the code once but also, changing/extending the code many times and maintaining it with expected quality for long term.
Effective Coding Approach
Let’s understand first, what is good quality code looks like. It should have at least below characteristics,
- Simple to read (by human 🙂 ) and extend
- Easy to test and verify
- Low Cost of Change
There are different ways and approaches to write effective and well designed code such as Extreme Programming, TDD, BDD, Pattern Oriented Design & Code etc. But, what I found most effective and simple to follow based on 3 basic steps as below.
- Unit Tests
Let’s understand the approach in detail. The approach is to write effective code is depicted in above diagram. The approach is this, Write Code –> Write Unit Tests –> Refactor –> Write/Change Unit Tests –> Write/Change Code –> Write / Change Unit Tests. Here, Step 1 is to Write/Change code to implement functional requirements. Steps 2 is to Write/Change Unit Test to Test a unit of code (generally a class in OOPS). Step 3 is to optimize the existing code without changing it’s behavior or functionality. Here is a link from Wikipedia on code refactoring.
Step 1 – Code
Writing is easy but writing effective code is not easy. At least, when you don’t know the right approach. An approach to write effective code is by following the design principles or thumb rules, which guides you and keeps you on right path. Some examples of such principles for object oriented systems are as below,
- Encapsulate Variations
- Favor Object Aggregation Over Class Inheritance
- Design to Interfaces
- Any class either using an object or creating it but not both
If you look at these principles, these are simple guidelines, which can be easily followed by anyone. it’s simple to follow compare to mapping and implementing design patterns etc. but, when you follow these principles you will not only able to write better code but also, it will help you become a better programmer by following a simple and effective approach.
Here are few more to help you writing better code.
- Any class should operate at one of these perspectives, (Martin Fowler)
- Conceptual – Abstract Classes or Interfaces
- Specification – Call other classes to get things done
- Implementation – Implementing classes and methods
- What you hide, you can change (Loose Coupling)
- Types of coupling
- Identity (Avoid – Should not use)
- Inheritance (Reduce – Use only when Natural)
- Subclass (Avoid – Should not use)
- Representational (Recommended – Use as per the need such as use of Interfaces/Abstract Classes)
- Types of coupling
Step 2 – Unit Test
Once you write code for a method or class, you should write unit tests and ensure it works as expected. It is not recommended to write too many classes and methods and then write unit test. It’s always good to write a method and write unit tests for it. This way, you will not only be able to ensure the correctness of the code piece by piece by also, switching the hat (Coder to Tester) will give you opportunity to see your code from different perspectives. It will also have lesser cost of a bug, compare to writing code for many methods at once.
Here are few guidelines for effective unit testing,
- Unit Test primary, secondary and exceptional flows
- Test boundary conditions and invalid inputs
- Code Coverage should be at least 70 – 80%
- Use Mocking framework such as MOQ to reduce effort in mocking external dependencies and increase effectiveness.
Step 3 – Refactor
Refactoring is an activity to improve the quality of code without affecting it’s behavior. This activity brings more value and helps maintain code in long run without adding much effort or risk.
Here is a definition from Martin Fowler.
Refactoring is a controlled technique for improving the design of an existing code base. Its essence is applying a series of small behavior-preserving transformations, each of which “too small to be worth doing”. However the cumulative effect of each of these transformations is quite significant. By doing them in small steps you reduce the risk of introducing errors. You also avoid having the system broken while you are carrying out the restructuring – which allows you to gradually refactor a system over an extended period of time.
As you have got a brief idea about refactoring so now, you must be having below questions,
- How do I know, what to be refactored?
- How can the refactoring be done?
- When do I need to do the refactoring?
To answer above questions or similar ones, here is the guidance, which I generally follow,
- Identify smells in code
- Refactor code to remove the smell
- Fix unit tests
Now, the questions is what are the smells. Here is an explanation.
Smells are certain structures that indicate violation of fundamental principles and negatively impact quality.
Below are some examples of such smells,
- Duplicate Code
- Long Method
- Large Class
- Long Parameter List
- Many Instance Variables
- Divergent Change
- Shotgun Surgery
- Data Clumps
- Primitive Obsession
- Switch Statement
Smells can be categorized in below categories,
- Code Smells – Violating coding rules, style or formatting. These can be identified and fixed using static code analysis tools such as Resharper, Sonar, Visual Studio Code Analysis etc.
- Design Smells – Violating design principles. Fixing these issues need some expertise in applying design principles and refactoring techniques.
- Architecture Smell – Violating architecture principles & design. These need major changes at the application/solution level, changing a part of the code would not help much.
Further on Code Refactoring
One important thing about the refactoring is, it should not be carried out as separate activity, task etc. but should be done when a code block requires a functional change or bug fix. A thumb rule for refactoring can be considered as if a piece of code is working don’t touch it. It means, if a code block is not having any issue don’t try to refactor just to simplify it. It should be done when any bug arises or needs a functional change. In such scenario, first refactor the code, if any smells found, fix unit tests and then, make the functional change or bug fix.
This way, you will not only be able to maintain the code quality without putting much efforts but also, avoid breaking the code, which is already working and, avoid extra efforts for coding, testing and deployment.
Thank you and Happy Learning!