Refactoring is a fact of life, and the "extract method" refactoring is the cornerstone of improving your code.
Let's dive right in with the cornerstone of refactoring, extract method, which is going to ask us to pull out related bits of functionality into their own self contained sub methods.
What we have in a theoretical sense is a statement method which is a pretty high level method. It's going to print the entire statement for one of our customers. We can start to group the related functionality here.
We see that a lot of the code works towards generating a total amount where each rental's cost is added to the total amount. It works towards building a total number of frequent renter points for this statement where each rental's frequent renter points is added to the total number of frequent rental points.
Then it also works towards printing a result, which is the statement itself. It interpolates a bit of information for every movie that is rented. What's actually returned is the result, so we can deduce that we could pull out methods for creating the total amount and creating the frequent renter points from this because the statement's really concerned with actually building the statement object.
We could split out the information, these two accumulator methods which are building towards different amounts.
Stating off with frequent renter points seems to have less associated code. There's not this huge block associated with it so I want to work with this first.
It would be pretty easy to do an extract method if we weren't worried about local variables but we can see here that frequent renter points is going to be used after the point where we would do this addition of frequent renter points because it needs to be added into the result here.
We're going to have to assign out to this total number of frequent renter points. It's used each time through the loops. We know here that we're going to have to add to it whatever the result would be from something like rental.frequentrenterpoints.
That's going to give us the frequent renter points for this particular rental. We'll start by pulling this out.
Let's head over to our rental. We're going to start by adding in the code that we pulled out. And so, we want a function called frequentrenterpoints. It shouldn't need any input because all of these are being called on the rental itself so it should already have all of the input that it needs.
Let's try to figure out what's going on here. We have if the rental's movie type is a new release and it's been rented more than one day then we're going to add one to the frequent renter points. We are always going to add one to the frequent renter points previously.
This is actually going to have to be the return value, whatever frequentrenterpoints was because that was a local variable in the previous function. I think it would make more sense to pull this up now.
If we say "if (rental..." and we need to replace that with "this"...
Here, if both of these things are true we're actually going to return two since that would have been the result. Otherwise we just return one. That should be the default behavior.
We see that this is working because now our tests are back to passing. We know we're good to go.
This is why it's so important for us to have a solid test harness that we know covers everything because it allows us to play around with the code that we're working with, trying different things out.
We want to come back here and try to do the same type of thing on this chunk of code here, understanding that it's probably going to be a little bit different.
We assume here that this amount is going to be added here to the total amount. It seems like this amount is only used within the context of this block. It just gets added to this amount.
The reason this is important is because this is a temporary variable that we can remove. We can move that off to the extracted method.
Here we're going to assume that we're going to add totalAmount with the rental.charge if we move it off to a method like that.
Now we're going to just see how this goes as we play around, knowing we can always get back to a previous good state with our test cases. We'll add a charge method here.
Let's see. We've got the rental.movie.type and we know that we can change all cases of rental to this. We'll do the same thing here. One of the really nice things that we get out of our tests are that we know at every moment what's wrong.
In rental on 29 we have no variable called thisAmount so that makes sense. It's because we haven't declared thisAmount in this context here. We can try that. It seems like in each case we're adding to thisAmount. Really, we're just going to return from each of those cases.
For now, we'll try returning thisAmount, see how that goes.
We still can't find thisAmount but this time it's in customer so let's head back to customer and see what's going on here.
Oh, thisAmount is being used in another place but we can just use rental.charge. This technique is called replace temp with query. Now we see our tests are passing here.
We see that we can replace that temp in multiple places with this query method. That's one of the really nice things about it because that temp wasn't being used for any other purpose than to get the value of this method off of it.
We use that replace temp with query.
Now we can really start to see some of the benefits of extract method. In the first place, we've improved the clarity of the statement method a whole lot because large hunks of code are just replaced with what their intention was.
Our intention was for each rental to add to the total amount based on the charge for that particular rental. It's now much more obvious how we're coming up with the total amount.
And then, the same thing is true for frequent renter points. For each rental's frequent rental points we add to the total number of frequent rental points.
We've revealed the intention that we had with the original method even better. We've helped clarify it quite a bit for ourselves because we hadn't seen this method before.
Another thing that's really nice here is code reusability. Just as we've seen, we were able to replace the temp with query with rental.charge. We could also reuse the charge method on the rental in many other cases. The same is true for frequent renter points.
One of the other things that we're starting to see here but we haven't really seen it yet because we've just started refactoring is that high level methods are able to use natural language. The total amount is the charge for each individual rental and the frequent renter points are the sum of all of the frequent renter points for each rental.
It starts to be much easier to read and starts to suggest other ways that we might refactor here. We'll take a look at those in the next couple of refactoring videos.
The end result that we're hoping for, though, is that we're able to much more closely model the domain language of renting videos, which maybe is a dead domain language at this point. But that's the concept that we're working towards through refactoring. We gain quite a lot of flexibility through extract method.
That's it for this video. Happy refactoring.