Extract Method


When developers go back and take a look at their code, perhaps during a periodic code review or after a particularly long session of heads-down development, they often find methods that are too long or course-grained, contain duplicate code, or are just poorly organized. A common thing to do is pass over the code and create fine-grained, discrete methods to reduce these issues and make for a more readable, reusable, and maintainable code base.

The problem, of course, is that doing this is time consuming and often introduces bugs into the code. The C# code editor in Visual Studio 2005 provides an Extract Method refactoring tool to ensure a quick, bug-free experience when you're working to better organize your code. With this tool, you can create a new method using existing code.

Accessing the Extract Method Refactor

To access the Extract Method refactor operation, you first must select a portion of code to refactor. You then can use the Refactor menu and select the Extract Method menu item. You can also invoke the Extract Method from the context menu via a right-click.

Tip

To invoke the Extract Method operation from the keyboard, first select the section of code you want to extract. Next, play the chord Ctrl+R, Ctrl+M.


Extracting Methods

With the Extract Method operation, you can create (or extract) a new method from multiple lines of code, a single line, or an expression within a given line of code. In each case, the method is created immediately following the method from which the code was extracted. The extracted code is replaced by a call to the new method.

Listing 8.1 provides an example of a typical, overlong method. We've added line numbers for reference purposes. When you're reviewing code, methods such as these are common and exactly what you should be looking for. The method is designed as a static call that returns a given customer's Order object based on the customer's ID number and the order ID number. However, the order, the order line items, and the customer details are all retrieved from discreet database calls and stored in domain-specific objects. These objects are then stored on the order as properties.

Listing 8.1. A Long Static Method

01  public static Order GetCustomerOrder(int customerId, int orderId) { 02 03    DataAccess.DAL dal = new DataAccess.DAL(); 04    Order order = new Order(); 05 06    //get order details 07    System.Data.DataTable dtOrder = dal.GetData("customerOrder", orderId); 08 09    //validate order against customer 10    if (customerId != (int)dtOrder.Rows[0]["customer_id"]) { 11      throw new ApplicationException("Invalid order for the given customer."); 12    } 13    order.Id = (string)dtOrder.Rows[0]["id"]; 14 15    //get order items 16    List<OrderItem> items = new List<OrderItem>(); 17    System.Data.DataTable dtItems = dal.GetData("orderItems", orderId); 18    foreach (System.Data.DataRow r in dtItems.Rows) { 19      OrderItem item = new OrderItem((int)r["product_id"], orderId); 20      item.Name = (string)r["name"]; 21      item.Description = (string)r["description"]; 22      item.Quantity = (int)r["quantity"]; 23      item.UnitPrice = (double)r["unit_price"]; 24      items.Add(item); 25    } 26    order.Items = items; 27 28    //get customer details 29    System.Data.DataTable dtCustomer = dal.GetData("customer", customerId); 30    Customer cust = new Customer(customerId); 31    cust.Name = (string)dtCustomer.Rows[0]["name"]; 32    order.Customer = cust; 33 34    return order; 35  }

Opportunities for method extraction inside this one method are numerous. You might consider extracting the call to initialize the Order object, the call to get order items, and the call to get customer details. Doing so will result in better organized code (thus, more readable), more opportunities for reuse, and an easier-to-maintain code base. Let's look at doing these extractions.

First, you'll extract the call that sets up the order. Knowing what to select for extraction requires a bit of experience with the tool. In this case, extract lines 313. This takes the code from the DataAccess setup through the order initialization. However, doing so confuses the Extract Method operation somewhat because you are setting up both a DataAccess object and an Order object in the first two operations. The Extract Method understands you need these two objects further in your method. Therefore, it will create both objects as out parameters of the method. What you want is the method to return an instance of the Order object and set up its own DataAccess object. You can accomplish this with the following steps:

1.

Select lines 413 (order creation through initialization).

2.

Select the Extract Method refactor operation (menu, right-click, or keyboard chord).

3.

Visual Studio will then present the Extract Method dialog box, as shown in Figure 8.11. This dialog box presents the new method name (NewMethod by default) and the method signature. If the method signature does not look right, you can cancel the operation and refine your code selection. In this case, the method is static; returns an Order object; and takes customerId, orderId, and DataAccess objects. We do not want the latter in our function signature but will deal with this momentarily.

Figure 8.11. Extracting code to a method to initialize the order.


4.

Rename the method to something meaningful. In this case, rename it to InitCustomerOrder.

5.

Click the OK button to allow the method to be extracted.

6.

The new method is created, and the old method is replaced by the following call:

Order order = InitCustomerOrder(customerId, orderId, dal);




Note

Extracted methods are created as private by default.


You still have one issue with the extracted method: It takes an instance of DataAccess when you would prefer that it created its own instance. Fortunately, you can use another refactoring operation to deal with this issue. In this case, use the Remove Parameters refactor. This refactoring operation is covered later in this chapter. It is important to point out that removing the parameter results in removing it from both the method signature and the call to the method. It does not, however, put the call to create that DataAccess object inside the new method (nor does it remove it from the old method). You must take these two steps manually.

Next, let's extract the call to get order items. Begin by selecting lines 1625 (see Listing 8.1). Note that we do not want to select the call to set the order's property (line 26); we simply want to return an object that represents all line items for a given order. Figure 8.12 shows the selection and method extraction. In this case, name the new method GetOrderItems. After the method is extracted, it is replaced by the following call to the new method:

Figure 8.12. Extracting code to a method to return order items.


List<OrderItem> items = GetOrderItems(orderId, dal);


Again you have the issue with the DataAccess object being passed into the new method. You solve this issue in the same manner as you did previously.

Finally, let's look at extracting the portion of the method that gets the customer details. By now, this procedure should be reasonably straightforward. You select the code (lines 2931) and choose the Extract Method operation. You name the new method GetCustomer and deal with the extracted DataAccess parameter.

The newly organized (and much shorter) method looks like Listing 8.2. In addition, you now have three new tight, discrete methods that you may be able to reuse in the future (and perhaps make public). These new methods can be found in Listing 8.3.

Listing 8.2. The Static Method After the Extractions

public static Order GetCustomerOrder(int customerId, int orderId) {   Order order = InitCustomerOrder(customerId, orderId);   //get order items   List<OrderItem> items = GetOrderItems(orderId);   order.Items = items;   //get customer details   Customer cust = GetCustomer(customerId);   order.Customer = cust;   return order; }

Listing 8.3. The Extractions

private static Customer NewMethod(int customerId) {   DataAccess.DAL dal = new DataAccess.DAL();   System.Data.DataTable dtCustomer = dal.GetData("customer", customerId);   Customer cust = new Customer(customerId);   cust.Name = (string)dtCustomer.Rows[0]["name"];   return cust; } private static List<OrderItem> GetOrderItems(int orderId) {   List<OrderItem> items = new List<OrderItem>();   DataAccess.DAL dal = new DataAccess.DAL();   System.Data.DataTable dtItems = dal.GetData("orderItems", orderId);   foreach (System.Data.DataRow r in dtItems.Rows) {     OrderItem item = new OrderItem((int)r["product_id"], orderId);     item.Name = (string)r["name"];     item.Description = (string)r["description"];     item.Quantity = (int)r["quantity"];     item.UnitPrice = (double)r["unit_price"];     items.Add(item);   }   return items; } private static Order InitCustomerOrder(int customerId, int orderId) {   Order order = new Order();   //get order details   DataAccess.DAL dal = new DataAccess.DAL();   System.Data.DataTable dtOrder = dal.GetData("customerOrder", orderId);   //validate order against customer   if (customerId != (int)dtOrder.Rows[0]["customer_id"]) {     throw new ApplicationException("Invalid order for the given customer.");   }   order.Id = (string)dtOrder.Rows[0]["id"];   return order; }

Note

Extract Method does not allow you to choose where to put the extracted method. Many times you might find a bit of code that really needs to be extracted into a method of another, different class. For this, you have to extract the method and then move things around manually.


Extracting a Single Line of Code

Sometimes, you might want to extract a single line of code or a portion of a line of code as its own method. For example, you may have a calculation that is done as part of a line of code but is common enough to warrant its own method. Alternatively, you might need to extract an object assignment to add additional logic to it. In either case, the C# code editor supports this type of extraction.

Let's look at an example. Suppose you have the following line of code that calculates an order's total inside a loop through the order items list:

total = total + item.Quantity * item.UnitPrice;


You may want to extract the portion of the assignment that calculates a line item's total (quantity * unit price). To do so, you simply select the portion of code and invoke the Extract Method refactor. Figure 8.13 shows this operation in action.

Figure 8.13. Extracting a portion of a line of code.


Notice that, by default, the new method would like an instance of OrderItem. You may prefer to pass both quantity and unit price instead. You would have to make this change manually. You could do so by creating the variables in the new method and doing a Promote Local to Parameter refactor (covered later in this chapter). Alternatively, if quantity and unit price were assigned to variables prior to doing the extraction, you would get a new method that accepted these parameters (instead of an OrderItem instance). Figure 8.14 demonstrates this fact.

Figure 8.14. An alternative extraction of a portion of a line of code.


The resulting refactor replaces a portion of the line of code with the following:

total = total + GetItemTotal(qty, unitPrice);


It also adds the new method as follows:

private static double GetItemTotal(int qty, double unitPrice) {   return qty * unitPrice; }


Generate Method Stub

You can get Visual Studio to automatically generate a method stub for you. This is not strictly a refactoring operation but can provide some similar increases in productivity. The scenario where this is applicable is as follows. Suppose you are writing code that calls a method off one of your objects. However, that method does not exist. You can still write code to make the call to the nonexistent method. Visual Studio will then recognize that this method does not exist and provide you with a smart tag (see Figure 8.15) to create the method.

Figure 8.15. Generate a method stub for a nonexistent method.


Clicking on the smart tag will result in Visual Studio extracting the method call to a newly generated method in the target assembly and class. Figure 8.16 shows the new method. Note that Visual Studio even provided a readable name for the method's parameter. This name was based on the variable inside the calling method.

Figure 8.16. The generated method stub.





Microsoft Visual Studio 2005 Unleashed
Microsoft Visual Studio 2005 Unleashed
ISBN: 0672328194
EAN: 2147483647
Year: 2006
Pages: 195

flylib.com © 2008-2017.
If you may any questions please contact us: flylib@qtcs.net