Refactoring: Improved design of existing code – book links

Reorganizing data

  1. Split variable

    A variable has only one responsibility

    function distanceTravelled(){
        let acc = 0
        acc = ' '
    }
    Copy the code
    function distanceTravelled(){
        let acc = 0
        let str = ' '
    }
    Copy the code

    practice

    1. At the declaration of the variable to be decomposed and its first assignment, change its name
    2. If possible, declare the new variable as unmodifiable
    3. The second assignment of the variable is decomposed to modify the previous reference to the variable and refer to the new variable
    function distanceTravelled(scenario, time){
        let result
        let acc = scenario.primaryForce / scenario.mass
        let primaryTime = Math.min(time, scenario.delay)
        result = 0.5 * acc * primaryTime * primaryTime
        let secondTime = time - scenario.delay
        if(secondTime > 0) {let primaryVelocity = acc * scenario.delay
            acc = (scenario.primaryForce + scenario.secondForce) / scenario.mass
            result += primaryVelocity * secondTime + 0.5 * acc * secondTime * secondTime
        }
        return result
    }
    Copy the code
    function distanceTravelled(scenario, time){
        let result
        let primaryAcceleration = scenario.primaryForce / scenario.mass
        let primaryTime = Math.min(time, scenario.delay)
        result = 0.5 * primaryAcceleration * primaryTime * primaryTime
        let secondTime = time - scenario.delay
        if(secondTime > 0) {let primaryVelocity = primaryAcceleration * scenario.delay
            let secondaryAcceleration = (scenario.primaryForce + scenario.secondForce) / scenario.mass
            result += primaryVelocity * secondTime + 0.5 * secondaryAcceleration * secondTime * secondTime
        }
        return result
    }
    Copy the code
  2. Field name

    Change non-semantically appropriate field names to more appropriate ones

    const organization = { name: 'Acme'.country: 'GB' }
    Copy the code
    class Organization {
        constructor(data){
            this._title = data.title || data.name
        }
        get title() {return this._title }
        set title(title) {this._title = title}
    }
    Copy the code

    practice

    1. If the record is not encapsulated, it is encapsulated first
    2. Rename the field and synchronize the update at the reference
    const organization = { name: 'Acme'.country: 'GB' }
    Copy the code
    class Organization {
        constructor(data){
            this._title = data.title || data.name
            this._country = data.country
        }
        get title() {return this._title }
        set title(title) {this._title = title}
        get country() {return this._country}
        set country(country) {this._country = country}
    }
    const organization =  new Organization({ name: 'Acme'.country: 'GB' })
    Copy the code
  3. Replace derived variables with queries

    Mutable pair data is the biggest source of pair errors in software, and pair modification often leads to ugly coupling between parts of code

    class ProductionPlan {
        constructor(adjustments){
            this._adjustments = adjustments
            this._production = this._adjustments.reduce((prev, item) = > prev + item.amount, 0)}get production() {return this._production}
        applyAdjustment(anAdjustment){
            this._adjustments.push(anAdjustment)
            this._production += anAdjustment.amount
        }
    }
    Copy the code
    class ProductionPlan {
        constructor(adjustments){
            this._adjustments = adjustments
        }
        get production() {return this._adjustments.reduce((prev, item) = > prev + item.amount, 0)}
        applyAdjustment(anAdjustment){
            this._adjustments.push(anAdjustment)
        }
    }
    Copy the code

    practice

    1. Identify all updates to variables
    2. Create a new function that evaluates the value of the variable
    class ProductionPlan {
        constructor(adjustments){
            this._adjustments = adjustments
            this._production = this._adjustments.reduce((prev, item) = > prev + item.amount, 0)}get production() {return this._production}
        applyAdjustment(anAdjustment){
            this._adjustments.push(anAdjustment)
            this._production += anAdjustment.amount
        }
    }
    Copy the code
    class ProductionPlan {
        constructor(adjustments){
            this._adjustments = adjustments
        }
        get production() {return this._adjustments.reduce((prev, item) = > prev + item.amount, 0)}
        applyAdjustment(anAdjustment){
            this._adjustments.push(anAdjustment)
        }
    }
    Copy the code
  4. Changing a reference object to a value object (changing a value object to a reference object)

    Updates the property value of a reference object, directly replacing the entire internal object

    class Person {
        constructor(){
            this._tele = new TelephoneNumber()
        }
    }
    class Tele{... }Copy the code
    class Person {
        set officeAreaCode(arg) {return this._tele = new Tele() }
        set officeNumber(arg) {return this._tele = new Tele() }
    }
    class Tele{... }Copy the code

    practice

    1. Checks whether the refactoring target is an immutable object or is modified to be immutable
    2. Modify the set function in the object to create a new object
    class Person {
        constructor(name){
            this._name = name
            this._telephoneNumber = new TelephoneNumber()
        }
        get name() {return this._name }
        get telephoneNumber() {return this._telephoneNumber.toString() }
        get officeAreaCode() {return this._telephoneNumber.areaCode }
        set officeAreaCode(arg) {return this._telephoneNumber.areaCode = arg }
        get officeNumber() {return this._telephoneNumber.number }
        set officeNumber(arg) {return this._telephoneNumber.number = arg }
    }
    class TelephoneNumber{
        get number() {return this._number}
        set number(arg) {return this._number = arg}
        get areaCode() {return this._areaCode }
        set areaCode(arg) {return this._areaCode = arg}
        toString(){return ` (The ${this._areaCode}) The ${this._number}`}}Copy the code
    class Person {
        constructor(name){
            this._name = name
        }
        get name() {return this._name }
        get telephoneNumber() {return this._telephoneNumber.toString() }
        get officeAreaCode() {return this._telephoneNumber.areaCode }
        set officeAreaCode(arg) {return this._telephoneNumber = new TelephoneNumber(arg, this.officeNumber) }
        get officeNumber() {return this._telephoneNumber.number }
        set officeNumber(arg) {return this._telephoneNumber = new TelephoneNumber(this.areaCode, arg) }
    }
    class TelephoneNumber{
        constructor(areaCode, number){
            this._areaCode = areaCode
            this._number = number
        }
        get number() {return this._number}
        get areaCode() {return this._areaCode }
        toString(){return ` (The ${this._areaCode}) The ${this._number}`}}Copy the code
  5. Change value objects to reference objects (change reference objects to value objects)

    Too many copies are difficult to maintain, so create a warehouse for storage

    class Order {
        constructor(data){
            this._customer = new Customer(data.customer)
        }
    }
    class Customer {}
    Copy the code
    let _repository = {}
    _repository.customer = new Map(a)function registerCustomer(id){
        if(! _repository.customer.has(id)){ _repository.customer.set(id,new Customer(id))
        }
        return findCustomer(id)
    }
    function findCustomer(id){
        return _repository.customer.get(id)
    }
    class Order {
        constructor(data){
            this._customer = registerCustomer(data.customer)
        }
    }
    class Customer {}
    Copy the code

    practice

    1. Create a repository for related objects
    2. Ensure that the constructor has a way of finding the correct instance of the associated object
    3. Modify the constructor of the host object to get the associated object from the repository
    class Order {
        constructor(data){
            this._number = data.number;
            this._customer = new Customer(data.customer)
        }
        get customer() {return this._customer}
    }
    class Customer {
        constructor(id){
            this._id = id
        }
        get id() {return this._id}
    }
    Copy the code
    let _repositoryData
    function initialize(){
        _repositoryData = {}
        _repositoryData.customer = new Map()}function registerCustomer(id){
        if(! _repositoryData.customer.has(id)){ _repositoryData.customer.set(id,new Customer(id))
        }
        return findCustomer(id)
    }
    function findCustomer(id){
        return _repositoryData.customer.get(id)
    }
    initialize()
    class Order {
        constructor(data){
            this._number = data.number;
            this._customer = registerCustomer(data.customer)
        }
        get customer() {return this._customer}
    }
    class Customer {
        constructor(id){
            this._id = id
        }
        get id() {return this._id}
    }
    Copy the code

Reduced conditional logic

  1. Decomposition conditional expression

    Decomposes complex conditional expressions into independent functions

    function price(){...if(aDate.isSummer || aDate.isSpring){
            charge = quantity * aPlan.summerRate
        }else{
            charge = quantity * aPlan.rate
        }
        ...
    }
    Copy the code
    function price(aDate, aPlan){...return summerOrSpring() ? summerCharge() : orderCharge()
        function summerOrSpring(){... }function summerCharge(){... }function orderCharge(){...}
    }
    Copy the code

    practice

    1. Extract functions are used for condition judgments and for each condition branch
    function price(aDate, aPlan){
        let charge
        let quantity = 100
        if(aDate.isSummer || aDate.isSpring){
            charge = quantity * aPlan.summerRate
        }else{
            charge = quantity * aPlan.rate
        }
        return charge
    }
    Copy the code
    function price(aDate, aPlan){
        let quantity = 100
        return summerOrSpring() ? summerCharge() : orderCharge()
        function summerOrSpring(){
            return aDate.isSummer || aDate.isSpring
        }
        function summerCharge(){
            return quantity * aPlan.summerRate
        }
        function orderCharge(){
            return quantity * aPlan.rate
        }
    }
    Copy the code
  2. Merge conditional expression

    Combine the same return conditions into one place

    if(state == 1) return 0
    if(start == 2) return 0
    return 1
    Copy the code
    if(state == 1 || start == 2) return 0
    return 1
    Copy the code

    practice

    1. Determine that conditional expressions have no side effects
    2. Use the appropriate operation to match the union
    if(state == 1) return 0
    if(start == 2) return 0
    if(end == 3) return 0
    return 1
    Copy the code
    if(state == 1 || start == 2 || end == 3) return 0
    return 1
    Copy the code
  3. Replace nested conditional expressions with a guard statement

    Multiple layers of nested judgments reduce readability and can be used for early returns

    function payAmount(employee){
        if(employee.isSeparated){
            ...
        }else{
            if(employee.isRetired){
                ...
            }else{... }}}Copy the code
    function payAmount(employee){
        if(employee.isSeparated) return 
        if(employee.isRetired) return 
        return 
    }
    Copy the code

    practice

    1. Select the outer conditional logic that needs to be replaced and replace it with a guard statement
    function payAmount(employee){
        let result
        if(employee.isSeparated){
            result = {amount: 0.reasonCode: 'SEP'}}else{
            if(employee.isRetired){
                result = {amount: 0.reasonCode: 'RET'}}else{
                result = {amount: 1000.reasonCode: ' '}}}return result
    }
    Copy the code
    function payAmount(employee){
        if(employee.isSeparated) return {amount: 0.reasonCode: 'SEP'}
        if(employee.isRetired) return {amount: 0.reasonCode: 'RET'}
        return {amount: 1000.reasonCode: ' '}}Copy the code
  4. Replace conditional expressions with polymorphism

    Class polymorphism is used to improve complex conditional expressions

    class Bird {
        constructor(name, type){
            switch (bird.type){
                case 'E': this.plumage = 'e'
                case 'N': this.plumage = 'n'
                default: this.plumage = 'unknown'}}}Copy the code
    class Bird {
        get plumage() {return 'unknown'}}class E extends Bird{
        get plumage() {return 'e'}}class N extends Bird{
        get plumage() {return 'n'}}function createBird(. arg){
        switch (arg[1]) {case 'E': return newE(... arg);case 'N': return newN(... arg);default: return new Bird(...arg);
        }
    }
    Copy the code

    practice

    1. If an existing class does not have polymorphic behavior, it is created using the factory pattern
    2. Use engineering functions in caller code to get object instances
    3. Move a function with conditional logic into a superclass
    4. Create a function in any subclass that duplicates the function in the superclass that holds the conditional expression
    5. Copies the conditional expression branch associated with that subclass into the new function
    6. Declare the functions of the superclass as abstract functions after processing
    function plumages(birds){
        return new Map(birds.map(b= > [b.name, b.plumage]))
    }
    function speeds(birds){
        return new Map(birds.map(b= > [b.name, airSpeedVelocity(b)]))
    }
    function plumage(bird){
        switch (bird.type){
            case 'E': return 'a'
            case 'A': return bird.counts > 2 ? 't' : 'a'
            case 'N': return bird.voltage > 100 ? 's' : 'b'
            default: return 'unknown'}}function airSpeedVelocity(bird){
        switch (bird.type){
            case 'E': return 35
            case 'A': return 40 - bird.counts
            case 'N': return bird.voltage / 10 + 10
            default: return null}}class Bird {
        constructor(name, type, counts, voltage){
            this.name = name
            this.type = type
            this.counts = counts
            this.voltage = voltage
            this.plumage = plumage(this)}}Copy the code
    function plumages(birds){
        return new Map(birds.map(b= > [b.name, b.plumage]))
    }
    function speeds(birds){
        return new Map(birds.map(b= > [b.name, b.airSpeedVelocity]))
    }
    class Bird {
        constructor(name, type, counts, voltage){
            this.name = name
            this.type = type
            this.counts = counts
            this.voltage = voltage
        }
        get plumage() {return 'unknown'
        }
        get airSpeedVelocity() {return null}}class E extends Bird{
        get plumage() {return 'a'
        }
        get airSpeedVelocity() {return 35}}class A extends Bird{
        get plumage() {return this.counts > 2 ? 't' : 'a'
        }
        get airSpeedVelocity() {return 40 - this.counts
        }
    }
    class N extends Bird{
        get plumage() {this.voltage > 100 ? 's' : 'b'
        }
        get airSpeedVelocity() {return this.voltage / 10 + 10}}function createBird(. arg){
        switch (arg[1]) {case 'E': return newE(... arg);case 'A': return newA(... arg);case 'N': return newN(... arg);default: return new Bird(...arg);
        }
    }
    Copy the code
    function rating(voyage, history){
        const vpf = voyageProfitFactor(voyage, history)
        const vr = voyageRisk(voyage)
        const chr = captainHistoryRisk(voyage, history)
        if(vpf * 3 > (vr + chr * 2)) return 'A'
        return 'B'
    }
    function voyageRisk(voyage){
        let result = 1
        if(voyage.length > 4) result += 2
        if(voyage.length > 8) result += voyage.length - 8
        if(['china'.'east-indies'].includes(voyage.zone)) result += 4
        return Math.max(result, 0)}function captainHistoryRisk(voyage, history){
        let result = 1
        if(history.length < 5) result += 4
        result += history.filter(v= > v.profit < 0).length
        if(voyage.zone === 'china' && hasChina(history)) result -= 2
        return Math.max(result, 0)}function hasChina(history) {
        return history.some(v= > v.zone === 'china')}function voyageProfitFactor(voyage, history){
        let result = 2
        if(voyage.zone === 'china') result += 1
        if(voyage.zone === 'east-indies') result += 1
        if(voyage.zone === 'china' && hasChina(history)){
            result += 3
            if(history.length > 10) result += 1
            if(voyage.length > 12) result += 1
            if(voyage.length > 18) result -= 1
        }else{
            if(history.length > 8) result += 1
            if(voyage.length > 14) result -= 1
        }
        return result
    }
    const voyage = { zone: 'west-indies'.length: 10 }
    const history = [
        { zone: 'east-indies'.profit: 5 },
        { zone: 'west-indies'.profit: 15 },
        { zone: 'china'.profit: -2 },
        { zone: 'west-africa'.profit: 7}]console.log(rating(voyage, history))
    Copy the code
    function rating(voyage, history){
        return createRating(voyage, history).value
    }
    function createRating(voyage, history){
        if(voyage.zone === 'china' && history.some(v= > v.zone === 'china')) return new ExperienceChinaRating(voyage, history)
        return new Rating(voyage, history)
    }
    class Rating {
        constructor(voyage, history){
            this.voyage = voyage
            this.history = history
        }
        get value() {const vpf = this.voyageProfitFactor
            const vr = this.voyageRisk
            const chr = this.captainHistoryRisk
            if(vpf * 3 > (vr + chr * 2)) return 'A'
            return 'B'
        }
        get voyageProfitFactor() {let result = 2
            if(this.voyage.zone === 'china') result += 1
            if(this.voyage.zone === 'east-indies') result += 1
            result += this.historyLengthFactor
            result += this.voyageLengthFactor
            return result
        }
        get voyageLengthFactor() {return this.voyage.length > 14 ? 1 : 0
        }
        get historyLengthFactor() {return this.history.length > 8 ? 1 : 0
        }
        get voyageRisk() {let result = 1
            if(this.voyage.length > 4) result += 2
            if(this.voyage.length > 8) result += this.voyage.length - 8
            if(['china'.'east-indies'].includes(this.voyage.zone)) result += 4
            return Math.max(result, 0)}get captainHistoryRisk() {let result = 1
            if(this.history.length < 5) result += 4
            result += this.history.filter(v= > v.profit < 0).length
            return Math.max(result, 0)}}class ExperienceChinaRating extends Rating{
        get captainHistoryRisk() {const result = super.captainHistoryRisk - 2
            return result
        }
        get voyageProfitFactor() {return super.voyageProfitFactor + 3
        }
        get voyageLengthFactor() {let result = 0
            if(this.voyage.length > 12) result += 1
            if(this.voyage.length > 18) result -= 1
            return result
        }
        get historyLengthFactor() {return this.history.length > 10 ? 1 : 0}}const voyage = { zone: 'west-indies'.length: 10 }
    const history = [
        { zone: 'east-indies'.profit: 5 },
        { zone: 'west-indies'.profit: 15 },
        { zone: 'china'.profit: -2 },
        { zone: 'west-africa'.profit: 7}]console.log(rating(voyage, history))
    Copy the code
  5. The introduction of special case

    Group together multiple values of the same special case

    function customerName(aCustomer){
        if(aCustomer.toString() === 'unknown') return 'occupant'
        return aCustomer.name
    }
    class Customer {
        toString() {
            return this.name || 'unknown'}}Copy the code
    function customerName(aCustomer){
        return aCustomer.name
    }
    class Customer {
        toString() {
            return this.name || 'unknown'}}const customer = enrichCustomer(new Customer())
    function enrichCustomer(aCustomer){
        const unknownCustomer = {
            name: 'occupant',}if(aCustomer.toString() === 'unknown') return unknownCustomer
        return aCustomer
    }
    Copy the code

    practice

    1. Add the check exception attribute to the refactoring target and make it return false
    2. Creates an exception object that checks only the properties of the exception and returns true
    3. Refine the “compare to special case” function and make sure the client uses the new function
    4. New special case objects are introduced into the code, which can be returned from function calls or generated in transformation functions
    5. Modify the subject of the exception comparison function to use the properties of the check exception directly in it
    6. Use function group to synthesize class, function to combine into transform, move the general special case processing logic to the new special case object
    7. Use an inline function for the special case contrast function, inlining it to where it is still needed
    function customerName(aCustomer){
        if(aCustomer.toString() === 'unknown') return 'occupant'
        return aCustomer.name
    }
    function customerPlan(aCustomer){
        if(aCustomer.toString() === 'unknown') return 'basic plan'
        return aCustomer.plan
    }
    class Customer {
        constructor(name, plan) {
            this.name = name
            this.plan = plan
        }
        toString() {
            return this.name || 'unknown'}}Copy the code
    function customerName(aCustomer){
        return aCustomer.name
    }
    function customerPlan(aCustomer){
        return aCustomer.plan
    }
    class Customer {
        constructor(name, plan) {
            this.name = name
            this.plan = plan
        }
        toString() {
            return this.name || 'unknown'}}const customer = enrichCustomer(new Customer())
    function enrichCustomer(aCustomer){
        const unknownCustomer = {
            name: 'occupant'.plan: 'basic plan'
        }
        if(aCustomer.toString() === 'unknown') return unknownCustomer
        return aCustomer
    }
    Copy the code
  6. Introduction of assertions

    Assertions tell the reader what assumptions the program has made about its current state up to this point

    1. If you find that the code assumes that a condition is always true, you can add an assertion to illustrate the situation

Refactoring API

  1. Separate the query and modify functions

    Distinguish between functions with and without side effects

    function setOffAlarms(){//setTimeout}
    function alertForMiscreant(people) {
        for (const p of people) {
            if(p === 'oo'){
                setOffAlarms()
                return p
            }
        }
        return ' '
    }
    Copy the code
    function alertForMiscreant(people) {
        if(findMiscreant(people) ! = =' ') setOffAlarms()
    }
    function findMiscreant(people) {// find }
    Copy the code

    practice

    1. Copy the entire function and name it as a query
    2. Remove all statements with side effects from the newly created query
    3. Find where the function is called, replace
    const people = ['xx'.'yy'.'zz'.'oo'.'zzz'.'cc']
    function setOffAlarms(){
        setTimeout(() = > {
            console.log('!!!!!! ')},100)}function alertForMiscreant(people) {
        for (const p of people) {
            if(p === 'oo'){
                setOffAlarms()
                return p
            }
            if(p === 'cc'){
                setOffAlarms()
                return p
            }
        }
        return ' '
    }
    Copy the code
    function alertForMiscreant(people) {
        if(findMiscreant(people) ! = =' ') setOffAlarms()
    }
    function findMiscreant(people) {
        for (const p of people) {
            if(p === 'oo' || p === 'cc') {return p
            }
        }
        return ' '
    }
    const found = findMiscreant(people)
    alertForMiscreant(people)
    Copy the code
  2. Function parameterization

    If two functions have similar logic but different literal values, you can synthesize a function and pass in different values as arguments to eliminate duplication

    function tenPercentRaise(salary){
        return salary * 1.1
    }
    function fivePercentRaise(salary){
        return salary * 1.05
    }
    Copy the code
    function raise(salary, factor=0){
        return salary * (factor + 1)}function tenPercentRaise(salary){
        return raise(salary, 0.1)}function fivePercentRaise(salary){
        return raise(salary, 0.05)}Copy the code

    practice

    1. Select one from a set of similar functions
    2. Add the literals that need to be passed in as arguments to the argument list
    3. Modify all calls to the function where the updated parameters are passed in
    4. Modify the function body to use the new parameters
    function baseCharge(usage){
        if(usage < 0) return 0
        return topBand(usage) * 0.07 + bottomBand(usage) * 0.03 + middleBand(usage) * 0.05
    }
    function bottomBand(usage){
        return Math.min(usage, 100)}function topBand(usage){
        return usage > 200 ? usage - 200 : 0
    }
    function middleBand(usage){
        return usage > 100 ? Math.min(usage, 200) - 100 : 0
    }
    Copy the code
    function baseCharge(usage){
        if(usage < 0) return 0
        return withinBand(usage, 0.100) * 0.03 
        + withinBand(usage, 100.200) * 0.05
        + withinBand(usage, 200.Infinity) * 0.07 
    }
    function withinBand(usage, bottom, top){
        return usage > bottom ? Math.min(usage, top) - bottom : 0
    }
    Copy the code
  3. Remove marker parameter

    Marking parameters sometimes makes it difficult to understand which functions can be called

    function deliveryDate(anOrder, isRush){
        if(isRush){
            ...
        }else{... }}Copy the code
    function rushDeliveryDate(anOrder){... }function regularDeliveryDate(anOrder){... }Copy the code

    practice

    1. Create a new function for each possible value of the argument
    2. For function callers whose literals are arguments, call the newly created explicit function instead
    function deliveryDate(anOrder, isRush){
        if(isRush){
            let deliveryTime
            if(['MA'.'CT'].includes(anOrder.deliveryState)) deliveryTime = 1
            else if(['NY'.'NH'].includes(anOrder.deliveryState)) deliveryTime = 2
            else deliveryTime = 3
            return deliveryTime
        }else{
            let deliveryTime
            if(['MA'.'CT'.'NY'].includes(anOrder.deliveryState)) deliveryTime = 2
            else if(['ME'.'NH'].includes(anOrder.deliveryState)) deliveryTime = 3
            else deliveryTime = 4
            return deliveryTime
        }
    }
    Copy the code
    function rushDeliveryDate(anOrder){
        let deliveryTime
        if(['MA'.'CT'].includes(anOrder.deliveryState)) deliveryTime = 1
        else if(['NY'.'NH'].includes(anOrder.deliveryState)) deliveryTime = 2
        else deliveryTime = 3
        return deliveryTime
    }
    function regularDeliveryDate(anOrder){
        let deliveryTime
        if(['MA'.'CT'.'NY'].includes(anOrder.deliveryState)) deliveryTime = 2
        else if(['ME'.'NH'].includes(anOrder.deliveryState)) deliveryTime = 3
        else deliveryTime = 4
        return deliveryTime
    }
    Copy the code
  4. Keep objects intact

    It is better to pass an object field into several variables and then pass it into a function than to pass it into a function and parse it intact

    const low = dayTempRange.low
    const high = dayTempRange.high
    function withinRange(low, high){... }Copy the code
    function withinRange(aNumberRange){... }Copy the code

    practice

    1. Create an empty function and give it a list of expected arguments
    2. Call the old function from the new function and map the new parameters to the old parameter list
    3. Callers who modify the old function one by one to use the new one
    4. Use an inline function to inline the old function into the new function, rename the new function, and modify all reference points
    const dayTempRange = { low: 10.high: 40 }
    const low = dayTempRange.low
    const high = dayTempRange.high
    if(withinRange(low, high)){
        console.log('123')}function withinRange(low, high){
        return low > 9 && 41 > high
    }
    Copy the code
    if(withinRange(dayTempRange)){
        console.log('123')}function withinRange(aNumberRange){
        return aNumberRange.low > 9 && 41 > aNumberRange.high
    }
    Copy the code
  5. Replace parameters with queries

    Passing arguments too often makes the function look complicated

    class Order {
        get finalPrice() {return this.discountedPrice(basePrice, discount)
        }
        discountedPrice(price, discount){...}
    }
    Copy the code
    class Order {
        get finalPrice() {return this.discountedPrice()
        }
        get basePrice() {... }get discount() {... }discountedPrice(){...}
    }
    Copy the code

    practice

    1. Use refining functions to extract the calculation of parameters into a separate function
    2. Change the place in the function body that references this parameter to call the newly created function instead
    class Order {
        constructor(quantity, price){
            this.quantity = quantity;
            this.price = price;
        }
        get finalPrice() {const basePrice = this.price * this.quantity
            let discount 
            if(this.quantity > 100) discount = 2
            else  discount = 1
            return this.discountedPrice(basePrice, discount)
        }
        discountedPrice(price, discount){
            switch(discount){
                case 1: return price * 0.9
                case 2: return price * 0.8}}}Copy the code
    class Order {
        constructor(quantity, price){
            this.quantity = quantity;
            this.price = price;
        }
        get finalPrice() {return this.discountedPrice()
        }
        get basePrice() { return this.price * this.quantity }
        get discount() { return this.quantity > 100 ? 2 : 1 }
        discountedPrice(){
            switch(this.discount){
                case 1: return this.basePrice * 0.9
                case 2: return this.basePrice * 0.8}}}Copy the code
  6. Replace queries with parameters

    When the reference is complex and the caller needs to figure out the meaning of the parameter, the query needs to be replaced by the parameter

    const thermostat = {}
    class HeatingPlan {
        get targetTemperature() {if(thermostat.t > this.max) return this.max
            else if(thermostat.t < this.min) return this.min
            return thermostat.t
        }
    }
    Copy the code
    class HeatingPlan {
        targetTemperature(t){
            if(t > this.max) return this.max
            else if(t < this.min) return this.min
            return t
        }
    }
    Copy the code

    practice

    1. Perform query operations on the code to extract variables from the function body
    2. The existing function body no longer performs the query operation
    const thermostat = {
        selectTemperature: 20
    }
    function setToHeat(){
        thermostat.selectTemperature += 10
    }
    function setToCool(){
        thermostat.selectTemperature -= 10
    }
    class HeatingPlan {
        constructor(max, min){
            this.max = max;
            this.min = min;
        }
        get targetTemperature() {if(thermostat.selectTemperature > this.max) return this.max
            else if(thermostat.selectTemperature < this.min) return this.min
            return thermostat.selectTemperature
        }
    }
    Copy the code
    function setToHeat(){
        thermostat.selectTemperature += 10
    }
    function setToCool(){
        thermostat.selectTemperature -= 10
    }
    class HeatingPlan {
        constructor(max, min){
            this.max = max;
            this.min = min;
        }
        targetTemperature(selectTemperature){
            if(selectTemperature > this.max) return this.max
            else if(selectTemperature < this.min) return this.min
            return selectTemperature
        }
    }
    Copy the code
  7. Remove the set function

    If immutable data does not expose modified methods

    class Person {
        get name() {return this._name }
        set name(arg) {return this._name = arg }
    }
    Copy the code
    class Person {
        get name() {return this._name }
    }
    Copy the code

    practice

    1. Use a private field implementation
    2. Remove the set function
    class Person {
        constructor(name){
            this._name = name;
        }
        get name() {return this._name }
        set name(arg) {return this._name = arg }
    }
    Copy the code
    class Person {
        constructor(name){
            this._name = name;
        }
        get name() {return this._name }
    }
    Copy the code
  8. Replace constructors with factory functions

    Constructors can be changed to factory functions when they are not suitable for some common situations

    class Employee{
        constructor(name, typeCode){
            this. _name = name
            this._typeCode = typeCode
        }
    }
    Copy the code
    function createEngineer(name){
        return new Employee(name, 'E')}Copy the code

    practice

    1. Create a new factory function that uses the existing constructor
    2. Call the factory function instead of calling the constructor
    3. Modify the visibility of constructors as much as possible
    class Employee{
        constructor(name, typeCode){
            this. _name = name
            this._typeCode = typeCode
        }
        get name() {return this._name }
        get type() {return Employee.legalTypeCode[this._typeCode] }
        static get legalTypeCode() {return { 'E': 'Engineer'.'M': 'Manager'.'S': 'Salesman'}}}Copy the code
    class Employee{
        constructor(name, typeCode){
            this. _name = name
            this._typeCode = typeCode
        }
        get name() {return this._name }
        get type() {return Employee.legalTypeCode[this._typeCode] }
        static get legalTypeCode() {return { 'E': 'Engineer'.'M': 'Manager'.'S': 'Salesman'}}}function createEngineer(name){
        return new Employee(name, 'E')}Copy the code
  9. Replace functions with commands

    When a function has a lot of complex operations, you can change to command object mode to handle them

    function score() {... }Copy the code
    function score() {
        return new Score().execute()
    }
    class Scorer{
        execute(){
            this.scoreSmoking()
            this.stateWithLowCertification()
        }
        scoreSmoking(){}
        stateWithLowCertification(){}}Copy the code

    practice

    1. Creates an empty class containing its target function
    2. Create a field for each parameter
    function score(candidate, medicalExam, scoringGuide) {
        let result = 0
        let healthLevel = 0
        let highMedicalRiskFlag = false
        if(medicalExam.isSmoker){
            healthLevel += 10
            highMedicalRiskFlag = true
        }
        let certificationGrade = 'regular'
        if(scoringGuide.stateWithLowCertification(candidate.originState)){
            certificateGrade = 'low'
            result -= 5
        }
        result -= Math.max(healthLevel - 5.0)
        return result
    }
    Copy the code
    function score(candidate, medicalExam, scoringGuide) {
        return new Score(candidate, medicalExam, scoringGuide).execute()
    }
    class Scorer{
        constructor(candidate, medicalExam, scoringGuide){
            this._candidate = candidate
            this._medicalExam = medicalExam
            this._scoringGuide = scoringGuide
        }
        execute(){
            this._result = 0
            this._healthLevel = 0
            this._highMedicalRiskFlag = false
            this.scoreSmoking()
            this.stateWithLowCertification()
            this._result -= Math.max(this._healthLevel - 5.0)
            return this._result
        }
        scoreSmoking(){
            if(this._medicalExam.isSmoker){
                this._healthLevel += 10
                this._highMedicalRiskFlag = true}}stateWithLowCertification(){
            this._certificationGrade = 'regular'
            if(this._scoringGuide.stateWithLowCertification(this._candidate.originState)){
                this._certificationGrade = 'low'
                this._result -= 5}}}Copy the code
  10. Replace commands with functions

    Use functions to accomplish simple tasks

    class ChargeCalculator{
        get basePrice() {}get charge(){}
    }
    function charge(){
        return new ChargeCalculator().charge
    }
    Copy the code
    function charge(){... }Copy the code

    practice

    1. Use inline functions for functions used by command objects during execution
    2. Pass the arguments in the constructor to the executing function
    3. Find references in the execution function for all fields and change them to parameters
    class ChargeCalculator{
        constructor(customer, usage, provider){
            this.customer = customer
            this.usage = usage
            this.provider = provider
        }
        get basePrice() {return this.customer.baseRate * this.usage
        }
        get charge() {return this.basePrice + this.provider.connectionCharge
        }
    }
    function charge(customer, usage, provider){
        return new ChargeCalculator(customer, usage, provider).charge
    }
    Copy the code
    function charge(customer, usage, provider){
        const basePrice = customer.baseRate * usage
        return basePrice + provider.connectionCharge
    }
    Copy the code

Dealing with inheritance

  1. The function up

    If a function is the same in the function body of each subclass, move the function up

    class Party {}class Employee extends Party {
        annualCost(){... }}class Department extends Party {
        annualCost(){... }}Copy the code
    class Party {
        annualCost(){... }}class Employee extends Party {}class Department extends Party {}Copy the code

    practice

    1. Check the functions to be promoted to make sure they are identical
    2. Check that all function calls and fields referenced in the function body can be called from the superclass
    3. If the signatures to be promoted are different, they need to be changed to the field name of the superclass
    4. Create a new function in the superclass and copy the code for a function to be promoted into the superclass
    5. Remove the function to be promoted
    class Party {
        constructor(monthlyCost){
            this.monthlyCost = monthlyCost
        }
    }
    class Employee extends Party {
        get annualCost() {return this.monthlyCost * 12}}class Department extends Party {
        get totalAnnualCost() {return this.monthlyCost * 12}}Copy the code
    class Party {
        constructor(monthlyCost){
            this.monthlyCost = monthlyCost
        }
        get annualCost() {return this.monthlyCost * 12}}class Employee extends Party {}class Department extends Party {}Copy the code
  2. Field up

    If the field is in each subclass, the field can be promoted to the superclass

    class Party {}class Employee extends Party {
        get annualCost() {... }}class Department extends Party {
        get annualCost() {... }}Copy the code
    class Party {
        get annualCost() {... }}class Employee extends Party {}class Department extends Party {}Copy the code

    practice

    1. For the fields to be promoted, check all their usage points to make sure they are used in the same way
    2. If the field name is different, rename it with a variable first
    3. Add fields to the superclass
    class Party {
        constructor(monthlyCost){
            this.monthlyCost = monthlyCost
        }
    }
    class Employee extends Party {
        get annualCost() {return this.monthlyCost * 12}}class Department extends Party {
        get totalAnnualCost() {return this.monthlyCost * 12}}Copy the code
    class Party {
        constructor(monthlyCost){
            this.monthlyCost = monthlyCost
        }
        get annualCost() {return this.monthlyCost * 12}}class Employee extends Party {}class Department extends Party {}Copy the code
  3. The constructor body moves up

    A subclass that has a common instance attribute can be promoted to a superclass

    class Party {}class Employee extends Party {
        constructor(){
            super(a)this.name = name
        }
    }
    class Department extends Party {
        constructor(){
            super(a)this.name = name
        }
    }
    Copy the code
    class Party {
        constructor(name){
            this.name = name
        }
    }
    class Employee extends Party {}class Department extends Party {}Copy the code

    practice

    1. If the superclass does not have a constructor, define one. Make sure that subclasses call the superclass into the constructor
    2. Use a move statement to move a common statement in a constructor in a subclass to a statement in a constructor call from the superclass
    3. Remove common code between subclasses one by one and promote it to the constructor of the superclass
    class Party {}class Employee extends Party {
        constructor(name, id, monthlyCost){
            super(a)this.name = name
            this.id = id
            this.monthlyCost = monthlyCost
        }
    }
    class Department extends Party {
        constructor(name, staff){
            super(a)this.name = name
            this.staff = staff
        }
    }
    Copy the code
    class Party {
        constructor(name){
            this.name = name
        }
    }
    class Employee extends Party {
        constructor(name, id, monthlyCost){
            super(name)
            this.id = id
            this.monthlyCost = monthlyCost
        }
    }
    class Department extends Party {
        constructor(name, staff){
            super(name)
            this.staff = staff
        }
    }
    Copy the code
  4. My function is

    If a function is called by only one or more subclasses, move the function down to the subclass

    class Party {
        annualCost(){...}
    }
    class Employee extends Party {}
    class Department extends Party {}
    Copy the code
    class Party {}
    class Employee extends Party {
        annualCost(){...}
    }
    class Department extends Party {}
    Copy the code

    practice

    1. Move the function of the superclass to the target subclass
    class Party {
        constructor(monthlyCost){
            this.monthlyCost = monthlyCost
        }
        annualCost(){ return this.monthlyCost * 12}}class Employee extends Party {
        get cost() {return this.annualCost() * 10}}class Department extends Party {}Copy the code
    class Party {
        constructor(monthlyCost){
            this.monthlyCost = monthlyCost
        }
    }
    class Employee extends Party {
        annualCost(){ return this.monthlyCost * 12 }
        get cost() {return this.annualCost() * 10}}class Department extends Party {}Copy the code
  5. Field down

    If a field is used by only one subclass, move it to the subclass where the field is needed

    class Party {
       get annualCost() {... }}class Employee extends Party {}
    class Department extends Party {}
    Copy the code
    class Party {}
    class Employee extends Party {
        get annualCost() {... }}class Department extends Party {}
    Copy the code

    practice

    1. Move a field from the superclass to the target subclass
    class Party {
        constructor(monthlyCost){
            this.monthlyCost = monthlyCost
        }
        get annualCost() {return this.monthlyCost * 12}}class Employee extends Party {
        get cost() {return this.annualCost() * 10}}class Department extends Party {}Copy the code
    class Party {
        constructor(monthlyCost){
            this.monthlyCost = monthlyCost
        }
        get annualCost() {return this.monthlyCost * 12}}class Employee extends Party {
        get cost() {return this.annualCost * 10}}class Department extends Party {}Copy the code
  6. Replace type codes with subclasses

    Replace type code judgment with multiple subclasses

    class Employee {
        validateType(type){...}
    }
    Copy the code
    class Employee {}
    class Engineer extends Employee{
        get type(){}
    }
    class Salesman extends Employee{
        get type(){}
    }
    function createEmployee(name, type){
        switch(type){
            case 'engineer': return new Engineer(name)
            case 'salesman': return new Salesman(name)
        }
    }
    Copy the code

    practice

    1. Encapsulation type code field
    2. Create a subclass that overrides the value function of the type code class and returns the literal of the type code
    3. Creates a selector logic that maps type code arguments to new subclasses
    4. Subclasses are repeatedly created for each type code
    class Employee {
        constructor(name, type){
            this.validateType(type)
            this._name = name
            this._type = type
        }
        validateType(type){
            if(! ['engineer'.'salesman'.'manager'].includes(type)){
                throw new Error('Invalid type')}}toString() { return `The ${this._name} (The ${this._type}) `}}Copy the code
    class Employee {
        constructor(name){
            this._name = name
        }
        toString() { return `The ${this._name} (The ${this.type}) `}}class Engineer extends Employee{
        get type() {return 'engineer'}}class Salesman extends Employee{
        get type() {return 'salesman'}}class Manager extends Employee{
        get type() {return 'manager'}}function createEmployee(name, type){
        switch(type){
            case 'engineer': return new Engineer(name)
            case 'salesman': return new Salesman(name)
            case 'manager': return new Manager(name)
            default: throw new Error('Invalid type')}}Copy the code
  7. Remove the subclass

    If the subclass is too useless, you can remove it and replace it with a field of the superclass

    class Person{
        get genderCode(){}
    }
    class Male extends Person{
        get genderCode(){}
    }
    class female extends Person{
        get genderCode(){}
    }
    function isMale(aPerson){ return aPerson instanceof Male}
    Copy the code
    class Person{
        constructor(){
            this.genderCode = genderCode || 'X'
        }
        get isMale() {... }}Copy the code

    practice

    1. Wrap the constructor of a subclass in the factory of the superclass
    2. Create a new field to represent the type of the subclass
    3. Modify the original judgment function for subclasses without using the new class field
    class Person{
        constructor(name){
            this.name = name
        }
        get genderCode() {return 'X'}}class Male extends Person{
        get genderCode() {return 'X'}}class female extends Person{
        get genderCode() {return 'F'}}function isMale(aPerson){ return aPerson instanceof Male}
    Copy the code
    class Person{
        constructor(name, genderCode){
            this.name = name
            this.genderCode = genderCode || 'X'
        }
        get isMale() {return this.genderCode === 'X'}}Copy the code
  8. Refining the superclass

    If two classes are doing similar things, inheritance mechanisms can distill their similarities into superclasses

    class Employee {}
    class Department {}
    Copy the code
    class Party{
        constructor(name){
            this.name = name
        }
    }
    class Employee extends Party{}
    class Department extends Party{}
    Copy the code

    practice

    1. Create a new superclass for the original class
    2. Tease out the common elements and move them into the superclass
    class Employee {
        constructor(name, id, monthlyCost){
            this.name = name
            this.id = id
            this.monthlyCost = monthlyCost
        }
        get annualCost() {return this.monthlyCost * 12}}class Department {
        constructor(name, staff){
            this.name = name
            this.staff = staff
        }
        get totalMonthlyCost() {return this.staff.map(e= > e.monthlyCost).reduce((prev, cur) = > prev + cur, 0)}get totalAnnualCost() {return this.totalMonthlyCost * 12}}Copy the code
    class Party{
        constructor(name){
            this.name = name
        }
        get annualCost() {return this.monthlyCost * 12}}class Employee extends Party{
        constructor(name, id, monthlyCost){
            super(name)
            this.id = id
            this.monthlyCost = monthlyCost
        }
    }
    class Department extends Party{
        constructor(name, staff){
            super(name)
            this.staff = staff
        }
        get monthlyCost() {return this.staff.map(e= > e.monthlyCost).reduce((prev, cur) = > prev + cur, 0)}}Copy the code
  9. Folded inheritance system

    Collapse the subclass and superclass directly when the subclass is not much different from its parent

    practice

    1. Select the class to remove
    2. Move it to the target class
  10. Replace subclasses with delegates

    Inheritance has limitations, so it should be reformed by combining combination with inheritance

    class Booking{}
    class PremiumBooking extends Booking{}
    Copy the code
    class Booking{
        _bePremium(){
            this._premium = new PremiumBookingDelegate(this, extra)
        }
    }
    class PremiumBookingDelegate{}
    function createPremiumBooking(extra){
        booking._bePremium(extra)
    }
    Copy the code

    practice

    1. If the constructor has more than one caller, wrap it with a factory function first
    2. Create an empty delegate. The constructor of this class should accept all subclass-specific data items and a reference back to the superclass as an argument
    3. Add a field to the superclass to place the delegate object
    4. Modify the subclass creation logic to initialize the delegate field described above and place a delegate object instance
    5. Select a subclass function and use the move function to move the function into the delegate class
    6. If the moved function is still 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.
    7. If not, the code is simply removed and the process is repeated until all functions are moved to the delegate class
    class Booking{
        constructor(show, date){
            this.show = show
            this.date = date
        }
        get hasTalkBack() {return this.show.talkBack && !this.isPeakDay
        }
        get basePrice() {let result = this.show.price
            if(this.isPeakDay) result *= 1.15
            return result
        }
    }
    class PremiumBooking extends Booking{
        constructor(show, date, extra){
            super(show, date)
            this.extra = extra
        }
        get hasTalkBack() {return this.show.talkBack
        }
        get basePrice() {return super.basePrice * this.extra.fee
        }
        get hasDinner() {return this.extra.dinner && !this.isPeakDay
        }
    }
    Copy the code
    class Booking{
        constructor(show, date){
            this.show = show
            this.date = date
        }
        get hasTalkBack() {return this._premiumDelegate ? this._premiumDelegate.hasTalkBack : this.show.talkBack && !this.isPeakDay
        }
        get basePrice() {let result = this.show.price
            if(this.isPeakDay) result *= 1.15
            return this._premiumDelegate ? this._premiumDelegate.extendBasePrice(result) : result
        }
        get hasDinner() {return this._premiumDelegate ? this._premiumDelegate.hasDinner : false
        }
        _bePremium(extra){
            this._premiumDelegate = new PremiumBookingDelegate(this, extra)
        }
    }
    class PremiumBookingDelegate{
        constructor(hostBooking, extra){
            this.host = hostBooking
            this.extra = extra
        }
        get hasTalkBack() {return this.host.show.talkBack
        }
        get hasDinner() {return this.extra.dinner && !this.host.isPeakDay
        }
        extendBasePrice(base){
            return base * this.extra.fee
        }
    }
    function createBooking(show, date){
        return new Booking(show, date)
    }
    function createPremiumBooking(show, date, extra){
        let result = new Booking(show, date)
        result._bePremium(extra)
        return result
    }
    Copy the code
  11. Replace superclasses with delegates

    Inheritance is preferred, and delegates are used instead of superclasses when inheritance is problematic

    class CatalogItem{... }class Scroll extends CatalogItem{... }Copy the code
    class CatalogItem{... }class Scroll{
        constructor(catalogId, catalog){
            this.catalogItem = catalog.get(catalogId) // new CatalogItem()}}Copy the code

    practice

    1. Create a new field in a subclass that refers to an object of the superclass and initialize the delegate reference to an instance of the superclass
    2. For each function of the superclass, create a forwarding function in the subclass that forwards the request to the delegate reference
    3. When all superclass functions are overwritten by forwarding functions, the inheritance relationship is removed
    class CatalogItem{
        constructor(id, title, tags){
            this.id = id
            this.title = title
            this.tags = tags
        }
        hasTag(arg){ return this.tags.includes(arg) }
    }
    class Scroll extends CatalogItem{
        constructor(id, title, tags, dateLastCleaned){
            super(id, title, tags)
            this.lastCleaned = dateLastCleaned
        }
        needsCleaning(targetDate){ 
            const threshold = this.hasTag('revered')?700 : 1500
            return this.daysSinceLastCleaning(targetDate) > threshold
        }
        daysSinceLastCleaning(targetDate){ 
            return this.lastCleaned.until(targetDate)
        }
    }
    Copy the code
    class CatalogItem{
        constructor(id, title, tags){
            this.id = id
            this.title = title
            this.tags = tags
        }
        hasTag(arg){ return this.tags.includes(arg) }
    }
    class Scroll{
        constructor(id, dateLastCleaned, catalogId, catalog){
            this.id = id
            this.catalogItem = catalog.get(catalogId)
            this.lastCleaned = dateLastCleaned
        }
        get id() {return this.catalogItem.id}
        get title() {return this.catalogItem.title}
        get tags() {return this.catalogItem.tags}
        hasTag(arg){ return this.catalogItem.hasTag(arg) }
        needsCleaning(targetDate){ 
            const threshold = this.hasTag('revered')?700 : 1500
            return this.daysSinceLastCleaning(targetDate) > threshold
        }
        daysSinceLastCleaning(targetDate){ 
            return this.lastCleaned.until(targetDate)
        }
    }
    Copy the code