|Delphi Clinic||C++Builder Gate||Training & Consultancy||Delphi Notes Weblog||Dr.Bob's Webshop|
Refactoring with Borland Delphi 2005
In this article, I will demonstrate refactoring in the Delphi 2005 IDE, using an existing demo project as starting point, turning it into a more flexible and powerful application using different refactoring techniques.
When you apply refactoring you are adding (more) structure to your code, making it clearer to understand, easier to reuse and better maintainable. However, as an important rule of thumb for refactoring, you do all this without adding new functionality to your application. The behavior of the application or piece of code should remain the same.
Borland Delphi 2005 Refactoring support includes a number of very helpful new features, from extracting methods to declaring new variables or fields, extracting resource strings, renaming identifiers and refining the namespace and uses clauses.
With Refactor - Rename you can rename symbols (like fields, methods, properties, variables, etc.). You can view all references before refactoring (to verify all places where the rename will be made, and can remove places if you wish).
While writing source code, it may happen that you use variables before you declare them. The Borland Delphi 2005 Refactoring allows you to automatically declare these variables using Refactor - Declare Variable, offering you a dialog to enter the specifics, and adding the variable declaration to the current scope.
This feature allows you to focus on the code and algorithm logic, without having to manually navigate to the beginning of the scope to add a variable declaration.
Similar to declaring undeclared variables, Delphi 2005 offers the ability to declare class fields using Refactor - Declare Field.
This feature greatly reduces time to extend your classes with fields while writing your source code, without forcing you to return to your class declaration and add the field definition manually.
Delphi 2005 allows you to select a portion of source code, and refactor it by turning the code into a method, extracting the selected source code. The refactored method will automatically get a parameter list as well as local variable declarations, and the original section of source code will be replaced by a call to the newly refactored method. Breaking long sections of code into methods increases maintainability and reusability.
Extract Resource String
There's nothing harder to localize than a portion of source code that uses hardcoded quoted strings inside. Delphi 2005 now allows you to extract these quoted strings and replace them with resource strings (adding the resource string declarations to the implementation section of your code).
Find Unit / Import Namespace
Sometimes you use classes, methods, fields or types that are defined in another unit's namespace. In order to add the corresponding namespace to the uses (for Borland Delphi) or using (for C#) clause, Borland Delphi 2005 Refactoring offers the ability to automatically import the required namespace for a selected identifier, using Refactor - Find Unit or Import Namespace.
The six refactoring techniques can be divided in two groups: those who help you write new code (like declare variable, declare field and find unit / import namespace), and those who help you restructure existing code (rename, extract method, extract resource string). We will now use an existing Delphi 2005 demo application to demonstrate the refactoring features in practice.
Start Delphi 2005, click on the Open Project button or do File | Open Project, then go to the BDS\3.0\Demos\Delphi.NET\VCL\SimplePuzzle directory and open the Puzzle.bdsproj project file. This is an example Delphi puzzle, written by one of the Borland employees, and offers a nice puzzle game. Compile and run it, and I'm sure you will recognize the puzzle (see below for an example).
However, if you take a look at the source code for the first time, and want to add some features (like the ability to change the number of puzzle pieces), then you may first have to study the source code. Not that it's written in a bad way, but it's just not code written by you (or me for that matter), which is always harder to read than your own code. Which is exactly where Refactoring can help you.
First of all, in the class definition of TWinForm1 inside the WinForm1.pas unit, I see the following public section with the definition of size, x1, y1 and mylabels:
public size:integer; x1,y1:integer; mylabels:TList;If you look further in the source code, it appears that x1 and y1 are the X and Y location of the free spot on the game board. In order to make sure I understand that from now on, let's rename x1 to FreeSpotX and y1 to FreeSpotY (feel free to use your own names that make sense, of course).
Note the option to view the references before refactoring. These references will be shown in a special Refactorings pane at the bottom of the Code Editor:
As you can see, renaming the field x1 to FreeSpotX is not done in only one location, but in fact throughout the entire unit. In fact, Refactoring makes sure the walk through the entire project to find all occurrences of "x1" to rename to FreeSpotX. This really beats a global search and replace!
Click on the Refactorings Cube on the toolbar to actually apply the refactoring actions. Then, do the same thing with "y1", renaming that field to FreeSpotY.
Time to scroll a bit down in the source code of the Puzzle project. In the LabelMouseDown method, there's a section of code from line 130 until line 176. Not too long, but it contains four subsections that look almost similar, marked with a "right", "left", "top" and "bottom" comment.
Select lines 139 until 142, and prepare to extract these to a new method. Right-click and select the Refactoring Extract Method choice. This will present you with the Extract Method dialog, which can be seen below:
Note that the new method is called "ExtractedMethod" by default, but we should change that to something like "MoveRight", to indicate that this is the method that moves a square to the right (and the free spot to the left). Also note that one argument is passed: the "o" variable that we're using here. Obviously, you can use the Rename Refactoring later to rename the "o" in this local routine to something else, a bit more descriptive.
Click on the OK button to extract the selected code and generate the new method "MoveRight". As a result, the selected code in the LabelMouseDown routine will be replaced by a single line, with "MoveRight(o)" inside. Note that no semicolon follows this call, to avoid compilation errors.
Delphi 2005 Error Insight
However, speaking of compilation errors! By extracting the MoveRight method, we accidentally broke the application: it will no longer compile. The problem is caused by the fact that the new method has an argument of type TMyLabel. And unfortunately, in the interface section of the WebForm1.pas unit, the TMyLabel type definition comes after the TWinForm1 definition. You don't even have to compile the application to see the problem, since numerous red wave lines will be displayed in the Code Editor to alert you to the problem. A complete list of syntax errors is also displayed in the Structure Pane at the upper-left corner of the IDE.
Obviously, we should go to the top of this unit, and move the definition of the TMyLabel class before the definition of the TWinForm1 class. That will resolve all errors again.
Once this is done, you can extract the code for the "Left" move as well, if you want.
Extract Resource String
Apart from extracting methods, we can also extract resource strings. This is useful if you want to translate the application, and display a localized (error) message to the users. In this SimplePuzzle project, there is only one string that's a candidate for extraction as resource string: right at the bottom of the unit in the Verif method. The string is used to display a congratulation message once the puzzle is solved.
You can place your cursor inside the string, right-click and select the Refactoring - Extract Resource String option. This will present you with the following dialog.
You can change the name of the resourcestring, and once you press OK, the new resource string is created for you. All extracted resource strings will appear at the start of the implementation section of your unit.
Add Variable / Field
So far, we've only renamed fields, extracted methods and resource strings - things to do with source code without changing the actual behavior. There are however also Refactoring techniques that can be applied best when adding new features, or changing the behavior (although it's generally recommended not to add features while refactoring), and these include the Add Variable, Add Field and Find Unit / Import Namespace. Let's view these in action now.
The current puzzle, is a 4 by 4 game board, with 15 pieces and one empty spot. The numbers 4 and 15 are "magic" numbers that are hardcoded in the application. And I want to remove the fact that they are hardcoded, so we can change the layout of the gameboard as well as the number of pieces on it.
Let's start with the number 4 - which is currently both the maximum range in the horizontal and vertical dimension. In order to find all occurrences of "4", we can use the new Find in Files feature of Delphi 2005. Although we only have to search in one file, the Find in Files dialog has an option to display the results grouped by file in a nice treeview.
We can click on each node in the search results tree to be taken to the corresponding line of source code. Let's take a look at the first statement, which contains two occurrences of '4': one for the maximum horizontal, and one for the maximum vertical pieces. Replace the first 4 by the identifier MaxX. This will immediately allow Error Insight to kick in and mark the MaxX identifier with a red wavy line, to indicate that it hasn't been declared, yet. Something which we'll do right now using Refactoring. Right-click on the MaxX identifier, and select the Refactoring - Add Field option. This will give you the following dialog:
Here you can specify the type of the new field (Integer) as well as the visibility specifier (by default set to private). Click on OK to add the declaration to the WinForm1.TWinForm1 class. Note that after you've clicked on OK, you are still at the old location in the Code Editor. You are not taken to the place where the new field declaration has been added to the class. This is actually a very helpful feature, since it allows you to continue working on the source code, declaring new fields and/or new variables without losing your momentum while writing the code. Never do you need to scroll up to the nearest "var" section or the definition area of the class itself. This will allow me to focus on the code, while the Error Insight red wavy lines will alert me when I need to declare a new field or variable.
After we've changed the first 4 to MaxX, we can change the next 4 to MaxY, changing that source line to the following (where MaxY is underlined with a red wavy line, since it hasn't been declared yet):
Self.ClientSize := System.Drawing.Size.Create(MaxX*size,MaxY*size);Use Refactoring to create a new private integer field called MaxY to make sure the project compiles again.
procedure TWinForm1.TWinForm_Load(sender: System.Object; e: System.EventArgs); begin MaxX := 8; // originally 4 MaxY := 2; // originally 4 size:=64; init(); end;If you compile and run the application, you can see that we changed the puzzle to an 8x2 format now. Do not try to change MaxX and MaxY to other values just yet, since the above example only worked because 8 * 2 is equal to 4 * 4, resulting in a gameboard with 16 places. And there are still some hardcoded 15's in the project. Search for all occurrences of 15, and replace these with the expression (MaxX * MaxY - 1). This will make sure that no matter which value for MaxX and MaxY is used, the gameboard will be correctly calculated.
I leave it as exercise for the reader to expose the MaxX and MaxY fields through a pop-up menu choice to allow the player to select the size of the gameboard before the game starts. After all, this was just an example application, albeit a very nice one at that.
In this article I've explained what Refactoring is, and which Refactoring features are available in Delphi 2005. I have then demonstrated the Rename, Extract Method and Extract Resource String refactoring features using the SimplePuzzle example application. We've restructured the source code without changing the actual behavior of the application.
I then decided to add the functionality to change the gameboard dimensions, adding the MaxX and MaxY fields to the WinForm, and replacing some hardcoded magic numbers by these new fields, using the Refactoring New Field feature.
Refactoring is supported in Delphi for Win32 as well as Delphi for .NET applications. For C# projects, only Rename, Extract Method and Import Namespace are available. The Add New Field and Add New Variable are not available, since you can declare variables almost anywhere in C# code (there is no need to move to a special "var" section). There is also no Extract Resource String functionality in C#, simply because that doesn't exist in C#.
Apart from Refactoring, I've also briefly demonstrated some of the other new Delphi 2005 new features, including Error Insight, and Find in Files results.
For more information and demonstrations, please attend one of my Delphi 2005 Essentials sessions or training classes, or check out my Delphi 2005 courseware manuals.