Refactoring Exercises – PointOfSale++ and NotePad++
Introduction
Refactoring means changing the source code of an existing program without affecting its behavior. Assuming that the program has unit tests available, developers can check that the behavior was not changed by rerunning all the existing tests after they change the code. Visual Studio Professional 2008 provides limited support for refactoring C++ code. In this lab we will cover: rename field, rename class, extract method, and extract class etc.
1. Prefactoring exercises in PointOfSale++
Note for instructors: Provide students with Revision 45 from the PointOfSale++ repository.
Scenario: A developer is asked to implement “support multiple cashiers” in PointOfSale++. To prepare for actualization of this change request, during prefactoring the developer extracts a new class CashierRecord from Cashier. Perform the following tasks:
Task 1: Download and open the solution for PointOfSale++ provided by the instructor. Run all the existing tests and verify that all tests pass. How many tests are executed?
Answer: 47 tests are executed
Task 2: Extract a new class CashierRecord from Cashier, following the next steps:
Task 2.1 Move the following fields into CashierRecord together with the getters and setters.
int cashierId;
String^ password;
String^ firstName;
String^ lastName;
bool loggedIn;
Task 2.2 Create a new field CashierRecord^ cashierRecord in the class Cashier and setters and getters for it.
Task 2.3 Keep the methods isLoggedIn(), login (int id, String^ pw), and logout() in the class Cashier. Run all the tests. How many tests fail and why?
Task 2.4 Create a new unit test CashierRecordTest for the class CashierRecord. Move the following unit tests from CashierTest to CashierRecordTest: testGetCashierId, testGetPassword, testGetFirstName, and testGetLastName. In CashierRecordTest, rename the variable testCashier with testCr. Add the following code for the initialization of the CashierRecord tests:
[TestInitialize()]
void MyTestInitialize()
{
testCr = gcnew CashierRecord(1234,"password","John","Doe");
};
[TestCleanup()]
void MyTestCleanup()
{
testCr = nullptr;
};
Task 2.5 Change the code of Cashier and CashierRecord until all the tests pass.
Task 3: Rename the class Cashier into Cashiers. Name the files changed in response to this refactoring by following the next steps:
Task 3.1 Select the text Cashier in the definition of the class. Right click on the context menu and run Find All References tool.
Task 3.2 Click on every line in the Find All References Results window and replace the text Cashier with Cashiers (note: there might be lines with multiple occurrences).
Task 3.3 Compile the program and fix any compilation errors may occur.
Task 3.4 Run all the tests and make sure they all pass
Task 3.5 Using the Find All References tool rename the “cashier” field of the class Store into “cashiers”. Similar to Task 3.2 use Find Symbol Results to keep track of the lines you need to change.
Task 3.6 Compile the program and fix any compilation errors may occur.
Task 3.7 Run all the tests and make sure they all pass
Task 3.8 Using the Find in Files tool, search for the whole word “cashier”. The Find results window contains 3 #include “Cashier.h” lines. Rename the files Cashier.h to Cashiers.h and Cashier.cpp into Cashier.cpp, and fix the include lines identified.
Task 3.9 Compile the program and fix any compilation errors may occur.
Task 3.10 Run all the tests and make sure they all pass
Task 3.11 Rename the file CashierTest.cpp into CashiersTest.cpp, and the class from this file into CashiersTest.cpp
Task 3.12 Compile the program and fix any compilation errors may occur.
Task 3.13 Run all the tests and make sure they all pass
Answer. The files Cashier.h, Cashier.cpp, CashierTest.cpp, Store.h, Store.cpp and StoreForm.h will all be impacted by this refactoring.
2. Postfactoring exercises in NotePad++
In Notepad++, we implemented the change request “using Find in Files, Notepad++ users can see the full file paths of the files that contain the searched string. Allow the users to view also the size of the files in the results” We are asked to leave the code better than we found it.
Note for Instructors: Provide students with the solution from the end of Actualization in Notepad++ lab.
Task 4: Use Find Symbol tool, and rename the field _scintView to _finderView in Notepad++.
Steps to follow (Figure 1):
Task 4.1 Open Find Symbol tool window, search for _scintView in the entire solution, using the Whole word match. If only 1 match is found proceed to the following steps.
Task 4.2 select Replace in Files option.
Task 4.3 Fill in the “Find what:” and “Replace with:” boxes with the corresponding strings
Task 4.4 Check the Find options for Match case and Match whole word.
Task 4.5 Replace all occurrences.
Observation: Use the “Replace All” button with caution! The refactoring is correct only if a single match is found during step 4.1.
Figure 1. Using Find Symbol tool to rename a _scintView field
Task 5: Extract new methods in the Finder class to avoid code duplications.
In the Finder class, there are several lines of code that are repeated more than 5 times in different methods. These code duplications might cause problems during software maintenance, and make the code harder to understand. These lines are marked in yellow in the following code fragments. These two lines unlock and lock for writing the window that displays the search results, whenever new text needs to be added or inserted. 8 methods were repeating these 2 lines before actualization. We also added this duplication during the actualization lab.
class Finder : public DockingDlgInterface {
friend class FindReplaceDlg;
public:
...
void addSearchLine(const TCHAR *searchName) {
...
setFinderReadOnly(false);
_scintView.addGenericText(str.c_str());
setFinderReadOnly(true);
...
};
void addFileNameTitle(const TCHAR * fileName) {
...
setFinderReadOnly(false);
_scintView.addGenericText(str.c_str());
setFinderReadOnly(true);
...
};
void addFileHitCount(int count) {
...
setFinderReadOnly(false);
_scintView.insertGenericTextFrom(_lastFileHeaderPos, text);
setFinderReadOnly(true);
...
};
...
};
Task 5.1 Extract 3 new methods, more general, for inserting text, inserting a new line, and deleting text.
void addNewLine(const TCHAR * text){
setFinderReadOnly(false);
_scintView.addGenericText(text);
setFinderReadOnly(true);
};
void insertText(const int position, const TCHAR * text){
setFinderReadOnly(false);
_scintView.insertGenericTextFrom(position, text);
setFinderReadOnly(true);
};
void deleteText(const int start, const int end) {
_scintView.execute(SCI_SETSEL, start, end);
setFinderReadOnly(false);
_scintView.execute(SCI_CLEAR);
setFinderReadOnly(true);
};
Task 5.2 We need to call these new methods in the code and replace the duplications. The green commented code can be deleted and was preserved for clarity only.
void addSearchLine(const TCHAR *searchName) {
generic_string str = TEXT("Search \"");
str += searchName;
str += TEXT("\"\r\n");
addNewLine(str.c_str());
//setFinderReadOnly(false);
//_scintView.addGenericText(str.c_str());
//setFinderReadOnly(true);
_lastSearchHeaderPos = _scintView.execute(SCI_GETCURRENTPOS) - 2;
_pMainFoundInfos->push_back(EmptyFoundInfo);
_pMainMarkings->push_back(EmptySearchResultMarking);
};
void addFileNameTitle(const TCHAR * fileName) {
generic_string str = TEXT(" ");
str += fileName;
str += TEXT("\r\n");
addNewLine(str.c_str());
//setFinderReadOnly(false);
//_scintView.addGenericText(str.c_str());
//setFinderReadOnly(true);
_lastFileHeaderPos = _scintView.execute(SCI_GETCURRENTPOS) - 2;
_pMainFoundInfos->push_back(EmptyFoundInfo);
_pMainMarkings->push_back(EmptySearchResultMarking);
};
void addFileHitCount(int count) {
TCHAR text[20];
wsprintf(text, TEXT(" (%i hits)"), count);
insertText(_lastFileHeaderPos, text);
//setFinderReadOnly(false);
//_scintView.insertGenericTextFrom(_lastFileHeaderPos, text);
//setFinderReadOnly(true);
nFoundFiles++;
};
void addFileLength(const int docLength) {
TCHAR text[50];
wsprintf(text, TEXT(" ( file length %i)"), docLength);
insertText(_lastFileHeaderPos, text);
//setFinderReadOnly(false);
//_scintView.insertGenericTextFrom(_lastFileHeaderPos, text);
//setFinderReadOnly(true);
};
void addSearchHitCount(int count) {
TCHAR text[50];
wsprintf(text, TEXT(" (%i hits in %i files)"), count, nFoundFiles);
insertText(_lastSearchHeaderPos, text);
//setFinderReadOnly(false);
//_scintView.insertGenericTextFrom(_lastSearchHeaderPos, text);
//setFinderReadOnly(true);
};