Skip to content

Refactoring in Day-to-Day Work | What is Code Refactoring + How to Refactor Code + Code Refactoring Example

Featured Image

What is code refactoring and why to refactor code?

It is a well-known belief that “refactoring requires dedicated effort from the team and it has to be executed as a special activity in the project.” I feel, in most cases, this is not true, not required, and not even practical.

  • The practical approach can be when we build any feature or fix any issue, we attempt to refactor some code around the targeted code.
  • One another popular argument against the concept of refactoring is “if things are working why touch them!” Well, there are multiple reasons for considering refactoring for this.
  • Usually, domain knowledge is gathered over a period. Once the system is used, all the stakeholders will have a better idea, including end-users. So, the facts which were true on day 1, it is possible that they would have been changed. If we don’t update the code to reflect that “new understanding” of the system, eventually code will be out of sync with real-life scenarios.
  • The second law of thermodynamics states that “The total entropy of a system either increases or remains constant in any spontaneous process; it never decreases.” This is equally true when we add new features or codes. Refactoring can help to reduce or maintain the entropy of the system.
  • Frequently, the system’s stability heavily relies on thorough testing conducted by the Quality team. However, it remains vulnerable to collapse when even minor changes are introduced for new business requirements. Most of the time the reason behind such vulnerability is messy code. The only option to come out of such a situation is to improve the code quality. And refactoring plays a vital role in achieving #CleanCode. Moreover, it will help developers to foresee possible use case scenarios.

Now that we have established the importance of #refactoring, let’s see one of the possible approaches to achieve it. I want to describe it with an example, but before that let me provide a high-level abstract view of the overall process.

How to refactor code?: Process abstract

  • If you are attached to the existing implementation, it would be difficult to get innovative ideas and so it is important to focus on desired functionality at the beginning without much worrying about existing implementation. And writing pseudo code can be a good idea in this situation.
  • I recommend using the abstract thought process while building pseudo code.
  • The advantage of writing pseudo code during refactoring process is, you would be able to identify patterns at functional level which always makes code better.

Our first-level pseudo code should make sense for business users. It means, please avoid very low-level details initially.

  • Iterate through the pseudo code multiple times and start to add more details gradually with each version.
  • The iteration-based process will provide us an opportunity to enhance pseudo code to reflect an understanding of the underlying business process in a better way.
  • Please keep in mind, the more you will think from an end-user/functional point of view, the better pseudo code will be generated. You will be able to observe patterns, the relations among business objects at a higher level, which may help you to ask meaningful questions to your business users or uncover special scenarios.

Note: Along with pseudo code, if you can consider the test scenarios, it would be the best.

Code Refactoring Example:

For our discussion, I would like to give an example of the Billing and Invoice Management System which we built for our client in the Real Estate domain.

To avoid confusion and stick to our context of refactoring, let me share only relevant information for the system.

  • This system is used by our client who provides skilled workers to construction companies. Our client also provides the service of executing whole assignments with the help of in-house worker staff.
  • As different types of services are provided, the client takes different charges depending on what services opt for, the kind of skills, and other factors.
  • System helps to generate billing on a monthly basis for different builders. It is called Monthly Milestone and it contains all the relevant details which include workers and skills associated with that, corresponding rates, hours spent, constraints and so on.(e.g. max or min hours per week).
  • At the end of the month, timesheets are submitted against the milestone and based on that billing data are calculated. The finance team can create invoices as per generated billing data.


Requirement:

The finance team wanted to update corrected billing rates in the system after billing data is calculated but before invoices are sent to customers and which should trigger recalculation of billing data.

Before we go deep into the process, we decided to take a high-level look at the existing code. We observe following points:

It was possible to untangle existing billing calculation logic from orchestration logic which would give us better code readability and the possibility to implement detailed automation testing scenarios by using pure functions.

There was a possibility to enhance calculation logic by modularizing it which will be easier to maintain in the longer run.

Let me share how iteratively pseudo codes were generated.

Note: You might find the length of the pseudo-code is larger, as I have shared the original pseudo-code as it is. In case you want to skip that part, you can directly jump to key learning section at the end.

Pseudo code version 1: 

save or submit

extract List<TimeBasedBillingInfo> BillingInfoForWorkers from incoming dto

get allocation detail (hours, roles) for these workers from external system
get leave data for workers from external system

get workpack (contract) for this project

TimeBased Engagement

workpackPotentialWorkers = Potential workers to fetch from workpack.

parameters coming from BillingInfoForWorkers

- workerId, workerRole, netBillableHours.

worker list to update

- if (BillingInfoForWorkers.get(workerId) != null) {

if (workpackPotentialWorkers. get Role (BillingInfoForWorkers.get(workerId).role) == null) {

// return error -> role is not defined at workpack level

}

worker. set role (BillingInfoForWorkers.get(workerId).role);

worker.netBillableHours = BillingInfoForWorkers.get(workerId).billableHours;

}

worker.set leave days;

// handle some of the constraint cases for worker. // refer workpack aggregate - TBD - possible to reuse workpack aggregate logic for projected amount calculation?

// as of now, constraints are not considered.
// set Total amount for worker

worker.set Total amount (workpackPotentialWorkers. get (worker.role). rate * worker. getBillableHours() )

// handle constraint cases based on amount. // refer workpack aggregate

return worker list.

// get projected value from workpack.
-------------
sync

create List<TimeBasedBillingInfo> BillingInfoForWorkers from read model

- mostly same logic of save/submit should work.
--------------
get milestone detail.

create List<TimeBasedBillingInfo> BillingInfoForWorkers from read model

get Info from external system and calcualte - before timesheet submission

Directly use read model -> after timesheet submission

- mostly same logic of save/submit should work 

This was the first version of the pseudo-code we generated. Though, it was missing a lot detail. Also, it looks shabby against the actual code standards, right? But it served multiple purposes.

We started to see the requirements from an abstract perspective. We could see that most of the logic can be reused across – save/submit/sync and get features.

We could focus on business steps to implement. In fact, while writing this, we realized that the scenario for constraint was not covered in the old code. And we also thought that it would be possible to use that logic from workpack (basically a contract).

We also gave thought to using the right type of collections (based on usage patterns).

For many places, we didn’t have a clue what to consider. But that kind of uncertainty is fine. Also deliberately, we didn’t consider all the scenarios so that we don’t spend too much time on the first version, and can improve things iteratively.

Pseudo code version 1.1:

Next steps:

Check logic for assignment driven engagement types for save and submit timesheet. Enhance pseudo code based on that.

For TimeBased Engagement:

Check logic of how projected amount was calculated for milestones of workpack. It might be possible to simplify and reuse the logic.

check if we can remove BillableHours, NonBillableHours and ResponseHours classes. and can convert “abstract class Hours” into “class BillableNonBillableHours”.

Check if using maps with worker Id as key can be helpful. it can remove iterations.

Now, this was interesting. instead of adding logic straight away, we thought about pending points (from pseudo-code perspective). And also, ideas such as using maps at some places instead of list types or removing unnecessary created classes.

In the next iteration of pseudo-code, we added pseudo code for “Assignment based” engagement. We also found out what logic can be common among both engagement types and what can be specific one.

Workpack Calculation Pseudo Code Version 1: 

save/submit timesheet API

common method for Projected amount and Actual amount calculation for Time based engagement type

-> use existing method with some refactoring

getProjectedAmountForTimebased(ZonedDateTime milestoneStartDate,

Map<Duration, Integer> effectiveDaysMap, List<TimeBasedProjectedWorker> projectedWorkers, BillingConstraints billingConstraint)

method from workpackDomainService (relocate this method from TimebasedBillingConfiguration class to workpackDomainService)

- after relocating, call this method from workpackDomainService into TMBillingConfiguration class

-> Method refactring :

- return a valueObject which contains (Double amount, List<WorkerBillingInfo>)

- WorkerBillingInfo contains : workerId, totalCost, netBillableHours(as per billingConstraint)

-> For Milestone Projected Amount: the same method will be called from 
: TMBillingConfiguration -> getProjectedAmount(
ZonedDateTime milestoneStartDate,
Map<Duration, Integer> effectiveDaysMap) method

- from the returned valueObject, 
amount can be used as value of ProjectedAmount

-> For Milestone Actual Amount: call this method from milestoneAggregate -> Save/Submit Command

- Create effectiveDaysMap using values for workers based on leave and allcoated data

Because for amount calculation, we thought that workpack logic of projected amount calculation can be reused with refactoring, we started to look into that. That’s why a separate version was created.

This is not the final version and we iterate further to make things better. and eventually, we could use the common logic in both places.

Once we found that the pseudo-code is mature enough (not 100% completed but enough to start with code updation), we started to make changes to the actual code and as and when required, we updated the pseudo-code too.

Key learnings for your next code refactoring exercise

  • Code #refactoring is a crucial part of #productdevelopment journey and pseudo code can help to do that most effectively if it is done right way.
  • Readability is a very important aspect of pseudo code. Your pseudo code should be readable/understandable which can help to achieve a clear understanding of implementation.
  • It is natural for a developer to build the pseudo code from a technology perspective and focus more on “technical readability”, e.g. I will bring this data by joining X & Y table. I will run this loop and update this value. I will store the data back in the Z table.

Instead of technical readability, if readability is considered from business needs, it can help in a better way.

Try to think about how your business user would describe the operation. What steps will they consider? Of course, you can enhance the business process, after all there should be advantage over manual execution but still that should be achieved in the context of business user’s need.

  • Not every time, but for critical module/functionality, if you can show your pseudo code with your business user and if they can provide feedback, that would be the most useful one.
  • #performance vs #readability dilemma: I am not sure whether you’ve faced this challenge ever. I consider for the best readability; the flow of the business process should be reflected in the code. Against which we require to take care of certain technical operations for better performance, e.g., bulk data loading at the start. While both #performance and #readability is important, when performance tuning impacts readability, please make sure to add detailed comments to explain that part of the code. It should be easier for you or other developers to understand the rationale behind that code in future.
  • While pseudo code is informal code, it provides a great opportunity to preview things at an abstract level. You can think about what part of logic should reside at what level. e.g., In the above example, we could identify what logic should be part of the domain layer and what part should be at the application layer.
  • This can also help to identify the nature of the methods. e.g., at the domain level, we prefer to implement pure function. It can help to perform automated unit testing for different scenarios very effectively.
Avatar photo
Team Azilen

Azilen Technologies is a Product Engineering company. We collaborate with organizations to propel their software product development journey from Idea to Implementation and all the way to product success.

Related Insights