Refactoring: Improve the design of existing code – buy book links

Step one in refactoring: Make sure your refactoring code has a solid set of tests! Step one in refactoring: Make sure your refactoring code has a solid set of tests! Step one in refactoring: Make sure your refactoring code has a solid set of tests!

The basic concept

Refactoring: an adjustment of the internal structure of software to improve the understandability and reduce the modification cost without changing the observable behavior pairs of software

purpose

  1. Improve software design
  2. Make software easier to understand
  3. Help find hidden bugs
  4. Speed up programming

Reconstruction time

  1. Preliminary refactoring: It is easier to add new features
  2. Refactoring to help you understand: Make your code more understandable
  3. Garbage pick refactoring: Make your code a little better each time you clean it up
  4. Planned refactoring
  5. Substitutability refactoring: replacing some dependent modules

The bad smell of code

  1. Cryptic naming: a good name that clearly identifies its function and usage (change function declarations, rename variables, rename fields)
  2. Duplicate code: Seeing the same code in more than one place (refining function, moving statement, function moving up)
  3. Long functions: The longer the function, the more complicated it is to read (refine functions, replace temporary variables with queries, introduce parameter objects, replace functions with commands)
  4. Long argument list: Long arguments can be confusing (queries instead of temporary variables, parameter objects)
  5. Global data: It can be changed anywhere in the project, and there is no way to locate where the change occurred (encapsulated variables)
  6. Variable data: Data modification is prone to unexpected bugs (encapsulating variables, splitting variables)
  7. Divergent variations: a function is responsible for only one type of context state (extract function)
  8. Shotgun change: changes need to be made in different classes (inline functions, inline classes)
  9. Attachment plot: Reduce interaction between modules (transfer function)
  10. Data muddle: the way in which data is aggregated and broken down into smaller granularity (refining classes, parameter objects)
  11. Base-type bias: Processing data with the wrong data type (object instead of base-type)
  12. Repetitive switch: Find all branch confirmations when you want to add a selected branch (polymorphic substitution conditional expression)
  13. Loop statements: You need to read through the code to understand the semantics inside the loop (pipes instead of loops)
  14. Redundant elements: Program elements add code structure to support change, facilitate reuse, but sometimes are simple functions (inline functions, inline classes)
  15. Talk about commonality: Give up situations you don’t need, such as various hooks, and do limited development (remove dead code)
  16. Temporary fields: Fields created only for a special case
  17. Too long message chain: object A accesses object B, object B accesses object C… Until the E object (hides the delegate relationship)
  18. Middleman: Over-delegating (remove the middleman)
  19. Insider trading: Exchanging data between modules (moving functions, moving fields, hiding delegate relationships)
  20. Too big a class: A single class does too much (refining classes)
  21. Similar classes: Classes of the same type should have the same interface (changing function declarations, moving functions, refining superclasses)
  22. Pure data class: has some fields and functions to access them (encapsulating records)
  23. Rejected gift: Subclass does not need most fields/functions of superclass (delegates instead of subclass)
  24. Comments: When you need to write comments, first try refactoring to make all the comments redundant

Refactoring techniques

  1. Extract function (inline function)

    Distilling the code into a separate function when you need to spend time looking through a piece of code to figure out what it’s doing

     function printOwing() {...// printBanner.// printDetails
     }
    Copy the code
     function printOwing() {
         printBanner()
         printDetails()
         return
         function printBanner(){... }function printDetails(){... }}Copy the code

    Practice:

    1. Create a new function, named after the function intent
    2. Copy the code from the source function into the new function
    3. Take a close look at the abstracted code, the variables referenced by the scope, and determine if they need to be passed in as arguments
    4. Compile after all variables are processed
     const invoices = [
         {
             customer: 'BigCo'.outstanding: [{amount: 10
                 },
                 {
                     amount: 20}]}, {customer: 'Helle'.outstanding: [{amount: 30
                 },
                 {
                     amount: 40}}]]function printOwing(invoice) {
         console.log('-- -- -- -- -- -- --)
         console.log('--- Owes ---')
         console.log('-- -- -- -- -- -- --)
         let amounts = 0
         for(let { amount } of invoice.outstanding) {
             amounts += amount
         }
         console.log(`name: ${invoice.customer}`)
         console.log(`amount: ${amounts}`)}Copy the code
    function printOwing(invoice) {
        printBanner()
        let outstanding = amountFor(invoice)
        printDetails(invoice, outstanding)
        return
        function printBanner(){
            console.log('-- -- -- -- -- -- --)
            console.log('--- Owes ---')
            console.log('-- -- -- -- -- -- --)}function amountFor(invoice){
            return invoice.outstanding.reduce((prev, item) = > prev + item.amount, 0)}function printDetails(invoice, outstanding){
            console.log(`name: ${invoice.customer}`)
            console.log(`amount: ${outstanding}`)
        }
    }
    invoices.forEach(printOwing)
    Copy the code
  2. Inline function (extract function)

    Some of the already readable code can be directly inlined within the source function, reducing the level of indirection

    function reportLines(aCustomer){... gatherCustomerData(...)return
        function gatherCustomerData(){...}
    }
    Copy the code
    function reportLines(aCustomer){... .// gatherCustomerData
        return lines
    }
    Copy the code

    practice

    1. Check the function to make sure it is not polymorphic
    2. Find all the call points for this function
    3. Replace the call point with the function body
    4. After the replacement, the tests are executed
    const customer = {
        name: 'BigCo'.location: 'sz'
    }
    function reportLines(aCustomer){
        const lines = []
        gatherCustomerData(lines, aCustomer)
        return lines
        function gatherCustomerData(out, aCustomer){
            out.push(['name', aCustomer.name])
            out.push(['location', aCustomer.location])
        }
    }
    Copy the code
    function reportLines(aCustomer){
        const lines = []
        lines.push(['name', aCustomer.name])
        lines.push(['location', aCustomer.location])
        return lines
    }
    Copy the code
  3. Extract variables (inline variables)

    Breaking complex, verbose expressions into variables is easier to read

    function price(order){
        return. }Copy the code
    function price(order){
        let basePrice
        return. }Copy the code

    practice

    1. Make sure the expression you want to refine has no side effects
    2. Declare an immutable variable, make a copy of the expression you want to refine, and assign the result of the expression to the variable
    3. Replace the expression with a new variable
    function price(order){
        // price is base price - quantity discount + shipping
        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
    function price(order){
        // price is base price - quantity discount + shipping
        let basePrice = order.quantity * order.itemPrice
        let quantityDiscount = Math.max(0, order.quantity - 500) * order.itemPrice * 0.05
        let shipping = Math.min(basePrice * 0.1.100)
        return basePrice - quantityDiscount + shipping
    }
    Copy the code
  4. Inline variables (inline variables)

    Local variables can be eliminated by inlining variables

    function price(order){
        let basePrice
        return. }Copy the code
    function price(order){
        return. }Copy the code

    practice

    1. Check whether the expression on the right side of the validation variable assignment statement has side effects
    2. Find the first use of the variable and replace it with the expression to the right of the assignment statement
    function price(order){
        let basePrice = order.basePrice;
        return (basePrice > 1000)}Copy the code
    function price(order){
        return order.basePrice > 1000
    }
    Copy the code
  5. Changing a function declaration

    Change functions and variables to semantically better names

    // before
    function circum (x){... }Copy the code
    // Simple
    function circumference (radius){... }// Complex approach
    function circum (radius){
        return circumference(radius)
        function circumference (radius){...}
    }
    Copy the code

    A simple approach

    1. To migrate a parameter, you need to make sure that no parameter is used in the function body
    2. Modifying a function declaration
    3. Find the reference and replace it

    The migration approach

    1. If necessary, refactor the inside of the function body first to make the subsequent extraction steps easy to unfold
    2. The extract function is used to extract the body of a function into a single function
    3. If the extracted function requires new arguments, follow the simple procedure
    4. Use inline functions for older functions
    function circum (radius){
        return 2 * Math.PI * radius
    }
    Copy the code
    // Simple
    function circumference (radius){
        return 2 * Math.PI * radius
    }
    // Complex approach
    function circum (radius){
        return circumference(radius)
        function circumference (radius){
            return 2 * Math.PI * radius
        }
    }
    Copy the code
    function isNewEndLand(aCustomer){
        return ['MA'.'CT'.'ME'.'VT'.'NH'.'NH'.'RI'].includes(aCustomer.address.state)
    }
    Copy the code
    function isNewEndLand(aCustomer){
        return checkState(aCustomer.address.state)
        function checkState(state){
            return ['MA'.'CT'.'ME'.'VT'.'NH'.'NH'.'RI'].includes(state)
        }
    }
    Copy the code
  6. Encapsulation variable

    Encapsulate variables into function calls for easy modification and data monitoring

    letdefaultOwnerData = {... }function defaultOwner(){
        return Object.assign({}, defaultOwnerData)
    }
    function setDefaultOwner(newOwner){
        return defaultOwnerData = newOwner
    }
    Copy the code

    practice

    1. Create a wrapper function in which variables are accessed and updated
    2. Performing a static check
    3. Modify the code that uses variables one by one to call the appropriate wrapper function
    4. Limit the variability of variables
    let defaultOwnerData = {
        firstName: 'Mt'.lastName: 'Fl'
    }
    function defaultOwner(){
        return Object.assign({}, defaultOwnerData)
    }
    function setDefaultOwner(newOwner){
        return defaultOwnerData = newOwner
    }
    Copy the code
  7. The variable name

    A good name is a good start

    let a = 'xy'
    a = 'dq'
    Copy the code
    let _name = 'xy'
    function name(){ return _name }
    function setName(name){ _name = name }
    Copy the code

    practice

    1. If a variable is widely referenced, it is encapsulated using encapsulated variables
    2. Find the code that uses this variable, and pay attention to the modifications
    let tpHd = '111';
    let result = ' '
    result += `title: ${tpHd}\n`
    tpHd = '222';
    result += `title: ${tpHd}`
    Copy the code
    let _title = '111';
    let result = ' '
    result += `title: ${title()}\n`
    tpHd = setTitle('222');
    result += `title: ${title()}`
    
    function title() { return _title }
    function setTitle(title) { _title = title }
    Copy the code
  8. Import parameter objects

    Using parameter objects instead of multiple parameters

    function readingsOutsideRange(station, min, max){
        return. }Copy the code
    class Range {... }function readingsOutsideRange(station, range){
        return. }Copy the code

    practice

    1. If you don’t have a data structure in place right now, create one
    let station = {
        name: 'ZB1'.readings: [{temp: 47 },
            { temp: 53 },
            { temp: 28 },
            { temp: 53 },
            { temp: 61},],}const min = 30
    const max = 60
    function readingsOutsideRange(station, min, max){
        return station.readings.filter((r) = > r.temp < min || r.temp > max)
    }
    let list = readingsOutsideRange(station, min, max)
    Copy the code
    class Range {
        constructor(min, max) {
            this.min = min
            this.max = max
        }
        contains(arg){
            return arg >= this.min && arg <= this.max
        }
    }
    function readingsOutsideRange(station, range){
        return station.readings.filter(r= >! range.contains(r.temp)) }let range = new Range(min, max)
    let list = readingsOutsideRange(station, range)
    Copy the code
  9. Function groups combine classes

    A set of functions that operate on the same block of data can form a class, which can pass fewer arguments and simplify calls

    constreading = {... }function baseRate(month, year) {... }const baseCharge = ...
    const base = ...
    Copy the code
    class Reading {... }const aReading = new Reading(reading)
    Copy the code

    practice

    1. Encapsulate data shared by multiple functions by encapsulating records
    2. For each function that uses the record structure, it is moved into the new class by a move function
    3. The logic used to process this data record can be extracted and moved to the new class using the extract function
    const reading = {
        customer: 'xy'.quantity: 10.month: 5.year: 2017
    }
    function baseRate(month, year) {
        return month * 0.1 + year * 0.15
    }
    function taxThreshold(year) {
        return year * 10
    }
    const baseCharge = baseRate(reading.month, reading.year) * reading.quantity
    const base = baseRate(reading.month, reading.year) * reading.quantity
    const taxableCharge = Math.max(0, base - taxThreshold(reading.year))
    const amount = calculateBaseCharge(reading)
    function calculateBaseCharge(aReading) {
        return baseRate(aReading.month, aReading.year) * aReading.quantity
    }
    Copy the code
    class Reading {
        constructor(data) {
            this._customer = data.customer
            this._quantity = data.quantity
            this._month = data.month
            this._year = data.year
        }
        get customer() { return this._customer }
        get quantity() { return this._quantity }
        get month() { return this._month }
        get year() { return this._year }
        get baseCharge() {return baseRate(this.month, this.year) * this.quantity
        }
        get taxableCharge() {return Math.max(0.this.baseCharge - taxThreshold(this.year))
        }
    }
    const aReading = new Reading(reading)
    const baseCharge = aReading.baseCharge
    const base = aReading.baseCharge
    const taxableCharge = aReading.taxableCharge
    const amount = aReading.baseCharge
    Copy the code
  10. Function combination transformation

    Take the source data as input, derive the data, and fill in the input and output data with the derived data as fields

    constreading = {... }function baseRate(month, year) {... }const baseCharge = ...
    const base = ...
    Copy the code
    function clone(obj){... }function enrichReading(aReading){
        let result = clone(aReading)
        ...
        return result
    }
    const aReading = enrichReading(reading)
    Copy the code

    practice

    1. Create a transformation function that takes the record as input and returns the value of that record (note that deep copying is best here)
    2. Extract the function and add the result as a field to the enhancement object
    const reading = {
        customer: 'xy'.quantity: 10.month: 5.year: 2017
    }
    function baseRate(month, year) {
        return month * 0.1 + year * 0.15
    }
    function taxThreshold(year) {
        return year * 10
    }
    const baseCharge = baseRate(reading.month, reading.year) * reading.quantity
    const base = baseRate(reading.month, reading.year) * reading.quantity
    const taxableCharge = Math.max(0, base - taxThreshold(reading.year))
    const amount = calculateBaseCharge(reading)
    function calculateBaseCharge(aReading) {
        return baseRate(aReading.month, aReading.year) * aReading.quantity
    }
    Copy the code
    function clone(obj){
        return JSON.parse(JSON.stringify(obj))
    }
    function enrichReading(aReading){
        let result = clone(aReading)
        result.baseCharge = baseRate(aReading.month, aReading.year) * aReading.quantity
        result.taxableCharge = Math.max(0, result.baseCharge - taxThreshold(aReading.year))
        return result
    }
    const aReading = enrichReading(reading)
    const baseCharge = aReading.baseCharge
    const base = aReading.baseCharge
    const taxableCharge = aReading.taxableCharge
    const amount = aReading.baseCharge
    Copy the code
  11. Split phase

    A piece of code that is doing two different things at once is broken up into separate modules

    function priceOrder() {...return price
    }
    Copy the code
    function priceOrder() {
        const priceData = calculatePricingData()
        return applyShipping(priceData)
        function calculatePricingData(){
            return{... }}function applyShipping(priceData){... }}Copy the code

    practice

    1. Extract the two-phase code into individual functions
    2. Introduce a relay data structure that is used as an argument to add the argument list that refines the new function
    3. One by one, “each parameter in Phase 2” is extracted, and if a parameter is used in phase 1, it is moved into the relay data structure
    4. Apply the extract function to the first phase of the code, and let the extract function return the transfer data structure
    / * * * *@param {{ basePrice: number, discountThreshold: number }} product 
    * @param {number} quantity 
    * @param {{discountThreshold: number, discountedFee: number, feePerCase: number}} shippingMethod 
    */
    function priceOrder(product, quantity, shippingMethod) {
        const basePrice = product.basePrice * quantity
        const discount = Math.max(quantity - product.discountThreshold, 0) * product.basePrice
        const shippingPerCase = (basePrice > shippingMethod.discountThreshold) ? shippingMethod.discountedFee : shippingMethod.feePerCase
        const shippingCost = quantity * shippingPerCase
        const price = basePrice - discount + shippingCost
        return price
    }
    Copy the code
    / * * * *@param {{ basePrice: number, discountThreshold: number }} product 
    * @param {number} quantity 
    * @param {{discountThreshold: number, discountedFee: number, feePerCase: number}} shippingMethod 
    */
    function priceOrder(product, quantity, shippingMethod) {
        const priceData = calculatePricingData(product, quantity)
        return applyShipping(priceData, shippingMethod)
        function calculatePricingData(product, quantity){
            const basePrice = product.basePrice * quantity
            const discount = Math.max(quantity - product.discountThreshold, 0) * product.basePrice
            return { basePrice, quantity, discount }
        }
        function applyShipping(priceData, shippingMethod){
            const shippingPerCase = (priceData.basePrice > shippingMethod.discountThreshold) ? shippingMethod.discountedFee : shippingMethod.feePerCase
            const shippingCost = priceData.quantity * shippingPerCase
            return priceData.basePrice - priceData.discount + shippingCost
        }
    }
    Copy the code