Introducing Legacy Payment Code0:00
Now, what if you inherited a project that's badly written? The code is coupled together, untestable, and each bug fix and new feature is a challenge on its own. Most of the time, you need to spend much more time reading the code than you do writing a new one. It's a daunting task, but for a lot of projects, it's a reality. You need to invest time to understand how the existing code works. Let's expand our payment example with some payment processing functionality. I have a PaymentProcessingController over here that is supposed to represent a functionality of legacy code.
I have a PaymentController over here that is supposed to represent a functionality of legacy code. Let's look at this Payment class. This one houses everything around payment, so payment processing, payment management, handling data from the database with raw queries. And it's complex, long, and difficult to navigate through. It's not small. It's not specific. It doesn't have a single responsibility, so all of the things we mentioned earlier about good practices when you design code are violated over here.
Incremental Legacy Code Approach0:57
It doesn't have a single responsibility, so all of the things we mentioned earlier about good practices when you design code are violated over here. Let's say you have been asked to add another payment option to the processing module. So you need to dig through the code, understand what it does, so you can add the requested functionality. The problem is that this code is too overwhelming. You're new to it. You're unsure how to start. Let's explore some techniques to help you do that. With legacy code, the best approach is to start small and incremental.
Tracing Execution Flow1:25
Let's explore some techniques to help you do that. With legacy code, the best approach is to start small and incremental. Going through one component after another can slowly build up your understanding of the software. And working on a concrete feature or bug fix is the best way to start because you have that concrete module that you want to understand, and it will just unravel everything afterwards. So what's the first step? It would be to trace the execution flow. Now, I don't have this implemented to show it in a browser, but in reality, you would just open your browser, locate the existing functionality, and find the request URL.
Now, I don't have this implemented to show it in a browser, but in reality, you would just open your browser, locate the existing functionality, and find the request URL. So let's say for us, this is processPayments, this one over here, admin/processPayments, and then I need to map it to the controller class that is handling the logging. Let's open the controller. We have processPayments method. So I'm looking for the processPayments method over here. This is where the processing happens. So what happens here? We create an instance of the Payment class, and then we call the processPayments with
So what happens here? We create an instance of the Payment class, and then we call the processPayments with startDate and endDate. Let's dig further to see how all of this behaves. So going to my Payment class, processPayments. There it is. It receives start and end, which I assume are dates, and gets all scheduled payments. Here is that method, pulling data from the database, yes, date between start and end. And then looping through all the payments to call some other method, processPayment. We have a switch block over here, and it's getting the payment type, implementing something.
Using Scratch Refactoring3:07
And then looping through all the payments to call some other method, processPayment. We have a switch block over here, and it's getting the payment type, implementing something depending on that type. I assume this is where my change is supposed to come. I want to demonstrate an interesting tactic here for understanding code that's difficult to understand. Apart from following the execution flow, which is the logical first step, there is something called scratch refactoring, and it's supposed to help you understand the code better. What does scratch refactoring mean? It means that you take some time to refactor the code without worrying if it's going to
What does scratch refactoring mean? It means that you take some time to refactor the code without worrying if it's going to work. You take a limited amount of time, let's say 30 minutes, before you start working on the functionality itself, and you spend this time going through the flow of the code and trying to improve it as you go. Let's look at an example of how it would look like. Again, from my controller, I find a Payment class, I find a method that is handling the processing of the payments located here, and I try to understand what all of this does. So getScheduledPayments.
Renaming for Readability4:15
processing of the payments located here, and I try to understand what all of this does. So getScheduledPayments. In real life, a lot of these methods and variables would be named weirdly, so your first step in the refactoring process is to rename them. Let's say this, we can make startDate and endDate, so that I can easily read through this and understand what it does. Then payments, processPayments, okay, over here, I receive some payment object, I have no idea what it is, but let's roll with it. I assume that it contains all of the details for this payment. This is pulling the payment details from the user.
Replacing Switch with Strategy4:57
I assume that it contains all of the details for this payment. This is pulling the payment details from the User. This part over here, I don't really like, and I could refactor this in a way that makes my code easier to extend, because I know that in the future, I will need to deal with some additional functionality, let's say, like the tasks that I'm supposed to implement right now. So instead of this switch, what I could do is maybe add a variable here, so I can create a constructor, and then maybe create a variable, this processingPaymentProcessors. And maybe this can be wire, and yeah, this is a nice suggestion. So I can create classes for all of this, PayPal and Payoneer, you know what.
And maybe this can be Wire, and yeah, this is a nice suggestion. So I can create classes for all of this, PayPal and Payoneer, you know what. And then, assuming that we're following the structure that we were demonstrating earlier, these are going to implement some specific contract or interface, and all have some process methods that I can rely on. So instead of doing this switch over here, I can just check my attribute from above. Okay, if not set, return invalid payment type. If it's set, then return yes. So this assumes that my new classes would implement this processPayment method and just receive the details, and now all of the methods below are obsolete, because I would
So this assumes that my new classes would implement this processPayment method and just receive the details, and now all of the methods below are obsolete, because I would just remove this part of the code, move it to the new class, and you understand the flow of action here. So the goal is to try to understand the legacy code by doing some refactoring and structuring it in a way that you see best fit for the given functionality. It doesn't mean that you are going to actually refactor it, or that you need to test it afterwards and send it to production. This is a simple exercise that is meant for you to understand how the code behaves. So after that limited time that you have allocated, just scratch everything.
This is a simple exercise that is meant for you to understand how the code behaves. So after that limited time that you have allocated, just scratch everything. And what I would do here is just don't save, forget about it, let it sink in, and then with this extra knowledge that you have gained about the structure of your class, you can go ahead and start actual refactoring and start implementing the new functionality.
