If you’ve never done Test Driven Development or aren’t even sure what this "crazy TDD stuff” is all about than this is the series for you. Over the next 30 days this series of posts take you from “I can spell TDD” to being able to consider yourself a “functional” TDD developer. Of course TDD is a very deep topic and truly mastering it will take quite a bit of time, but the rewards are well worth it. Along the way I’ll be showing you how tools like JustCode and JustMock can help you in your practice of TDD.
Previous Posts in this Series: 30 Days of TDD – Day 18 – Refactoring Revisited Pt. 1
In the previous post we started refactoring our code to make sure we were complying with the SRP. Using our tests we are able to optimize our code for readability and maintainability. We’ll continue in the post by examining how the concept of abstractions can inform our refactoring efforts.
We did some basic refactoring around the interaction with the order fulfillment service in the last post. We extracted the whole block of code that dealt with order fulfillment from PlaceOrder and called that method from PlaceOrder. We then pulled out the code to open and close sessions with the order fulfillment service. When we were done, our order fulfillment code looked a bit like this:
1: private void PlaceOrderWithFulfillmentService(ShoppingCart shoppingCart, Customer customer)
3: //Open Session
4: var orderFulfillmentSessionId = OpenOrderFulfillmentSession();
6: var firstItemId = shoppingCart.Items.ItemId;
7: var firstItemQuantity = shoppingCart.Items.Quantity;
9: //Check Inventory Level
10: var itemIsInInventory = _orderFulfillmentService.IsInInventory(orderFulfillmentSessionId, firstItemId, firstItemQuantity);
12: //Place Orders
13: var orderForFulfillmentService = new Dictionary<Guid, int>();
14: orderForFulfillmentService.Add(firstItemId, firstItemQuantity);
15: var orderPlaced = _orderFulfillmentService.PlaceOrder(orderFulfillmentSessionId,
19: //Close Session
23: private void CloseOrderFulfillmentService(Guid orderFulfillmentSessionId)
25: //Close Session
29: private Guid OpenOrderFulfillmentSession()
31: var orderFulfillmentSessionId = _orderFulfillmentService.OpenSession(USERNAME, PASSWORD);
32: return orderFulfillmentSessionId;
(get sample code)
I want to refactor out the logic around placing orders with the order fulfillment service now. I could extract each out to an individual method from here. But, the better solution it to extract both methods (and the code around them that supports them) to an intermediary method. This accomplished a couple things. For starters it makes dealing with the logic around placing orders with the order fulfillment service that much easier to work with since I am reducing the amount of intermingling that code has to do with the code around opening and closing sessions. This hints at the second benefit, which is that this approach brings our code further into compliance with SRP. If you view the actions around placing an order (checking inventory levels and then placing the order if the inventory exists) as steps in the “adding an item to the order process” then you can also see that simply pulling the two actions (check inventory level and place orders) into methods called from PlaceOrderWithFulfillmentSerivce gives that method too many reasons to change. It not only needs to change if the “macro” workflow changes (open a session, place an order, close a session) it also needs to change if a detail of placing an order changes. An intermediary method is the best means of fixing this problem. It makes the code easier to work with and abstracts the details of placing an order away from a method that has no reason to know or care about such details.
Pulling the block of code that handles placing orders out of the PlaceOrderWithFulfillmentService into a separate method gives me this:
6: PlaceOrderWithFulfillmentService(shoppingCart, orderFulfillmentSessionId, customer);
8: //Close Session
12: private void PlaceOrderWithFulfillmentService(ShoppingCart shoppingCart, Guid orderFulfillmentSessionId, Customer customer)
14: var firstItemId = shoppingCart.Items.ItemId;
15: var firstItemQuantity = shoppingCart.Items.Quantity;
17: //Check Inventory Level
18: var itemIsInInventory = _orderFulfillmentService.IsInInventory(orderFulfillmentSessionId, firstItemId, firstItemQuantity);
20: //Place Orders
21: var orderForFulfillmentService = new Dictionary<Guid, int>();
22: orderForFulfillmentService.Add(firstItemId, firstItemQuantity);
23: var orderPlaced = _orderFulfillmentService.PlaceOrder(orderFulfillmentSessionId,
I was able to create a new method which I also called PlaceOrderWithFulfillmentService. This demonstrates another advantage of an intermediate method; potential code reuse. I can use the original method if I need to have “full service” for my order method. If I somehow already have a session id I can use the second method to order items. I run my tests and I see that they all still pass, which makes this a successful refactor (figure 1):
Figure 1 – Passing tests
One thing I don’t really like about the new method is the parameter list. I think it’s more logical to have the orderFulfillmentSessionId as the first argument. Luckily JustCode has an automated refactoring that can handle this for me. I can access this automated refactoring by placing my cursor in the parameter name I want to move (in this case the orderFulfillmentSessionId) and bringing up the JustCode visual aid menu (CTRL + `) and selecting “Move Or Delete Parameter…” from the Refactor menu (Figure 2). I can also use the keyboard shortcut of CTRL + R, Ctrl + O if I prefer.
Figure 2 – The Visual Aid menu
Activating the Move Or Delete Parameter selects the parameter. From here I can hit the Delete key if I want to remove the parameter from the method or ESC if I want to abort the operation entirely. I want to move the parameter, so I use the TAB key to cycle my parameter through the various positions in the parameter list. Once the parameter is in the front, I hit enter to complete the refactoring. JustCode not only changes the parameter position in my method, it also finds all the places that my method was called from and changes the variable list of each call. I can verify this worked by running my tests (Figure 3):
Figure 3 – The tests still pass
In the next post we’ll tackle separating the two methods that deal with placing orders from the order fulfillment service. As you’ll see, this will provide some different challenges from the refactorings we’ve done in the past couple posts.
In this and the previous post I’ve demonstrated that refactoring is not simply pulling code out of methods into other methods. Understand the steps in your code and leaning where the natural abstractions occur is something that makes refactoring easy and effective. I’ve also demonstrated how JustCode can make refactoring easy by automatically extracting code you specify to new private methods, but also by allowing you to easily and quickly change those methods as needed.
Continue the TDD journey:
Copyright © 2017, Progress Software Corporation and/or its subsidiaries or affiliates. All Rights Reserved.
Progress, Telerik, and certain product names used herein are trademarks or registered trademarks of Progress Software Corporation and/or one of its subsidiaries or affiliates in the U.S. and/or other countries. See Trademarks or appropriate markings.