preface

Many of you have encountered code that is riddled with if and else, and in the face of such a mess, simply adding incremental changes often leads to more complexity and less readability. Then it’s time to refactor. Take a few minutes to read this article and you may find it helpful.

Scenario 1: The corresponding name is displayed based on status

Optimization Solution 1: Object

const statusStr = {

  '1''To be paid'.

  '2''To be shipped'.

  '3''Shipped'.

  '4''Transaction completed'.

  '5''Transaction closed'.

  'default'' '.

}



const getStatus = (status) = >{

  return statusStr[status] || statusStr['default']

}

Copy the code

The judgment condition is used as the attribute name of the object, and the processing logic is used as the attribute value of the object. When the button is clicked, the logical judgment is made by the way of object attribute search.

Optimization solution 2: Map object

const statusStr = new map([

  ['1': ['To be paid']],

  ['2': ['To be shipped']],

  ['3': ['Shipped']],

  ['4': ['Transaction completed']],

  ['5': ['Transaction closed']],

  ['default': [' ']],

])



const getStatus = (status) = >{

  let actions = statusStr.get(status) || statusStr.get('default')

  return  actions[0];

}

Copy the code

What’s the difference between a Map Object and an Object?

An object usually has its own prototype, so an object always has a “prototype” key. The keys of an object can only be strings or Symbols, but the keys of a Map can be any value. You can easily get the number of key-value pairs for a Map using the size attribute, whereas the number of key-value pairs for an object can only be determined manually.

Scenario 2: Multiple condition names

Now, to upgrade the problem a little bit, where once the button was clicked only to determine the status, now you need to determine the user’s identity: “For example:”

const onButtonClick = (status,identity) = >{

  if(identity == 'guest') {

    if(status == 1) {

      //do sth

    }else if(status == 2) {

      //do sth

    }else if(status == 3) {

      //do sth

    }else if(status == 4) {

      //do sth

    }else if(status == 5) {

      //do sth

    }else {

      //do sth

    }

  }else if(identity == 'master') {

    if(status == 1) {

      //do sth

    }else if(status == 2) {

      //do sth

    }else if(status == 3) {

      //do sth

    }else if(status == 4) {

      //do sth

    }else if(status == 5) {

      //do sth

    }else {

      //do sth

    }

  }

}

Copy the code

As we saw in the example above, when you upgrade your logic to binary judgment, you double the amount of judgment, and you double the amount of code, so how can you write cleaner?

Optimization scheme 1: Condition is stored in a Map object in the form of character concatenation

const actions = new Map([

  ['guest_1', () = > {/*do sth*/}].

  ['guest_2', () = > {/*do sth*/}].

  ['guest_3', () = > {/*do sth*/}].

  ['guest_4', () = > {/*do sth*/}].

  ['guest_5', () = > {/*do sth*/}].

  ['master_1', () = > {/*do sth*/}].

  ['master_2', () = > {/*do sth*/}].

  ['master_3', () = > {/*do sth*/}].

  ['master_4', () = > {/*do sth*/}].

  ['master_5', () = > {/*do sth*/}].

  ['default', () = > {/*do sth*/}].

])



const onButtonClick = (identity,status) = >{

  let action = actions.get(`${identity}_${status}`) || actions.get('default')

  action()

}

Copy the code

The core logic of the above code is: concatenate two conditions into a string, and search and execute the Map object with the conditional concatenation string as the key and the processing function as the value. This writing method is especially useful for multivariate condition judgment.

Optimization scheme 2: Condition is stored in Object Object in the form of character splicing

const actions = {

  'guest_1':(a)= >{/*do sth*/},

  'guest_2':(a)= >{/*do sth*/},

  //....

}



const onButtonClick = (identity,status) = >{

  let action = actions[`${identity}_${status}`] || actions['default']

  action()

}

Copy the code

Optimization scheme 3: Condition is stored in Map as Object

If this is a bit awkward, there is an alternative, which is to use a Map Object with an Object as the key:

const actions = new Map([

  [{identity:'guest'.status:1= > {}, ()/*do sth*/}].

  [{identity:'guest'.status:2= > {}, ()/*do sth*/}].

  / /...

])



const onButtonClick = (identity,status) = >{

  let action = [...actions].filter(([key,value]) = >(key.identity === identity && key.status === status))

  action.forEach(([key,value]) = >value())

}

Copy the code

Scenario 3: Perform operations based on status

Here’s an example:

/ * *

OrderType: orderType, 1.500 yuan deposit user, 2.200 yuan deposit user, 3. The average user

Pay: Whether you have paid true/false (users who have ordered but have not paid need to enter normal purchase mode)

Stock: Product inventory, users who have paid the deposit are not limited

* /


let handleOrder = (orderType, pay, stock) = > {

  if ( orderType === 1) {// 500 yuan deposit purchase mode

      if ( pay === true) {// A deposit has been paid

          console.log( '500 yuan deposit, get 100 coupons' );

      }else// If no deposit is paid, downgrade to normal purchase mode

          if ( stock > 0) {// There are still phones available for general purchase

              console.log( 'Regular purchase, no coupons' );

          }else{

              console.log( 'Lack of mobile phone stock' );

          }

      }

  }

  else if ( orderType === 2) {// 200 yuan deposit purchase mode

      if ( pay === true) {

          console.log( '200 yuan deposit, get 50 coupons' );

      }else{

          if ( stock > 0) {

              console.log( 'Regular purchase, no coupons' );

          }else{

              console.log( 'Lack of mobile phone stock' );

          }

      }

  }

  else if ( orderType === 3) {

      if ( stock > 0) {

          console.log( 'Regular purchase, no coupons' );

      }else{

          console.log( 'Lack of mobile phone stock' );

      }

  }

};

handleOrder( 1 , true.500); // Output: 500 yuan deposit pre-order, get 100 coupons

Copy the code

Optimization Plan 1: Chain of responsibility mode

let order500 = (orderType, pay, stock) = > {

    if ( orderType === 1 && pay === true) {

        console.log( '500 yuan deposit, get 100 coupons' );

    }else{

        order200( orderType, pay, stock ); // Pass the request to the 200 yuan order

    }

};

let order200 = (orderType, pay, stock) = > {

    if ( orderType === 2 && pay === true) {

        console.log( '200 yuan deposit, get 50 coupons' );

    }else{

        orderNormal( orderType, pay, stock ); // Pass the request to the normal order

    }

};

let orderNormal = (orderType, pay, stock) = > {

    if ( stock > 0) {

        console.log( 'Regular purchase, no coupons' );

    }else{

        console.log( 'Lack of mobile phone stock' );

    }

};

order500( 1 , true.500); // Output: 500 yuan deposit pre-order, get 100 coupons

Copy the code

Tips: The chain of responsibility pattern is defined to avoid coupling between the sender and receiver of the request by giving multiple objects the opportunity to process the request, connecting the objects into a chain and passing the request along the chain until one object processes it.

Optimization 2: Functional programming

import R from 'ramda'



var fn = R.cond([

  [R.equals(0),   R.always('water freezes at 0 ° C')].

  [R.equals(100), R.always('the water boils at 100 ° C')].

  [R.T,           temp => 'nothing special happens at ' + temp + '° C']

]);



fn(0); //=> 'freezes at 0°C'

fn(50); //=> 'nothing special happens at 50°C'

fn(100); //=> 'Water displayed at 100°C'

Copy the code

To solve this problem, use functional programming libraries such as Ramda, 🔗 link: ramda.cn/docs/#cond

Scenario 4: Do different processing depending on the scope

For example, if the credit score of a platform exceeds 700-950, it means excellent credit, 650-700 excellent credit, 600-650 good credit, 550-600 medium credit and 350-550 poor credit.

function showGrace(grace{

    let _level=' ';

    if(grace>=700) {

        _level='Excellent credit'

    }

    else if(grace>=650) {

        _level='Excellent credit'

    }

    else if(grace>=600) {

        _level='Good credit'

    }

    else if(grace>=550) {

        _level='Medium credit'

    }

    else{

        _level='Poor credit'

    }

    return _level;

}

Copy the code

Solution 1: Use a look-up table to separate configuration data from service logic

function showGrace(grace,level,levelForGrace{

    for(let i=0; i<level.length; i++){

        if(grace>=level[i]){

            return levelForGrace[i];

        }

    }

    // If it does not exist, then the score is very low

    return levelForGrace[levelForGrace.length- 1];

}

let graceForLevel=[700.650.600.550];

let levelText=['Excellent credit'.'Excellent credit'.'Good credit'.'Medium credit'.'Poor credit'];

Copy the code

Using a separate form of configuration data and business logic benefits: (1) Modifying configuration data is cheaper and less risky than modifying service logic. (2) The source of configuration data and modification can be flexible. (3) The configuration and service logic are separated, so that the code to be modified can be found more quickly. [Brief analysis] Replace if-else and switch schemes in specific scenarios

summary

There are many cases where we can use more flexible alternatives to if else and switch, but not all if else needs to be replaced, depending on the situation.