Refactoring in Code


As stated earlier, we'll demonstrate the majority of the refactoring types in code. In each of the following cases, we'll be using C# code.

Rename

Renaming is one of the simplest refactorings, and perhaps the most obvious in terms of the result. It's like a global search-and-replace of your sources files that enables you to change the name of a namespace, type, member, or parameter wherever it is used.

Remember the IStockDeal interface that was used to demonstrate the Extract Interface refactoring? Suppose you don't like the "I" prefix on interface names, even though it's a Microsoft recommended standard. How could you change the name of that interface to StockDeal in every place that it occurs?

Just open the IStockDeal.cs source file in the code editor, as shown in the following code, right-click the word IStockDeal in the interface definition, and choose Refactor image from book Rename.

     interface IStockDeal     {           int NumberOfShares { get; set; }           String StockSymbol { get; set; }     } 

The Rename dialog box will appear, as shown in Figure 11-4, so that you can specify the new name of the interface. Note that in the figure, the checkbox Preview Reference Changes has been checked.

image from book
Figure 11-4

Upon pressing the OK button, the Preview Changes—Rename dialog box (shown in Figure 11-5) appears, enabling you to preview the changes that will be made in the source code. If you don't want to review the changes and instead simply want to apply them, you would uncheck the Preview Reference Changes checkbox on the previous Rename dialog.

image from book
Figure 11-5

The upper portion of this dialog shows that, besides the change to the interface definition itself, there will also be changes to the StockPurchase and StockSale classes that implement the interface. You needn't worry if you get the message "Your project or one of its dependencies does not build. References may not be updated" as shown in the figure. We're only building a set of examples to try out the techniques, not building a functional application that compiles and runs.

Note

Note that following this refactoring, the interface IStockDeal will be changed to StockDeal throughout your source files, but the name of the source file, IStockDeal.cs, will not change.

You can accept the proposed changes by clicking the Apply button, meaning you are accepting all of the changes you have reviewed — not only the one currently on display.

Before showing the effect in the source code, we suggest one more renaming. Changing the name of StockSymbol to StockIdentifier will serve to demonstrate that this refactoring type may be applied to members as well as types.

The final result of both renamings will be as shown in the following example. We list only the StockSale.cs source file as an example, but rest assured that similar changes will have been made in the StockPurchase.cs file:

     public class StockSale : Refactoring.StockDeal, Refactoring.IAudit     {           #region IStockDeal Members           public int NumberOfShares           {             get             {                throw new Exception("The method or operation is not implemented.");             }             set             {                throw new Exception("The method or operation is not implemented.");             }           }           public String StockIdentifier             {              get              {                 throw new Exception("The method or operation is not implemented.");              }              set              {                 throw new Exception("The method or operation is not implemented.");              }           }       #endregion       #region IAudit Members       public void audit()       {          throw new Exception("The method or operation is not implemented.");       } #endregion       } 

You'll be interested to know that you can also perform this same refactoring from Class Designer. Just right-click the type or member on a Class Diagram and choose Refactor image from book Rename.

Note

We demonstrated another use of Rename refactoring in Chapter 5, as a technique for renaming the participant classes of a pattern when applying that pattern.

Encapsulate field

Suppose another developer coded the StockPurchase class, helpfully extracted an IStockDeal interface, and provided this to you as an aid to developing your StockSale class. Without his original implementation, there would be no reason for you not to implement the interface in a slightly different way — for example, by returning the value of a mNumberOfSharesSold field directly via the NumberOfShares property that you have to implement in order to comply with the interface.

The code for your StockSale class would look like this:

       public class StockSale : Refactoring.IStockDeal, Refactoring.IAudit       {     public int NumberOfShares     {         get         {         return mNumberOfSharesSold;         }         set         {            mNumberOfSharesSold = value;         }     }     ...     private int mNumberOfSharesSold;     private String mStockSymbol;       } 

Note

There is nothing wrong with this code in terms of complying with the contract defined by the interface.

In a subsequent code review, the original developer points out — constructively, of course — that in his implementation of the StockPurchase class, there is a specifically named NumberOfSharesBought property to return the value of the private mNumberOfSharesBought field. His NumberOfShares property (required by the interface) piggybacks the aforementioned property in order to return the NumberOfSharesBought as NumberOfShares.

You think this is a great idea in terms of encapsulation, so you decide to refactor your code accordingly. You would do this by right-clicking your mNumberOfSharesSold field — in the code editor or on a Class diagram — and choosing Refactor image from book Encapsulate Field (see Figure 11-6).

image from book
Figure 11-6

The intention is to encapsulate the mNumberOfSharesSold field with a NumberOfSharesSold property, to update all references to the field so that they now use the property, and to preview the changes that will be made before accepting them.

Note

Not only will references be updated automatically within your project, but also across all referenced projects of the same language. If you have a Windows application that references a class library, for example, and you refactor that class library, then references to the refactored items in the class library will be corrected automatically in the Windows application.

Figure 11-7 shows a preview of one of those changes.

image from book
Figure 11-7

The code becomes the following:

     public class StockSale : Refactoring.IStockDeal, Refactoring.IAudit     {         public int NumberOfShares          {           get          {            return NumberOfSharesSold;          }           set          {            NumberOfSharesSold = value;          }         }         ...         private int mNumberOfSharesSold;         public int NumberOfSharesSold          {           get          {            return mNumberOfSharesSold;          }           set           {            mNumberOfSharesSold = value;           }       }     } 

Extract Method

In Chapter 2, we devised a Web service named DealingService, with two web methods: buyStock and sellStock. Though you need not have read that chapter, nor be already familiar with the DealingService class, we have reproduced it here as an ideal example of a class crying out for the Extract Method refactoring:

     public class DealingService     {         /// <summary>         /// This operation allows a stock to be bought.         /// </summary>         /// <param name="stockSymbol">The unique identifier for the stock.</param>         /// <param name="numberOfShares">The number of shares to be bought.</param>         /// <returns>Returns the number of shares bought.</returns>         [System.Web.Services.WebMethod(Description = ""),           System.Web.Services.Protocols.SoapDocumentMethod(Binding = "DealingService")]         public short buyStock(string stockSymbol, short numberOfShares)         {           System.Random rnd = new System.Random(numberOfShares);           if (rnd.NextDouble() > 0.5) return numberOfShares;           else return 0;         }         /// <summary>         /// This operation allows a stock to be sold.         /// </summary>         /// <param name="stockSymbol">The unique identifier for the stock.</param>         /// <param name="numberOfShares">The number of shares to be sold.</param>         /// <returns>Returns the number of shares sold.</returns>         [System.Web.Services.WebMethod(Description = ""),           System.Web.Services.Protocols.SoapDocumentMethod(Binding = "DealingService")]         public short sellStock(string stockSymbol, short numberOfShares)         {           System.Random rnd = new System.Random(numberOfShares);           if (rnd.NextDouble() > 0.5) return numberOfShares;           else return 0;         }     } 

In both web methods, we simulate the action of buying or selling a stock by either of the following:

  • Returning the number of shares transacted, if we deem the deal to have succeeded

  • Returning 0 as the number of shares transacted if we deem the deal to have failed

In our simulation, one of those results is chosen according to whether or not a randomly generated number is greater than 0.5.

You will notice that exactly the same code is used in each of the two methods, so to change the probability of a positive result in this simulation, you would need to change the value 0.5 in two places — unless, of course, you factored out the common functionality into a new method (let's call it calculateNumberOfShares) to be invoked from within each web method in place of the original code.

That's exactly what the Extract Method refactoring type enables you to do. To perform that refactoring, you would select one of the segments of code that is highlighted in the preceding code listing. For the sake of argument, we'll use the buyStock method. Right-click and choose Refactor image from book Extract Method.

The Extract Method dialog (see Figure 11-8) enables you to specify a name for the extracted method, private static, by default because no instance-level variables were included in the code selected.

image from book
Figure 11-8

Upon accepting that, you would see the result in code as follows:

     public short buyStock(string stockSymbol, short numberOfShares)     {       return calculateNumberOfShares(numberOfShares);     }     private static short calculateNumberOfShares(short numberOfShares)     {       System.Random rnd = new System.Random(numberOfShares);       if (rnd.NextDouble() > 0.5) return numberOfShares;       else return 0;     } 

Notice that the selected code is now encapsulated wholly within the calculateNumberOfShares method, and that an invocation of the new method takes the place of the selected code in the buyStock method. Notice also that the numberOfShares variable, on which the result depends, has been included as a parameter of the extracted method.

The sellStock method will not have changed, but obviously it would now make sense to change its implementation to match that of the buyStock method, like this:

     public short sellStock(string stockSymbol, short numberOfShares)     {       return calculateNumberOfShares(numberOfShares);     } 

Now, with the simulated result calculated in just one place, you could affect the probability of successful deals by changing this one line of code:

     if (rnd.NextDouble() > 0.9) return numberOfShares; 

Obviously, the more places that identical repeated code occurs, the more benefit you will gain from the Extract Method.

Promote local variable to parameter

Often, when coding a method initially, you hard-code values that you later decide should not be hardcoded at all, but rather should be parameters to the method.

Suppose you devise an audit method that writes audit trail information to a log file when invoked. Initially, you might code the method as follows:

     public void audit()     {       string logFileName = @"C:\auditLog.txt";       System.IO.StreamWriter writer = System.IO.File.OpenWrite(logFileName);       // write the audit information       // close the file     } 

Subsequently, you decide that the location of the log file should not be fixed within the method, but should instead be passed in as a parameter so that the location of the logged information may be varied.

To trigger the required refactoring, you can right-click the logFileName variable in the code editor and choose Refactor image from book Promote Local Variable to Parameter. A dialog box will appear warning you that this will result in cascading updates, and the result will show up in code as follows:

     public void audit(string logFileName)     {       System.IO.StreamWriter writer = System.IO.File.OpenWrite(logFileName);       // write the audit information       // close the file     } 

Note how the method now takes a parameter string logFileName, and how that parameter is used to open the file.

If you try out this example using the audit method of the IAudit interface (described earlier) you will see that, as warned, the change is propagated (cascaded) through the implementation hierarchy. For example, you could insert your code into the audit method implementation within the StockPurchase class, and after refactoring you could examine the StockSale class — and the IAudit interface itself — to see the new method signature applied there automatically.

Reorder parameters

This refactoring does exactly what it says on the box — it reorders the parameters of a method. As an example, take a look at the following three overloaded methods that might appear on a Customer class in a stockbroking application:

     public IStockDeal[] getDeals(String stockSymbol)     {         // return an array of customer deals for this stockSymbol.     }     public IStockDeal[] getDeals(String stockSymbol, DateTime dateLimit)     {         // return an array of customer deals for this stockSymbol         // that occurred before the specified date.     }     public IStockDeal[] getDeals(int qtyLowerLimit, String stockSymbol)     {         // return an array of customer deals for this stockSymbol         // where more than qtyLowerLimit shares were traded.     } 

Though syntactically correct, there is clearly an aesthetic problem. Wouldn't you prefer the stockSymbol to be the first parameter in the last method, to match the other two? Not only that, but imagine the confusion if each method took two parameters of the same type — for example, both strings — and yet one of the methods had them arbitrarily switched around.

To resolve such problems, you can right-click the last getDeals method and choose Refactor image from book Reorder Parameters. In the Reorder Parameters dialog (shown in Figure 11-9), you can then move parameters up and down in the list to achieve the desired ordering.

image from book
Figure 11-9

Note

For any refactoring that changes a method's signature, it's wise to preview the changes because refactoring will affect all invocations of that method. So we checked the box in Figure 11-9, which triggers the Preview Changes dialog box to appear before you see the result in code.

The result in code, which you will have anticipated, is as follows:

     public IStockDeal[] getDeals(String stockSymbol)     {         // return an array of customer deals for this stockSymbol.     }     public IStockDeal[] getDeals(String stockSymbol, DateTime dateLimit)     {         // return an array of customer deals for this stockSymbol         // that occurred before the specified date.     }     public IStockDeal[] getDeals(String stockSymbol, int qtyLowerLimit)     {         // return an array of customer deals for this stockSymbol         // where more than qtyLowerLimit shares were traded.     } 

Although we have demonstrated this refactoring type when applied to a method, be aware that the same refactoring may be applied to indexers, constructors, and delegates.

Remove parameters

Suppose you decide that each of the getDeals methods described earlier should not take in a stockSymbol at all. They should simply return a list of either all deals, deals prior to a certain date, or deals over a certain size, regardless of the particular stock.

In that case, you could right-click each method in turn and choose Refactor image from book Remove Parameters. The Remove Parameters dialog is shown in Figure 11-10.

image from book
Figure 11-10

This refactoring type can be used to modify indexers, constructors, and delegates as well as methods; and in the case of methods, it's possible to trigger the refactoring from a method invocation, rather than the method declaration as just demonstrated.

Generate Method Stub

Generate Method Stub is not really a refactoring in the sense of code restructuring applied retrospectively, but we include it here as a bridge between the retrospective code improvement techniques (refactoring) described above and the proactive code improvement techniques (code snippets) discussed in the next section.

So how does it help you? Consider the code for the audit method described earlier. As you write that method, you might decide to insert comments to show where additional code should be added:

     public void audit(string logFileName)     {         System.IO.StreamWriter writer = System.IO.File.OpenWrite(logFileName);         // write the audit information         // close the file     } 

Alternatively, you might go ahead and insert the required code right away, or — with your best design hat on — decide that those pieces of functionality should be factored out into separate methods. Without much thinking, you code the method like this:

     public void audit(string logFileName)     {         System.IO.StreamWriter writer = System.IO.File.OpenWrite(logFileName);         writeToFile(writer, textToWrite);         closeFile(writer);     } 

Of course, those methods do not yet exist, which is where Generate Method Stub comes in. Just rightclick the writeToFile method invocation and choose Generate Method Stub. Then do the same for the closeFile method invocation.

Your reward will be just enough code to make it compilable:

     public void audit()     {         string logFileName = "C:\auditLog.txt";         System.IO.StreamWriter writer = System.IO.File.OpenWrite(logFileName);         writeToFile(writer, textToWrite);         closeFile(writer);     }     private void writeToFile(System.IO.StreamWriter writer, object textToWrite)     {         throw new Exception("The method or operation is not implemented.");     }     private void closeFile(System.IO.StreamWriter writer)     {         throw new Exception("The method or operation is not implemented.");     } 

Of course, you would now need to complete the implementations of those methods.



Professional Visual Studio 2005 Team System
Professional Visual Studio 2005 Team System (Programmer to Programmer)
ISBN: 0764584367
EAN: 2147483647
Year: N/A
Pages: 220

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