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 9.1 provides an example of a method (GetFullInvoice
) that is unnecessarily long. 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 Invoice
object based on the ID number. However, the invoice and the line items are all retrieved from discrete database calls and stored in domain-specific objects. These objects are then stored on the Invoice
instance as properties.
Note
If you would like to follow along in your code editor, download the sample code from the website associated with this book: informit.com/title/9780162337369.
01 public class InvoiceDb
02 {
03 public static Invoice GetFullInvoice(int id)
04 {
05 //get invoice table data
06 DataTable dtOrder = DataAccess.GetTableData("Invoice", id);
07
08 //validate invoice against id
09 if (id != (int)dtOrder.Rows[0]["id"])
10 {
11 throw new ApplicationException("Invalid invoice.");
12 }
13
14 //create empty invoice object based on id
15 Invoice invoice = new Invoice(id);
16
17 //get invoice items
18 List<InvoiceLineItem> items = new List<InvoiceLineItem>();
19 DataTable invItems = DataAccess.GetTableData("InvoiceLineItems", id);
20 foreach (DataRow r in invItems.Rows)
21 {
22 InvoiceLineItem item = new InvoiceLineItem(
23 (string)r["name"], (string)r["description"],
24 (double)r["unit_price"], (Int16)r["quantity"]);
25 items.Add(item);
26 }
27 invoice.LineItems = items;
28 return invoice;
29 }
30 }
Opportunities for method extraction inside this one method are numerous. Obvious considerations are the code to initialize the Invoice
instance and the code to get invoice line items. Extracting these two chunks of code into discrete methods would 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 two extractions.
First, let’s extract the code that sets up the Invoice
instance. Knowing what to select for extraction requires a bit of experience with the tool. In this case, we will extract lines 05 through 15 (as numbered in the Listing 9.1), which is the code from the first call to DataAccess
through the Invoice
class initialization. Figure 9.14 shows the selected code and related light bulb menu to access the Extract Method refactor.
Invoking the Extract Method refactor creates a new method from your selected code along with the appropriate parameters and return type. It also replaces the extracted code with a reference call to this new method. Finally, Visual Studio invokes the Rename refactor so you can give your new method a name that makes sense. Figure 9.15 shows the extracted method, the call to the extracted method, and the results of renaming the new method inside the IDE.
Next, let’s extract the code that builds a list of invoice items and adds them to the Invoice
object. We begin by selecting the code represented by lines 17 through 26 in Listing 9.1. Note that we do not want to select the call to set the invoice’s LineItems
property (line 27); we simply want to return an object that represents all line items for a given invoice.
Figure 9.16 shows the method extraction. In this case, we name the new method GetInvoiceItems
. Notice that the method takes a parameter named id
. It would likely be helpful to rename this parameter invoiceId
.
The newly organized (and much shorter) original method looks like Listing 9.2. In addition, you now have two new tight, discrete methods that you may be able to reuse in the future (and perhaps make public). These new methods are in Listing 9.3.
public static Invoice GetFullInvoice(int id)
{
Invoice invoice = GetInvoice(id);
//get invoice items
List<InvoiceLineItem> items = GetInvoiceItems(id);
invoice.LineItems = items;
return invoice;
}
private static List<InvoiceLineItem> GetInvoiceItems(int invoiceId)
{
List<InvoiceLineItem> items = new List<InvoiceLineItem>();
DataTable invItems = DataAccess.GetTableData("InvoiceLineItems", invoiceId);
foreach (DataRow r in invItems.Rows)
{
InvoiceLineItem item = new InvoiceLineItem(
(string)r["name"], (string)r["description"],
(double)r["unit_price"], (Int16)r["quantity"]);
items.Add(item);
}
return items;
}
private static Invoice GetInvoice(int id)
{
//get invoice table data
DataTable dtOrder = DataAccess.GetTableData("Invoice", id);
//validate invoice against id
if (id != (int)dtOrder.Rows[0]["id"])
{
throw new ApplicationException("Invalid invoice.");
}
//create empty invoice object based on id
Invoice invoice = new Invoice(id);
return invoice;
}
Note
The 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.