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 itEncapsulation 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 themEncapsulation variableEncapsulate 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.
  • useChange the function declarationAdd 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

  • usingEncapsulation recordEncapsulate data records shared by multiple functions.
  • For each function that uses the record structure, applyMove the functionMove it to a new class.
  • The logic used to process the data record can be usedRefining functionExtract 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 applicationRefining 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 recordsEncapsulation variableEncapsulate 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 againEncapsulation recordorEncapsulation collectionTechnique.

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 firstEncapsulation variableWrap 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 firstEncapsulation variableWrap 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 stepThe function nameTo reflect its use.
  • Consider the application willChange a reference object to a value objectOr 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.
  • applicationInline variableManipulation 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, applyMove the fieldMove it.
  • useMove the functionMove 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 classesChange a reference object to a value objectMake 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 functionsInline 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 firstMobile statementMove 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 applyRefining functionTo 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, applyInline functionInline the objective function to the new function and remove the original objective function.
  • Apply the new functionThe function nameRename 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 firstRefining functionExtract 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 antiderivativeInline function.
  • Apply the extracted functionChange the function declarationMake 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 firstEncapsulation 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, applyChange the function declarationRename it.
  • usingThe function nameRename 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, useSplit variableSplit the update points.
  • Create a new function that evaluates the value of the variable.
  • withIntroduction of assertionsAssert 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.
  • withRemove dead codeRemove 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.
  • withRemove the set functionGet 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 separatelyRefining functionTechnique.

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 expressionRefining 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 resultMerge conditional expressionMerge.

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” codeRefining functionMake 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.
  • useFunction group synthetic classorFunctions combine into transformationsTo move the general exception handling logic to the newly created exception object.
  • Use for special case comparison functionsInline functionInline 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.
  • usingChange the function declarationTo 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, useInline functionInline 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, useRefining functionThe 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, useChange the function declarationRemove 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 operationDerived variablesTo separate it from the function body.
  • Now that the function body code no longer performs the query operation, use this part of the codeRefining function.
  • useInline variableTo eliminate the extracted variables.
  • For the original functionInline 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 setChange the function declarationPass 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.
  • useInline functionGet 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.
  • useMove the functionMove 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

  • usingRefining 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 objectInline function.
  • useChange the function declarationTo 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.
  • withRemove dead codeDelete 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, useChange the function declarationChange 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 firstThe variable nameGive 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.
  • useMobile statementMove 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 firstRefining functionAnd reuseThe function upAscension.

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.
  • useMy function isandReplace conditional expressions with polymorphismHandles 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

  • useReplace constructors with factory functionsWrap 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 firstRefining functionWrap type checking logic around it and useMove the functionMove 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.
  • useThe constructor body moves up,The function upandField upMove 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 firstRefining functionExtract it and reuse itThe function upMove 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?
  • useField up,Field down,The function up,My function isMove 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 firstReplace constructors with factory functionsWrap 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.
  • useMove the functionMove 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 subclassRemove dead codeGet 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.
  • usingRemove dead codeGet 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