Refactoring C# Code Using Visual Studio 2005
Andrew W. Troelsen, Microsoft MVP
Intertech Training
July 2004
Applies to:
Microsoft Visual Studio 2005
Microsoft Visual C# 2.0
Summary: This article examines the role of code refactoring, and the refactoring techniques supported by Visual Studio 2005. (23 printed pages)
Contents
Defining the Refactoring Process
Refactoring Support Under Visual Studio 2005
Extract Method
Encapsulate Field
Extract Interface
Reorder Parameters
Remove Parameters
Rename
Promote Local Variable to Parameter
Generate Method Stub
Summary
**Note **This article assumes you're familiar with the C# programming language and the .NET platform. Previous experience with refactoring is helpful, but not required, to enjoy the pages to follow.
Defining the Refactoring Process
I'd bet you would agree that a significant part of any engineer's day is spent re-working existing code to become in some way 'better.' In many cases, 'better code' is a subjective term, but some common improvements include: adhering to OO-best practices, increasing type safety, improving performance, and increasing code readability and maintainability.
Refactoring is a formal and mechanical process, used to modify existing code in such a way that it does indeed become 'better' while preserving the program's intended functionality. In addition to improving a program's overall design, the refactoring process tends to yield code which is far easier to maintain and extend in the long run.
In many cases, refactoring can be seen as obvious (almost simplistic) acts of programming common sense. For example, the Replace Magic Number with Symbolic Constant refactoring states that if your application has a well-understood value, don't just type the value verbatim (that is, a magic number); instead, create a named constant. For example, the following type.
class SimpleMath { public static double CalcCircumference(double diameter) { return 3.14 * diameter; } }
Could be refactored using Replace Magic Number with Symbolic Constant as so.
class SimpleMath { public const double PI = 3.14; public static double CalcCircumference (double diameter) { return PI * diameter; } }
I'd hope you'd agree that this modification is an improvement, given that:
- We have increased the readability of our code (the meaning of 'PI' is much clearer than the value 3.14).
- If we need to change the value of this symbolic constant (perhaps to account for greater precession of PI), we need only modify the constant's declaration (rather than each occurrence of the magic number).
Other refactorings are not so simplistic or obvious. For example, the Tease Apart Inheritance refactoring can be applied so as to dramatically reshape an existing class hierarchy into multiple (related) hierarchies. This single refactoring typically implies the use of related refactorings such as Extract Class, Move Method, Move Field, Pull Up Method, Pull Up Field, and so forth.
Again, given that refactoring applies a formal, mechanical process to the process (pardon the redundancy), we have a solid roadmap for the tasks at hand.
Where Do Refactorings Come From?
In the previous paragraphs, I made mention of some commonplace refactorings (Move Method, Move Field, and so forth). The funny thing about refactoring is that there actually is no universal list etched in stone. For example, "Replace Magic Number with Symbolic Constant" could just as easily been called "Magic Numbers are Really Bad...Use a Const or Enum."
The programming community as a whole, however, does tend to agree upon a set of well-known and time-tested refactoring techniques. Many of these recognized refactions are formalized in a book by Martin Fowler called Refactoring: Improving the Design of Existing Code (Addison Wesley, 1999). If you are new to the refactoring process, I would heartily recommend picking up a copy of this text, or at very least visiting the supporting website for further insights.
Refactoring Support Under Visual Studio 2005
In the bad old days, refactoring typically involved a ton of manual labor. Once developers identified the code to modify, they tended to make substantial use of copy, paste, find, replace, compile, test (repeat) techniques. This was time-intensive and error prone, to say the least.
Luckily, Visual Studio 2005 does a great deal to automate the refactoring process. Using the Refactoring menu, related keyboard shortcuts, SmartTags, and/or context-sensitive mouse right-clicks, you can dramatically reshape your code with minimal fuss and bother. Table 1 defines some common refactorings recognized by Visual Studio 2005:
Table 1. Visual Studio 2005 refactorings.
Refactoring Technique | Meaning in Life |
---|---|
Extract Method | This allows you to define a new method based on a selection of code statements. |
Encapsulate Field | Turns a public field into a private field encapsulated by a .NET property. |
Extract Interface | Defines a new interface type based on a set of existing type members. |
Reorder Parameters | Provides a way to reorder member arguments. |
Remove Parameters | As you would expect, this refactoring removes a given argument from the current list of parameters. |
Rename | This allows you to rename a code token (method name, field, local variable, and so on) throughout a project. |
Promote Local Variable to Parameter | Moves a local variable to the parameter set of the defining method. |
Let's see how these refactorings behave up-close and personal. As we move through each example, remember that most refactorings can be activated using several techniques (menu systems, context clicking, and so on). While I'll tend to favor context sensitive mouse right-clicks, I'll point out other options where appropriate.
Extract Method
The first refactoring we will examine is Extract Method. I am sure you have created a type member which just 'feels' like it is doing too much. Perhaps you have created a method which is responsible for obtaining a user name and password, the results of which are placed into local variables for later use.
using System; namespace RefactoringExamples { class Program { static void Main(string[] args) { Console.WriteLine("*** Please enter your credentials ***"); // Get user name and password. string userName; string passWord; Console.Write("Enter User Name: "); userName = Console.ReadLine(); Console.Write("Enter Password: "); passWord = Console.ReadLine(); } } }
While there is nothing terribly wrong with this Main() method, consider what would happen if other parts of the Program class need to prompt the user for the identical information. This would obviously entail a ton of redundant (and therefore difficult to maintain) code statements.
Ideally, you could refactor the Main() method in such a way that the common logic is scraped into a new method, named (for example) GetCredentials(). By doing so, any member of the Program type who needs to obtain the user name and password could simply invoke the new method. The code within the Main() method becomes much more readable, as well, in that related statements can be replaced by a descriptive method call.
Rather than manually creating the GetCredentials() method, the Extract Method refactoring of Visual Studio .NET will automatically generate the new method, and replace the selected code with a subsequent method invocation. The first step is to determine exactly which code statement(s) should represent the logic of the new method. Assume that we have selected the following statement set for Extract Method refactoring (Figure 1).
Figure 1. Selecting statements for the Extract Method refactoring.
At this point, you will be presented with a dialog box that allows you to enter the name of the new method to be generated (Figure 2). Notice that the new method is automatically defined as private and static, as the code statements did not refer to any instance level variables (generally speaking, extracted methods will be given the most restrictive modifiers possible).
Figure 2. Providing a name for the newly extracted method.
Once you click OK, you find the following update.
namespace RefactoringExamples { class Program { static void Main(string[] args) { Console.WriteLine("*** Please enter your name ***"); GetCredentials(); Console.ReadLine(); } private static void GetCredentials() { // Get user name and password. string userName; string passWord; Console.Write("Enter User Name: "); userName = Console.ReadLine(); Console.Write("Enter Password: "); passWord = Console.ReadLine(); } } }
In this revised form it is easy to see what Main() does at a glance, because there are only three lines and each line is very descriptive. If readers want to delve into the details behind the GetCredentials() method they are free to do so, but if they don't care to view the implementation logic, they no longer need to.
Note that this particular version of GetCredentials() has been extracted to take no arguments. The reason is due to the fact that the selected code was a self-contained unit, which means the variables (userName and passWord) that are required by the invoked methods were included in the selection, as well. If, however, we had only selected the following statements for the Extract Method refactoring,
Console.Write("Enter User Name: "); userName = Console.ReadLine(); Console.Write("Enter Password: "); passWord = Console.ReadLine();
the method prototype would now include two output parameters (Figure 3).
Figure 3. The method extracted by Visual Studio 2005 is contextually prototyped.
The resulting extraction is as so:
using System; namespace RefactoringExamples { class Program { static void Main(string[] args) { Console.WriteLine("*** Please enter your credentials ***"); // Get user name and password. string userName; string passWord; GetCredentials(out userName, out passWord); } private static void GetCredentials(out string userName, out string passWord) { Console.Write("Enter User Name: "); userName = Console.ReadLine(); Console.Write("Enter Password: "); passWord = Console.ReadLine(); } } }
Note If a block of selected statements entails a single return value, it will become the physical return value of the extracted method. If the set of selected statements demand multiple logical return values, however, the set of statments will become realized as reference or output parameters.
Encapsulate Field
Hopefully we are all in agreement that publicly accessible field data is a bad thing (readonly fields notwithstanding). The law of encapsulation begs us to define a type's field data as private, while providing safe and controlled access to the underlying value using .NET properties.
For the sake of argument, however, assume you have inherited the following structure created by a less seasoned engineer than yourself.
public struct MyPoint { public int x; public int y; public void SetLocation() { Console.Write("Enter X position: "); x = int.Parse(Console.ReadLine()); Console.Write("Enter Y position: "); y = int.Parse(Console.ReadLine()); } public void PrintLocation() { // Print new location. Console.WriteLine("[{0}, {1}]", x, y); } }
As you can see, the field data of the MyPoint type has been defined as public. To preserve encapsulation, we should refactor the code as so:
- Redefine the x and y fields as private.
- Create a read / write property for each piece of field data.
- Replace all previous field assignments with property set syntax.
- Replace all previous field access logic with property get syntax.
Again, while you could just pound out the changes by hand, Visual Studio 2005 automates the process using the Encapsulate Field refactoring. To illustrate, select the x field and activate the refactoring of interest (Figure 4).
Figure 4. Selecting a field for the Encapsulate Field refactoring.
Once you select the Encapsulate Field option, you are prompted to configure the scope of this refactoring. As seen in Figure 5, we will change the suggested property name to Xpos, and enable the All value of the Updated Reference option (this ensures that all occurrences with the selected field are accounted for during the refactoring process).
Figure 5. Configuring the new property.
If you have checked the 'Preview Reference Changes' checkbox, you will then be presented with a final dialog that documents the full results of the proposed change (Figure 6).
Figure 6. Review the scope of the current refactoring.
Here is the end result of this Encapsulate Field refactoring.
public struct MyPoint { private int x; public int XPos { get{return x;} set{x = value;} } public int y; public void SetLocation() { Console.Write("Enter X position: "); XPos = int.Parse(Console.ReadLine()); Console.Write("Enter Y position: "); y = int.Parse(Console.ReadLine()); } public void PrintLocation() { // Print new location. Console.WriteLine("[{0}, {1}]", XPos, y); } }
Of course, if you were to apply the same refactoring to the remaining public field (y), a similar result will be achieved.
Extract Interface
Once you become comfortable working with interface types, it is very hard not to leverage their usefulness whenever possible. In a nutshell, interfaces define a set of abstract members (properties, methods, and events) that a given type may support. If so, the implementing type fleshes out the details as it sees fit. The beauty of interfaces becomes clear when you understand that types in completely different hierarchies can implement the same interface. Given this, interfaces allow us to obtain polymorphism across hierarchies, namespaces, assemblies, and .NET programming languages.
The Extract Interface refactoring allows you to select a group of existing type members to yield a new interface abstraction. For example, let's say you are creating a graphics library. Your first type (TwoDShape) defines the following members.
// Assume you have referenced System.Drawing.dll // and updated your 'using' directives // to make use of the Rectangle and Color types. class TwoDShape { public void Draw() { /* Some interesting code. */ } public Rectangle GetBoundingRect() { /* More interesting code. */ } public Color GetArgb() { /* Even more interesting code. */ } }
At this point, it occurs to you that other types you intend to build (say, ThreeDShape and Printer) also require members named Draw(), GetBoundingRect() and GetArgb(). If you extract these members into a custom interface (which we will name IRender), you can achieve interface-based polymorphism.
To extract the IRender interface from the TwoDShape type, begin by selecting all three members (including their implementations) and active the Extract Interface refactoring. Once you do, you are presented with a dialog box that allows you to establish the interface name, defining file, and the interface members themselves (Figure 7).
Figure 7. Configuring the new interface type.
Note You can also extract an interface by simply highlighting the name of the class (or structure) and activating the Refactoring menu, rather than selecting a set of type members. This can is helpful when the members of the to-be-defined interface are at various locations within the source code file.
Once you click the OK button, you not only have a new file defining the interface itself,
namespace RefactoringExamples { interface IRender { void Draw(); System.Drawing.Color GetArbg(); System.Drawing.Rectangle GetBoundingRect(); } }
but you will be happy to see that the original class (TwoDShape) now implements the interface in question.
class TwoDShape : RefactoringExamples.IRender { ... }
At this point, you are ready to define the remaining class types (ThreeDShape and Printer) with support for IRender. To facilitate this process, consider Figure 8, which makes use of Visual Studio 2005 SmartTags.
Figure 8. Implementing interfaces using Visual Studio 2005 SmartTags.
As you can see from Figure 8, Visual Studio .NET provides SmartTags to implement a given interface. One major improvement from Visual Studio .NET 2003 is that you can now make use of explicit interface implementation. As you may know, this technique hides the interface members from an object level, thereby forcing the caller to obtain the interface reference directly.
Reorder Parameters
The Reorder Parameters refactoring is a bit more difficult to motivate than the examples we have just covered. As you would expect, this refactoring allows you to change the ordering of a member's arguments; but when you would need to do so? Assume you have created a type that contains an overloaded method, each of which make use of a System.Drawing.Graphics type.
class ImageRenderer { public void Render(Point topLeft, Point bottomRight, Graphics g) { /* interesting code */ } public void Render(Graphics g, int x, int y) { /* interesting code */ } public void Render(Graphics g, Rectangle boundingBox) { /* interesting code */ } }
Notice that the second and third Render() members have the Graphics type as their first parameter, while the first version places this argument dead last. While this C# code is syntactically correct, the asymmetric nature of this overloaded method may be confusing (or at very least annoying) to the object user.
Using the Reorder Parameters refactoring, you can quickly clean up the existing model. Begin by selecting the method containing the parameters you wish to modify, and activate the Reorder Parameter.
Like other Visual Studio 2005 refactoring options, you are next presented with a configuration dialog box. In this case, you are able move a particular argument up or down the chain (Figure 9):
Figure 9. Repositioning the System.Drawing.Graphics parameter.
We would find the following update.
class ImageRenderer { public void Render(Graphics g, Point topLeft, Point bottomRight) { /* interesting code */ } public void Render(Graphics g, int x, int y) { /* interesting code */ } public void Render(Graphics g, Rectangle boundingBox) { /* interesting code */ } }
Note The .NET design guidelines also suggest that you put arguments to overloads in a consistent order.
Remove Parameters
The Remove Parameters refactoring is quite self-explanatory: existing parameters are removed from a method's signature. To illustrate, assume you now wish to remove the second Point parameter from the Render() method.
At this point, you will (once again) be presented with a configuration dialog where you are able to select the parameters you wish to remove (Figure 10).
Figure 10. Deleting the parameter(s) in question.
As you would guess, when you choose to remove a parameter, it is especially important to review the result of this refactoring, given that this will affect each and every invocation of the method (Figure 11).
Figure 11. View the scope of the parameter deletion.
Rename
The Rename refactoring is also quite straightforward: Change the name of a namespace, type, member, or parameter to something new. One of the key benefits of the Rename refactoring is that it relieves the developer of having to pick the right name at first. To illustrate, you have to begin to make use of the ImageRenderer type, as so.
using System; using System.Collections.Generic; using System.Text; using System.Drawing; namespace RefactoringExamples { class ImageRenderer { public void Render(Graphics g, Point topLeft) { /* interesting code */ } public void Render(Graphics g, int x, int y) { /* interesting code */ } public void Render(Graphics g, Rectangle boundingBox) { /* interesting code */ } } class Program { static void Main(string[] args) { ImageRenderer imgObj = new ImageRenderer(); } } }
After a bit of thought, you decide that Renderer (rather than ImageRenderer) is a more fitting name for this type. Rather than having to make use of manual find/replace techniques, the Rename refactoring reliably renames all occurrences of the selected token through out the entire project (Figure 12).
Figure 12. Applying the Rename refactoring.
Promote Local Variable to Parameter
When you are constructing a method, you may find that a set of local variables would be better suited as arguments provided by the caller. For example, assume that you have authored a method which opens a specific *.txt file for parsing.
class Program { public static void ParseTextFile() { // Open file on local disk. string fileToParse = @"C:\myFile.txt"; StreamReader sr = File.OpenText(fileToParse); // Start parsing using StreamReader... } static void Main(string[] args) { ParseTextFile(); } }
Once you have implemented all of the necessary parsing logic, you decide that the ParseTextFile() method would be even more useful if you allowed the caller to pass in the local System.String as an argument. Enter the Promote Local Variable to Parameter refactoring (Figure 13).
Figure 13. Reshaping a method's parameters with a local variable.
Once you select this refactoring, you will find that the ParseTextFile() method has been updated to take a System.String as a parameter, while the existing invocations of this method have been reshaped to pass in the value of "C:\myFile.txt" (given this was the value of the original local variable).
class Program { public static void ParseTextFile(string fileToParse) { // Open file on local disk. StreamReader sr = File.OpenText(fileToParse); // Start parsing using StreamReader... } static void Main(string[] args) { ParseTextFile(@"C:\myFile.txt"); } }
Generate Method Stub
The final refactoring to examine is Generate Method Stub. This refactoring is useful when you are thinking faster than your hands can type. Assume you are typing away within your Main() method, anticipating you will eventually need to define a method which will display basic type information of a given variable. Although you have not yet defined a method named ObtainTypeInfo(), you press onward.
static void Main(string[] args) { Type t = typeof(Renderer); string typeInfo = ObtainTypeInfo(t); Console.WriteLine(typeInfo); }
At this point you can select the yet-to-be-generated ObtainTypeInfo() method and apply the Generate Method Stub refactoring (Figure 14).
Figure 14. Generating a stub code for an anticipated method.
Notice how the method stub will be defined in the context of the calling method; therefore ObtainTypeInfo() is defined as 'private' and 'static'.
private static string DisplayTypeInfo(Type t) { throw new NotImplementedException(); }
At this point, you can replace the throwing of a NotImplementedException type with the required implementation logic:
private static string ObtainTypeInfo(Type t) { string typeInfo = ""; typeInfo += string.Format("Base class: {0}", t.BaseType.Name); typeInfo += string.Format("\nFull name: {0}", t.FullName); typeInfo += string.Format("\nNumber of methods: {0}", t.GetMethods().Length); return typeInfo; }
Sweet! As you can see, Visual Studio 2005 offers a number of useful tools to reshape your existing code to become 'better.' As you have seen, the IDE provides a number of Menu options, keystrokes, and mouse-clicks, which make the process of refactoring code quite simple.
Summary
Refactoring is a process to improve upon the design of existing code, while preserving its intended functionality. As you have seen, Visual Studio 2005 provides support for a number of common refactorings. Using various techniques, you are able to reshape your code in a predictable manner.
Obviously there are many established refactorings which are not currently automated by Visual Studio 2005. Again, if you are interested in learning more (which I hope you are) check out this website, which supports the seminal book on the topic, Refactoring: Improving the Design of Existing Code (Addison Wesley, 1999).
On a related note, if you are interesting in obtaining refactoring plug-ins for Visual Studio .NET 2003, check out the following links:
About the author
Andrew Troelsen is a consultant and trainer with Intertech Training. Andrew is the author of numerous books, including the award winning C# and the .NET Platform Second Edition (Apress 2002). He also authors a monthly column for (of all places) MacTech, where he explores .NET development on Unix-based systems using the SSCLI, Portible.NET, and Mono CLI distributions.