preface
This article picks up where it left off when do YOU need to refactor code? , I organized all the techniques in “Reconstruction” into an article, convenient for myself and others to quickly find, because of the length of the problem, the techniques did not add sample code, if you can insist on reading, equivalent to you re-read the essence of the book. 😄
Refining function
The operation practice
- Create a new function and name it according to its intent.
- Copy the code to be refined from the source function into the newly created target function.
- Carefully examine the extracted code to see if it references variables that are scoped from the source function and cannot be accessed in the extracted new function.
- After all variables are processed, compile.
- In the source function, replace the extracted code snippet with a call to the target function.
- See if any other code is the same or similar to the snippet being refined.
The code shown
The original code
function printOwin(invoice) {
printBanner();
let outstanding = calculateOutstanding();
// print details
console.log(`name: ${invoice.customer}`);
console.log(`amount: ${outstanding}`);
}
Copy the code
The new code
function printOwin(invoice) {
printBanner();
let outstanding = calculateOutstanding();
printDetails(invoice, outstanding);
}
function printDetails(invoice, outstanding) {
console.log(`name: ${invoice.customer}`);
console.log(`amount: ${outstanding}`);
}
Copy the code
Inline function
The operation practice
- Check the function to make sure it is not polymorphic.
- Find all call points for this function.
- Replace all call points of this function with the function body.
- After each replacement, the test is performed.
- Delete the definition of this function.
The code shown
The original code
function getRating(driver) {
return moreThanFiveLateDeliveries(driver) ? 2 : 1;
}
function moreThanFiveLateDeliveries(driver) {
return driver.numberOfLateDeliveries > 5;
}
Copy the code
The new code
function getRating(driver) {
return (driver.numberOfLateDeliveries > 5)?2 : 1;
}
Copy the code
Derived variables
The operation practice
- Verify that the expression to be refined has no side effects.
- Declare an immutable variable, make a copy of the expression you want to extract, and assign a value to the variable with the resulting value of the expression.
- Replace the original expression with this new variable.
- The test.
The code shown
The original code
return order.quantity * order.itemPrice - Math.max(0, order.quantity - 500) * order.itemPrice * 0.05 + Math.min(order.quantity * order.itemPrice * 0.1.100);
Copy the code
The new code
const basePrice = order.quantity * order.itemPrice;
const quantityDiscount = Math.max(0, order.quantity - 500) * order.itemPrice * 0.05;
const shipping = Math.min(order.quantity * order.itemPrice * 0.1.100);
return basePrice - quantityDiscount + shipping;
Copy the code
Inline variable
The operation practice
- Check that the expression on the right side of the variable assignment statement has no side effects.
- If the variable is not declared unmodifiable, make it unmodifiable first and perform the test.
- Find the first place where this variable is used and replace it with the expression on the right that uses the assignment statement directly.
- The test.
- Repeat the previous two steps to replace everything else where this variable is used.
- Delete the declaration point and assignment statement for the variable.
- The test.
The code shown
The original code
let basePrice = anOrder.basePrice;
return (basePrice > 1000);
Copy the code
The new code
return anOrder.basePrice > 1000;
Copy the code
Change the function declaration
The operation practice
- If you want to remove a parameter, you need to make sure that the parameter is not used in the function body.
- Modify the function declaration to make it the desired state.
- Find all the places where the old function declarations are used and change them to use the new ones.
- The test.
The code shown
The original code
function circum(radius) {}
Copy the code
The new code
function circumference(radius) {}
Copy the code
Encapsulation variable
The operation practice
- Create wrapper functions that access and update variable values.
- Perform a static check.
- Modify the code that uses this variable one by one to call the appropriate wrapper function instead. After each replacement, the test is performed.
- Limit the visibility of variables.
- The test.
- If the value of a variable is a record, consider using it
Encapsulation record
.
The code shown
The original code
let defaultOwner = {firstName: 'Martin'.lastName: 'Fowler'};
Copy the code
The new code
let defaultOwner = {firstName: 'Martin'.lastName: 'Fowler'};
export function defaultOwner() { returndefaultOwnerData; }export function setDefaultOwner(arg) { defaultOwnerData = arg; }
Copy the code
The variable name
The operation practice
- If variables are widely used, consider using them
Encapsulation variable
Encapsulate it. - Find all the code that uses this variable and modify it one by one.
- The test.
The code shown
The original code
let a = height * width;
Copy the code
The new code
let area = height * width;
Copy the code
Introducing parameter objects
The operation practice
- If you don’t already have a suitable data structure, create one.
- The test.
- use
Change the function declaration
Add a new parameter to the function of type new data structure. - The test.
- Adjust all callers, passing in the appropriate instance of the new data structure.
- Replace each element in the new data structure with the corresponding parameter item in the parameter list, and then delete the original parameter.
The code shown
The original code
function amountInvoiced(startDate, endDate) {}
Copy the code
The new code
function amountInvoiced(aDateRange) {}
Copy the code
Function group synthetic class
The operation practice
- using
Encapsulation record
Encapsulate data records shared by multiple functions. - For each function that uses the record structure, apply
Move the function
Move it to a new class. - The logic used to process the data record can be used
Refining function
Extract it and move it into a new class.
The code shown
The original code
function base(aReading) {}
function taxableCharge(aReading) {}
function calculateBaseCharge(aReading) {}
Copy the code
The new code
class Reading {
base() {}
taxableCharge() {}
calculateBaseCharge() {}
}
Copy the code
Functions combine into transformations
The operation practice
- Creates a transform function that takes the record to be transformed and returns the value of the record directly.
- Pick a piece of logic, move its body into the transformation function, and add the result as a field to the output record. Modify the client code to use this new field.
- The test.
- Repeat the above steps for other relevant computational logic.
The code shown
The original code
function base(aReading) {}
function taxableCharge(aReading) {}
Copy the code
The new code
function enrichReading(argReading) {
const aReading = _.cloneDeep(argReading);
aReading.baseCharge = base(aReading);
aReading.taxableCharge = taxableCharge(aReading);
return aReading;
}
Copy the code
Split phase
The operation practice
- Refine the phase 2 code into separate functions.
- The test.
- Introduce a transit data structure and add it as a parameter to the parameter list of the extracted new function.
- The test.
- Examine each parameter of the refined second-stage function one by one.
- Phase 1 code application
Refining function
, let the extracted function return to the transit data structure.
The code shown
The original code
const orderData = orderString.split(/\s+/);
const productPrice = priceList[orderData[0].split(The '-') [1]].const orderPrice = parseInt(orderData[1]) * productPrice;
Copy the code
The new code
const orderRecord = parseOrder(order);
const orderPrice = price(orderRecord, priceList);
function parseOrder(aString) {
const values = aString.split(/\s+/);
return ({
productID: values[0].split(The '-') [1].quantity: parseInt(values[1])}); }function price(order, priceList) {
return order.quantity * priceList[order.productID];
}
Copy the code
Encapsulation record
The operation practice
- Used with variables that hold records
Encapsulation variable
Encapsulate it in a function. - Create a class that wraps the record and replaces the value of the record variable with an instance of the class. The test.
- Create a new function that returns the object of that class instead of the original record.
- For each point of use of the record, replace the function call that originally returns the record with the function call that returns the instance object.
- Remove the class’s access function to the original record, and remove the easily searchable function that returns the original data.
- The test.
- If the fields in the record are themselves complex structures, consider applying them again
Encapsulation record
orEncapsulation collection
Technique.
The code shown
The original code
organization = {name: 'Acme Gooseberries'.country: 'GB'};
Copy the code
The new code
class Organization {
constructor(data) {
this._name = data.name;
this._country = data.country;
}
get name() { return this._name; } set name(arg) {this._name = arg; } get country() {return this._country; }
set country(arg) { this._country = arg; }}Copy the code
Encapsulation collection
The operation practice
- If the reference to the collection has not already been wrapped, use first
Encapsulation variable
Wrap it. - Add functions for Add collection elements and remove collection elements to the class.
- Perform a static check.
- Find the reference point of the collection.
- Modify the collection’s value function to return a read-only copy of the data, either using a read-only proxy or a copy of the data.
- The test.
The code shown
The original code
class Person {
get courses() { return this._courses; }
set courses(aList) { this._courses = aList; }}Copy the code
The new code
class Person {
get courses() { return this._courses.slice(); }
addCourse(aCourse) {}
removeCourse(aCourse) {}
}
Copy the code
Replace primitive types with objects
The operation practice
- If the variable is not already wrapped, use it first
Encapsulation variable
Wrap it. - Create a simple class for this data value.
- Perform a static check.
- Modify the set function from the first step to create an object of a new class and store it in the field, if necessary, along with the field’s type declaration.
- Modify the value function to call the value function of the new class and return the result.
- The test.
- Consider using the accessor function obtained in the first step
The function name
To reflect its use. - Consider the application will
Change a reference object to a value object
Or willObject to reference object
, specifying whether the new object is a value object or a reference object.
The code shown
The original code
orders.filter(o= > 'hight' === o.priority || 'rush' === o.priority);
Copy the code
The new code
orders.filter(o= > o.priority.higherThan(new Priority('normal')));
Copy the code
Replace temporary variables with queries
The operation practice
- Check that the variable is fully evaluated before it is used, and that the piece of code that evaluates it gets the same value every time.
- If a variable is not currently read-only, but can be made read-only, make it a read-only variable first.
- The test.
- Extract the code snippets that assign values to variables into functions.
- The test.
- application
Inline variable
Manipulation to remove temporary variables.
The code shown
The original code
const basePrice = this._quantity * this._itemPrice;
if (basePrice > 1000)
return basePrice * 0.95;
else
return basePrice * 0.98;
Copy the code
The new code
get basePrice() { this._quantity * this._itemPrice; }if (this.basePrice > 1000)
return this.basePrice * 0.95;
else
return this.basePrice * 0.98;
Copy the code
Derived classes
The operation practice
- Decide how to break down the responsibilities that the class is responsible for.
- Create a new class to represent the responsibilities separated from the old class.
- When constructing an old class, create an instance of the new class, and establish the “access from the old class to the new class” connection.
- For each field you want to move, apply
Move the field
Move it. - use
Move the function
Move the necessary functions to the new class. - Examine the interfaces of the two classes, remove functions that are no longer needed, and rename them as appropriate for the new environment if necessary.
- Decides whether to expose the new class. If necessary, consider applying to new classes
Change a reference object to a value object
Make it a value object.
The code shown
The original code
class Person {
get officeAreaCode() { return this._officeAreaCode; } get officeNumber() {return this._officeNumber;}
}
Copy the code
The new code
class Person {
get officeAreaCode() { return this._telephoneNumber.areaCode; } get officeNumber() {return this._telephoneNumber.number;}
}
class TelephoneNumber {
get areaCode() { return this._areaCode; }
get number() { return this._number; }}Copy the code
Inline class
The operation practice
- For all public functions in the class to be inlined, create a corresponding function on the target class, and delegate all newly created functions directly to the source class.
- Modify all reference points of the source class public methods to call the corresponding delegate methods of the target class. Run the tests after each change.
- Move all functions and data from the source class to the target class, and test after each change until the source class becomes an empty shell.
- Finally, delete the source class.
The code shown
The original code
class Person {
get officeAreaCode() { return this._telephoneNumber.areaCode; } get officeNumber() {return this._telephoneNumber.number;}
}
class TelephoneNumber {
get areaCode() { return this._areaCode; }
get number() { return this._number; }}Copy the code
The new code
class Person {
get officeAreaCode() { return this._officeAreaCode; } get officeNumber() {return this._officeNumber;}
}
Copy the code
Hiding the Delegate Relationship
The operation practice
- For each function in the delegate relationship, create a simple delegate function on the service object side.
- Adjust the client so that it only calls functions provided by the service object. Run the tests after each adjustment.
- If no future client needs to access a Delegate, you can remove the accessor function from the service object.
- The test.
The code shown
The original code
manager = aPerson.department.manager;
Copy the code
The new code
manager = aPerson.department;
class Person {
get manager() { return this.department.manager; }}Copy the code
Remove the middleman
The operation practice
- Creates a value function for the entrusted object.
- For each delegate function, turn its client into a continuous access function call.
- The test.
The code shown
The original code
manager = aPerson.department;
class Person {
get manager() { return this.department.manager; }}Copy the code
The new code
manager = aPerson.department.manager;
Copy the code
Replacement algorithm
The operation practice
- Rearrange the algorithm to be replaced, making sure it has been extracted into a separate function.
- Prepare tests only for this function first to fix its behavior.
- Get ready for another algorithm.
- Perform a static check.
- Run the test and compare the results of the old and new algorithms.
The code shown
The original code
function foundPerson(people) {
for(let i = 0; i < people.length; i++) {
if (people[i] === 'Don') {
return 'Don';
}
if (people[i] === 'John') {
return 'John';
}
if (people[i] === 'Kent') {
return 'Kent'; }}return ' ';
}
Copy the code
The new code
function foundPerson(people) {
const candidates = ['Don'.'John'.'Kent'];
return people.find(p= > candidates.includes(p)) || ' ';
}
Copy the code
Move the function
The operation practice
- Examine all program elements (including variables and functions) that the function references in the current context and consider whether they need to be removed altogether.
- Check whether the function to be moved is polymorphic.
- Make a copy of the function into the target context.
- Perform a static check.
- Try to reference the target function correctly from the source context.
- Modify the source function to be a pure delegate function.
- The test.
- Consider using source functions
Inline function
.
The code shown
The original code
class Account {
get overdraftCharge() {}
}
Copy the code
The new code
class AccountType {
get overdraftCharge() {}
}
Copy the code
Move the field
The operation practice
- Make sure the source fields are well encapsulated.
- The test.
- Create a field on the target object.
- Perform a static check.
- Ensure that the target object is properly referenced in the source object.
- Adjust the accessor function of the source object to use the fields of the target object.
- The test.
- Removes a field on the source object.
- The test.
The code shown
The original code
class Customer {
get plan() { return this._plan; } get discountRate() {return this._discountRate; }}Copy the code
The new code
class Customer {
get plan() { return this._plan; } get discountRate() {return this.plan._discountRate; }}Copy the code
Move statements to functions
The operation practice
- If the repeated code segment is still some distance from the target function call, use first
Mobile statement
Move these statements close to the target function. - If the target function is called only by a single function, you simply cut and paste the repeated code snippets from the source function into the target function, and then run the test.
- If the function has more than one call point, select one of the call points first to apply
Refining function
To extract the statement to be moved along with the target function into a new function. Give the new function a temporary name, as long as it is easy to search. - Adjust other call points of the function to call the newly refined function.
- After all reference points have been replaced, apply
Inline function
Inline the objective function to the new function and remove the original objective function. - Apply the new function
The function name
Rename it to the name of the original target function.
The code shown
The original code
result.push(`<p>title: ${person.photo.title}</p>`);
result.concat(photoData(person.photo));
function photoData(aPhoto) {
return [
`<p>location: ${aPhoto.location}</p>`.`<p>date: ${aPhoto.date.toDateString()}</p>`
];
}
Copy the code
The new code
result.concat(photoData(person.photo));
function photoData(aPhoto) {
return [
`<p>title: ${person.photo.title}</p>`
`<p>location: ${aPhoto.location}</p>`.`<p>date: ${aPhoto.date.toDateString()}</p>`
];
}
Copy the code
Move the statement to the caller
The operation practice
- In the simplest case, where the original function is very simple and has only a few callers, you simply cut and paste the code you want to move from the function back to the caller, making adjustments if necessary.
- If there are more than one or two call points, use it first
Refining function
Extract the code you don’t want to move into a new function with a temporary name, as long as it’s easy to search later. - Apply it to the antiderivative
Inline function
. - Apply the extracted function
Change the function declaration
Make it use the same name as the original function.
The code shown
The original code
emitPhotoData(outStream, person.photo);
function emitPhotoData(outStream, photo) {
outStream.write(`<p>title: ${photo.title}</p>\n`);
outStream.write(`<p>location: ${photo.location}</p>\n`);
}
Copy the code
The new code
emitPhotoData(outStream, person.photo);
outStream.write(`<p>location: ${person.photo.location}</p>\n`);
function emitPhotoData(outStream, photo) {
outStream.write(`<p>title: ${photo.title}</p>\n`);
}
Copy the code
Replace inline code with function calls
The operation practice
- Replace inline code with a call to an existing function.
- The test.
The code shown
The original code
let appliesToMass = false;
for(const s of states) {
if (s === 'MA') appliesToMass = true;
}
Copy the code
The new code
appliesToMass = states.includes('MA');
Copy the code
Mobile statement
The operation practice
- Determine where the snippet to be moved should be moved. Take a close look at the statements between the fragment you want to move and the destination to see if moving it will affect how the code works.
- Cut and paste the source code snippet into the location selected in the previous step.
- The test.
The code shown
The original code
const pricingPlan = retrievePricingPlan();
const order = retrieveOrder();
let charge;
const chargePerUnit = pricingPlan.unit;
Copy the code
The new code
const pricingPlan = retrievePricingPlan();
const chargePerUnit = pricingPlan.unit;
const order = retrieveOrder();
let charge;
Copy the code
Break up the cycle
The operation practice
- Copy the loop code again.
- Identify and remove duplicate code in loops so that each loop does only one thing.
- The test.
The code shown
The original code
let averageAge = 0;
let totalSalary = 0;
for(const p of people) {
averageAge += p.age;
totalSalary += p.salary;
}
averageAge = averageAge / people.length;
Copy the code
The new code
let totalSalary = 0;
for(const p of people) {
totalSalary += p.salary;
}
let averageAge = 0;
for(const p of people) {
averageAge += p.age;
}
averageAge = averageAge / people.length;
Copy the code
Replace circulation with pipes
The operation practice
- Create a new variable to hold the collection of participants in the loop.
- Starting at the top of the loop, move each piece of behavior out of the loop and replace it with a pipe operation on the set variable created in the previous step.
- After moving all the actions in the loop, delete the entire loop.
The code shown
The original code
const names = [];
for (const i of input) {
if (i.job === 'programmer')
names.push(i.name);
}
Copy the code
The new code
const names = input.filter(i= > i.job === 'programmer').map(i= > i.name);
Copy the code
Remove dead code
The operation practice
- If dead code can be referenced directly from outside, such as when it is a separate function, look for call-free points first.
- Remove dead code.
- The test.
The code shown
The original code
if (false) {
doSomethingThatUsedToMatter();
}
Copy the code
The new code
Copy the code
Split variable
The operation practice
- Change the name of the decomposed variable when it is declared and first assigned.
- If possible, declare the new variable as unmodifiable.
- All previous references to the variable are modified to refer to the new variable, bounded by the second assignment of the variable.
- The test.
- Repeat the process. Each time the variable is renamed at the declaration and the reference before the next assignment is modified until the last assignment is reached.
The code shown
The original code
let temp = 2 * (height + width);
console.log(temp);
temp = height * width;
console.log(temp);
Copy the code
The new code
const perimeter = 2 * (height + width);
console.log(perimeter);
const area = height * width;
console.log(area);
Copy the code
Field name
The operation practice
- If the record is small in scope, you can simply modify all the code for that field and then test it. I don’t need the rest of the steps.
- If the record is not already encapsulated, use it first
Encapsulation record
. - Renaming a private field inside an object corresponds to adjusting the function that accesses the field internally.
- The test.
- If the constructor argument uses an old field name, apply
Change the function declaration
Rename it. - using
The function name
Rename the access function.
The code shown
The original code
class Organization {
get name() {}
}
Copy the code
The new code
class Organization {
get title() {}
}
Copy the code
Replace derived variables with queries
The operation practice
- Identify all updates to variables. If necessary, use
Split variable
Split the update points. - Create a new function that evaluates the value of the variable.
- with
Introduction of assertions
Assert that the variable and the evaluated function always give the same value. - The test.
- Modify the code that reads the variable to call the newly created function.
- The test.
- with
Remove dead code
Remove variable declarations and assignments.
The code shown
The original code
get discountedTotal() { return this._discountedTotal; } set discount(aNumber) {const old = this._discount;
this._discount = aNumber;
this._discountedTotal += old - aNumber;
}
Copy the code
The new code
get discountedTotal() { return this._baseTotal - this._discount; }
set discount(aNumber) { this._discount = aNumber; }
Copy the code
Change a reference object to a value object
The operation practice
- Checks whether the reconstruction target is immutable or can be modified to be immutable.
- with
Remove the set function
Get rid of all the set functions one by one. - Provides a value-based equality judgment function that uses the fields of a value object.
The code shown
The original code
class Product {
applyDiscount(arg) { this._price.amount -= arg; }}Copy the code
The new code
class Product {
applyDiscount(arg) {
this._price = new Money(this._price.amount - arg, this._price.currency); }}Copy the code
Change a value object to a reference object
The operation practice
- Create a repository for related objects.
- Ensure that the constructor has a way of finding the correct instance of the associated object.
- Modify the constructor of the host object to get the associated object from the repository. Perform tests after each change.
The code shown
The original code
let customer = new Customer(customerData);
Copy the code
The new code
let customer = customerRepository.get(customerData.id);
Copy the code
Decomposition conditional expression
The operation practice
- Apply the conditional judgment and each conditional branch separately
Refining function
Technique.
The code shown
The original code
if(! aDate.isBefore(plan.summerStart) && ! aDate.isAfter(plan.summerEnd)) charge = quantity * plan.summerRate;else
charge = quantity * plan.regularRate + plan.regularServiceCharge;
Copy the code
The new code
if (summer())
charge = summer();
else
charge = regularCharge();
Copy the code
Merge conditional expression
The operation practice
- Make sure that none of these conditional expressions have side effects.
- Combine two related conditional expressions into one, using the appropriate logical operators.
- The test.
- Repeat the previous merge process until all related conditional expressions are merged.
- Consider implementing the merged conditional expression
Refining function
.
The code shown
The original code
if (anEmployee.seniority < 2) return 0;
if (anEmployee.monthsDisabled > 12) return 0;
if (anEmployee.isPartTime) return 0;
Copy the code
The new code
if (isNotEligibleForDisability()) return 0;
function isNotEligibleForDisability() {
return ((anEmployee.seniority < 2) || (anEmployee.monthsDisabled > 12) || (anEmployee.isPartTime));
}
Copy the code
Replace nested conditional expressions with a guard statement
The operation practice
- Select the outermost conditional logic that needs to be replaced and replace it with a guard statement.
- The test.
- Repeat the steps as necessary.
- Can be used if all guard statements raise the same result
Merge conditional expression
Merge.
The code shown
The original code
function getPayAmount() {
let result;
if (isDead)
result = deadAmount();
else {
if (isSeparated)
result = separatedAmount();
else {
if (isRetired)
result = retiredAmount();
elseresult = normalPayAmount(); }}return result;
}
Copy the code
The new code
function getPayAmount() {
if (isDead) return deadAmount();
if (isSeparated) return separatedAmount();
if (isRetired) return retiredAmount();
return normalPayAmount();
}
Copy the code
Replace conditional expressions with polymorphism
The operation practice
- If an existing class does not already have polymorphic behavior, it is created using a factory function that returns the appropriate object instance.
- Use the factory function in the caller code to get the object instance.
- Move a function with conditional logic into a superclass.
- Create a function in any subclass that overrides the function in the superclass that holds the conditional expression. Copy the conditional expression branch associated with this subclass into the new function and adjust it appropriately.
- Repeat the process for the other conditional branches.
- Leave the default logic in superclass functions.
The code shown
The original code
switch (bird.type) {
case 'EuropeanSwallow':
return 'average';
case 'AfricanSwallow':
return (bird.numberOfCoconuts > 2)?'tired' : 'average';
case 'NorwegianBlueParrot':
return (bird.voltage > 100)?'scorched' : 'beautiful';
default:
return 'unknown';
}
Copy the code
The new code
class EuropeanSwallow {
get plumage() {
return 'average'; }}class AfricanSwallow {
get plumage() {
return (this.numberOfCoconuts > 2)?'tired' : 'average'; }}class NorwegianBlueParrot {
get plumage() {
return (this.voltage > 100)?'scorched' : 'beautiful'; }}Copy the code
Replace conditional expressions with polymorphism
The operation practice
- If an existing class does not already have polymorphic behavior, it is created using a factory function that returns the appropriate object instance.
- Use the factory function in the caller code to get the object instance.
- Move a function with conditional logic into a superclass.
- Create a function in any subclass that overrides the function in the superclass that holds the conditional expression. Copy the conditional expression branch associated with this subclass into the new function and adjust it appropriately.
- Repeat the process for the other conditional branches.
- Leave the default logic in superclass functions.
The code shown
The original code
switch (bird.type) {
case 'EuropeanSwallow':
return 'average';
case 'AfricanSwallow':
return (bird.numberOfCoconuts > 2)?'tired' : 'average';
case 'NorwegianBlueParrot':
return (bird.voltage > 100)?'scorched' : 'beautiful';
default:
return 'unknown';
}
Copy the code
The new code
class EuropeanSwallow {
get plumage() {
return 'average'; }}class AfricanSwallow {
get plumage() {
return (this.numberOfCoconuts > 2)?'tired' : 'average'; }}class NorwegianBlueParrot {
get plumage() {
return (this.voltage > 100)?'scorched' : 'beautiful'; }}Copy the code
The introduction of special case
The operation practice
- Add the check exception attribute to the refactoring target and make it return false.
- Creates an exception object that checks only the properties of the exception and returns true.
- Use of “compare to special case values” code
Refining function
Make sure that all clients use the new function instead of directly comparing special case values. - New special case objects are introduced into the code, which can be returned from function calls or generated in transformation functions.
- Modify the body of the exception comparison function to use the attributes of the check exception directly in it.
- The test.
- use
Function group synthetic class
orFunctions combine into transformations
To move the general exception handling logic to the newly created exception object. - Use for special case comparison functions
Inline function
Inline it to where it is still needed.
The code shown
The original code
if (aCustomer === 'unknown') customerName = 'occupant';
Copy the code
The new code
class UnknownCustomer {
get name() {
return 'occupant'; }}Copy the code
Introduction of assertions
The operation practice
- If you find that your code assumes a condition is always true, add an assertion that explicitly states the case. Because assertions should have no effect on system operation, “join assertions” should always be behavior persistent.
The code shown
The original code
if (this.discountRate)
base = base - (this.discountRate * base);
Copy the code
The new code
assert(this.discountRate >= 0);
if (this.discountRate)
base = base - (this.discountRate * base);
Copy the code
Separate the query and modify functions
The operation practice
- Copy the entire function and name it as a query.
- Remove all statements that cause side effects from the new query function.
- Perform a static check.
- Find all the places where the original function was called. If the return value of this function is used, call the newly created query function instead, and call the original function again immediately below. Test after each change.
- Remove the return value from the original function.
- The test.
The code shown
The original code
function getTotalOutstandingAndSendBill() {
const result = customer.invoices.reduce((total, each) = > each.amount + total, 0);
sendBill();
return result;
}
Copy the code
The new code
function totalOutstanding() {
return customer.invoices.reduce((total, each) = > each.amount + total, 0);
}
function sendBill() {
emailGateway.send(formatBill(customer));
}
Copy the code
Function parameterization
The operation practice
- Select one from a set of similar functions.
- using
Change the function declaration
To add the literals that need to be passed in as arguments to the argument list. - Modify all calls to this function so that the literal value is passed in when called.
- The test.
- Modify the function body to use the newly passed parameters. Test every time you use a new parameter.
- For other similar functions, one by one, change where they are called to call already parameterized functions. Test after each change.
The code shown
The original code
function tenPercentRaise(aPerson) {
aPerson.salary = aPerson.salary.multiply(1.1);
}
function fivePercentRaise(aPerson) {
aPerson.salary = salary.salary.multiply(1.05);
}
Copy the code
The new code
function raise(aPerson, factor) {
aPerson.salary = salary.salary.multiply(1 + factor);
}
Copy the code
Remove marker parameter
The operation practice
- Create an explicit function for each possible value of the argument.
- For function callers that “take literal values as arguments,” call the newly created explicit function instead.
The code shown
The original code
function setDimension(name, value) {
if (name === 'height') {
this._height = value;
return;
}
if (name === 'width') {
this._width = value;
return; }}Copy the code
The new code
function setHeight(value) {
this._height = value;
this._width = value;
}
Copy the code
Keep objects intact
The operation practice
- Create an empty function and give it the expected list of arguments (that is, pass in the full object as arguments).
- The old function is called in the body of the new function, and the new parameters (that is, the full object) are mapped to the old parameter list (that is, the data from the full object).
- Perform a static check.
- Callers who modify the old function one by one, make them use the new function, and execute the test after each change.
- After all calls have been modified, use
Inline function
Inline the old function into the new function. - Rename the new function from the easily searchable temporary name at the beginning of the refactoring to the name of the old function, while changing all calls.
The code shown
The original code
const low = aRoom.daysTempRange.low;
const high = aRoom.daysTempRange.high;
if (aPlan.withinRange(low, high))
Copy the code
The new code
if (aPlan.withinRange(aRoom.daysTempRange))
Copy the code
Replace parameters with queries
The operation practice
- If necessary, use
Refining function
The calculation of parameters is distilled into a separate function. - Change the place in the function body that references this parameter to call the newly created function instead. Perform tests after each change.
- After all the replacement is complete, use
Change the function declaration
Remove this parameter.
The code shown
The original code
availableVacation(anEmployee, anEmployee.grade);
function availableVacation(anEmployee, grade) {}
Copy the code
The new code
availableVacation(anEmployee);
function availableVacation(anEmployee) {
const grade = anEmployee.grade;
}
Copy the code
Replace queries with parameters
The operation practice
- Used with the code that performs the query operation
Derived variables
To separate it from the function body. - Now that the function body code no longer performs the query operation, use this part of the code
Refining function
. - use
Inline variable
To eliminate the extracted variables. - For the original function
Inline function
. - Change the name of the new function back to the name of the original function.
The code shown
The original code
targetTemperature(aPlan);
function targetTemperature(aPlan) {
currentTemperature = thermostat.currentTemperature;
}
Copy the code
The new code
targetTemperature(aPlan, thermostat.currentTemperature);
function targetTemperature(aPlan, currentTemperature) {}
Copy the code
Remove the set function
The operation practice
- If the constructor cannot get the value of the field it wants to set
Change the function declaration
Pass this value as an argument to the constructor. Call the set function in the constructor to set the value of the field. - Removes all calls to set functions outside the constructor and uses the new constructor instead. Test after each change.
- use
Inline function
Get rid of the set function. If possible, declare the field immutable. - The test.
The code shown
The original code
class Person {
get name() {}
set name(aString) {}
}
Copy the code
The new code
class Person {
get name() {}
}
Copy the code
Replace constructors with factory functions
The operation practice
- Create a new factory function that calls the existing constructor.
- Change the code that calls the constructor to call the factory function instead.
- Tests are performed for each change.
- Minimize the visibility of constructors.
The code shown
The original code
leadEngineer = new Employee(document.leadEngineer, 'E');
Copy the code
The new code
leadEngineer = createEmployeer(document.leadEngineer);
Copy the code
Replace functions with commands
The operation practice
- Create an empty class for the function you want to wrap, naming it after the function’s name.
- use
Move the function
Move the function into an empty class. - Consider creating a field for each parameter and adding the corresponding parameter to the constructor.
The code shown
The original code
function score(candidate, medicalExam, scoringGuide) {
let result = 0;
let healthLevel = 0;
}
Copy the code
The new code
class Scorer {
constructor(candidate, medicalExam, scoringGuide) {
this._candidate = candidate;
this._medicalExam = medicalExam;
this._scoringGuide = scoringGuide;
}
execute() {
this._result = 0;
this._healthLevel = 0; }}Copy the code
Replace commands with functions
The operation practice
- using
Refining function
, distills the code for creating and executing command objects into a single function. - Use each of the functions used in the execution of the command object
Inline function
. - use
Change the function declaration
To pass the constructor arguments to the executing function. - For all fields, find references to them in the execution function and use arguments instead. Test after each change.
- Inline both the “call constructor” and “call execution function” steps to the caller.
- The test.
- with
Remove dead code
Delete the command class.
The code shown
The original code
class ChargeCalculator {
constructor(customer, usage) {
this._customer = customer;
this._usage = usage;
}
execute() {
return this._customer.rate * this._usage; }}Copy the code
The new code
function charge(customer, usage) {
return customer.rate * usage;
}
Copy the code
The function up
The operation practice
- Check the functions to be promoted to make sure they are exactly the same.
- Check that all function calls and fields referenced in the function body can be called from the superclass.
- If the signature of the promoted function is different, use
Change the function declaration
Change those signatures to the ones you want to use in your superclass. - Create a new function in the superclass and copy the code for one of the functions to be promoted into it.
- Perform a static check.
- Removes a subclass function to be promoted.
- The test.
- Remove the subclass functions to be promoted one by one until only the functions in the superclass remain.
The code shown
The original code
class Employee {}
class Salesman extends Employee {
get name() {}
}
class Engineer extends Employee {
get name() {}
}
Copy the code
The new code
class Employee {
get name() {}
}
class Salesman extends Employee {}
class Engineer extends Employee {}
Copy the code
Field up
The operation practice
- For the fields to be promoted, check all their usage points to make sure they are used in the same way.
- If the field names are different, use first
The variable name
Give them the same name. - Create a new field in the superclass.
- Removes a field in a subclass.
- The test.
The code shown
The original code
class Employee {} //Java
class Salesman extends Employee {
private String name;
}
class Engineer extends Employee {
private String name;
}
Copy the code
The new code
class Employee {
protected String name;
}
class Salesman extends Employee {}
class Engineer extends Employee {}
Copy the code
The constructor body moves up
The operation practice
- If the superclass does not already have a constructor, define one for it first. Make sure that subclasses call the constructor of the superclass.
- use
Mobile statement
Move the public statement of the constructor in the subclass after the constructor call statement of the superclass. - Remove common code between subclasses one by one and promote it to the superclass constructor. For variables referenced in common code, pass them as arguments to the constructor of the superclass.
- The test.
- If there is common code that cannot be easily promoted to a superclass, apply it first
Refining function
And reuseThe function up
Ascension.
The code shown
The original code
class Party {}
class Employee extends Party {
constructor(name, id, monthlyCost) {
super(a);this._id = id;
this._name = name;
this._monthlyCost = monthlyCost; }}Copy the code
The new code
class Party {
constructor(name) {
this._name = name; }}class Employee extends Party {
constructor(name, id, monthlyCost) {
super(name);
this._id = id;
this._monthlyCost = monthlyCost; }}Copy the code
My function is
The operation practice
- Copy the function body from the superclass into every subclass that needs this function.
- Delete functions in the superclass.
- The test.
- Remove this function from all subclasses that do not need it.
- The test.
The code shown
The original code
class Employee {
get quota {}
}
class Engineer extends Employee {}
class Salesman extends Employee {}
Copy the code
The new code
class Employee {}
class Engineer extends Employee {}
class Salesman extends Employee {
get quota {}
}
Copy the code
Field down
The operation practice
- Declare the field in all subclasses that need it.
- Remove the field from the superclass.
- The test.
- Remove this field from all subclasses that don’t need it.
- The test.
The code shown
The original code
class Employee { // Java
private String quota;
}
class Engineer extends Employee {}
class Salesman extends Employee {}
Copy the code
The new code
class Employee {}
class Engineer extends Employee {}
class Salesman extends Employee {
protected String quota;
}
Copy the code
Replace type codes with subclasses
The operation practice
- Self-encapsulation type code field.
- Create a subclass of any type code. Overrides the value function of a type code class that returns the literal value of the type code.
- Create a selector logic that maps type code parameters to a new subclass.
- The test.
- Repeat create subclass, Add selector logic for each type code value.
- Remove type code fields.
- The test.
- use
My function is
andReplace conditional expressions with polymorphism
Handles functions that originally access type codes.
The code shown
The original code
function createEmployee(name, type) {
return new Employee(name, type);
}
Copy the code
The new code
function createEmployee(name, type) {
switch (type) {
case 'engineer': return new Engineer(name);
case 'salesman': return new Salesman(name);
case 'manager': return newManager(name); }}Copy the code
Remove the subclass
The operation practice
- use
Replace constructors with factory functions
Wrap the constructor of the subclass in the factory function of the superclass. - If there is any code that checks the type of a subclass, use first
Refining function
Wrap type checking logic around it and useMove the function
Move it to the superclass. - Create a new field that represents the type of the subclass.
- Change the function that used to judge the type of a subclass to use the newly created type field.
- Delete subclasses.
- The test.
The code shown
The original code
class Person {
get genderCode() { return 'X';}
}
class Male extends Person {
get genderCode() { return 'M';}
}
class female extends Person {
get genderCode() { return 'F';}
}
Copy the code
The new code
class Person {
get genderCode() { return this._genderCode;}
}
Copy the code
Refining the superclass
The operation practice
- Create a new, blank superclass for the original class.
- The test.
- use
The constructor body moves up
,The function up
andField up
Move the common elements of subclasses up to the superclass one by one. - Check the functions left in the subclasses to see if they have any common components. If so, you can use it first
Refining function
Extract it and reuse itThe function up
Move to the superclass. - Examine all client code that uses the original class and consider adapting it to use the superclass interface.
The code shown
The original code
class Department {
get totalAnnualCost() {}
get name() {}
get headCount() {}
}
class Employee {
get annualCost() {}
get name() {}
get id() {}
}
Copy the code
The new code
class Party {
get name() {}
get annualCost() {}
}
class Department extends Party {
get annualCost() {}
get headCount() {}
}
class Employee extends Party {
get annualCost() {}
get id() {}
}
Copy the code
Folded inheritance system
The operation practice
- Select the class you want to remove: superclass or subclass?
- use
Field up
,Field down
,The function up
,My function is
Move all elements into the same class. - Adjust all reference points of the class to be removed so that they refer to the class left behind after the merge.
- Remove our target;
- The test.
The code shown
The original code
class Employee {}
class Salesman extends Employee {}
Copy the code
The new code
class Employee {}
Copy the code
Replace subclasses with delegates
The operation practice
- If the constructor has more than one caller, use it first
Replace constructors with factory functions
Wrap the constructor around it. - Create an empty delegate class whose constructor should accept all subclass-specific data items, often taking a reference back to the superclass as an argument.
- Add a field to the superclass to place the delegate object.
- Modify the creation logic of the subclass to initialize the above delegate field to place an instance of the delegate object.
- Select a function from a subclass and move it to the delegate class.
- use
Move the function
Move the above functions around without removing the delegate code from the source class. - If the moved source function is called outside the subclass, the delegate code left in the source class is moved from the subclass to the superclass, and the delegate code is preceded by a guard statement to check that the delegate exists. If there are no other callers outside the subclass
Remove dead code
Get rid of delegate code that no one uses anymore. - The test.
- Repeat the process until all the functions in the subclass are moved to the delegate class.
- Find all the places where the subclass constructors are called and change them one by one to use the superclass constructors.
- The test.
- using
Remove dead code
Get rid of subclasses.
The code shown
The original code
class Order {
get daysToShip() {
return this._warehouse.daysToShip; }}class PriorityOrder extends Order {
get daysToShip() {
return this._priorityPlan.daysToShip; }}Copy the code
The new code
class Order {
get daysToShip() {
return (this._priorityDelegate) ? this._priorityDelegate.daysToShip : this._warehouse.daysToShip; }}class PriorityOrderDelegate {
get daysToShip() {
return this._priorityPlan.daysToShip; }}Copy the code
Replace superclasses with delegates
The operation practice
- Create a new field in a subclass that refers to an object of the superclass, and initialize the delegate reference to a new instance of the superclass.
- For each function of the superclass, create a forward function in the subclass that forwards the invocation request to the delegate reference.
- When all superclass functions forward function overrides, inheritance can be removed.
The code shown
The original code
class List {}
class Stack extends List {}
Copy the code
The new code
class Stack {
constructor() {
this._storage = newList(); }}class List {}
Copy the code