Telerik blogs

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. 

Where We Left Off

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)
 2:         {
 3:  //Open Session
 4:             var orderFulfillmentSessionId = OpenOrderFulfillmentSession();
 5:  
 6:             var firstItemId = shoppingCart.Items[0].ItemId;
 7:             var firstItemQuantity = shoppingCart.Items[0].Quantity;
 8:  
 9:  //Check Inventory Level
 10:             var itemIsInInventory = _orderFulfillmentService.IsInInventory(orderFulfillmentSessionId, firstItemId, firstItemQuantity);
 11:  
 12:  //Place Orders
 13:             var orderForFulfillmentService = new Dictionary<Guid, int>();
 14:             orderForFulfillmentService.Add(firstItemId, firstItemQuantity);
 15:             var orderPlaced = _orderFulfillmentService.PlaceOrder(orderFulfillmentSessionId,
 16:                 orderForFulfillmentService,
 17:                 customer.ShippingAddress.ToString());
 18:  
 19:  //Close Session
 20:             CloseOrderFulfillmentService(orderFulfillmentSessionId);
 21:         }
 22:  
 23:  private void CloseOrderFulfillmentService(Guid orderFulfillmentSessionId)
 24:         {
 25:  //Close Session
 26:             _orderFulfillmentService.CloseSession(orderFulfillmentSessionId);
 27:         }
 28:  
 29:  private Guid OpenOrderFulfillmentSession()
 30:         {
 31:             var orderFulfillmentSessionId = _orderFulfillmentService.OpenSession(USERNAME, PASSWORD);
 32:  return orderFulfillmentSessionId;
 33:         }

(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:

 1:  private void PlaceOrderWithFulfillmentService(ShoppingCart shoppingCart, Customer customer)
 2:         {
 3:  //Open Session
 4:             var orderFulfillmentSessionId = OpenOrderFulfillmentSession();
 5:  
 6:             PlaceOrderWithFulfillmentService(shoppingCart, orderFulfillmentSessionId, customer);
 7:  
 8:  //Close Session
 9:             CloseOrderFulfillmentService(orderFulfillmentSessionId);
 10:         }
 11:  
 12:  private void PlaceOrderWithFulfillmentService(ShoppingCart shoppingCart, Guid orderFulfillmentSessionId, Customer customer)
 13:         {
 14:             var firstItemId = shoppingCart.Items[0].ItemId;
 15:             var firstItemQuantity = shoppingCart.Items[0].Quantity;
 16:  
 17:  //Check Inventory Level
 18:             var itemIsInInventory = _orderFulfillmentService.IsInInventory(orderFulfillmentSessionId, firstItemId, firstItemQuantity);
 19:  
 20:  //Place Orders
 21:             var orderForFulfillmentService = new Dictionary<Guid, int>();
 22:             orderForFulfillmentService.Add(firstItemId, firstItemQuantity);
 23:             var orderPlaced = _orderFulfillmentService.PlaceOrder(orderFulfillmentSessionId,
 24:                 orderForFulfillmentService,
 25:                 customer.ShippingAddress.ToString());
 26:         }
(get sample code)

 
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):

image

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.

image

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):

image

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.

Summary

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:

JustCode download banner image


About the Author

James Bender

is a Developer and has been involved in software development and architecture for almost 20 years. He has built everything from small, single-user applications to Enterprise-scale, multi-user systems. His specialties are .NET development and architecture, TDD, Web Development, cloud computing, and agile development methodologies. James is a Microsoft MVP and the author of two books; "Professional Test Driven Development with C#" which was released in May of 2011 and "Windows 8 Apps with HTML5 and JavaScript" which will be available soon. James has a blog at JamesCBender.com and his Twitter ID is @JamesBender. Google Profile

Comments

Comments are disabled in preview mode.