You could have written less if else

Keywords: Attribute less IE

“… He thinks that Blub language is enough, and then his thinking has been assimilated by Blub language "- Paul Graham, hackers and painters >

preface

I don't like the shuttle if/else statement in business code. It's complex and bloated. At least in terms of aesthetic feeling, switch is much more elegant than if/else. If cross language comparison is made, I think the pattern matching of ReasonML is much stronger than that of ordinary switch statement. The different writing methods of complex judgment in JS bring different feelings. In this article, I will briefly introduce several writing methods used to replace if/else. Only when we are familiar with more code ideas, can we broaden our thinking. If we can't learn more possibilities of writing code, maybe we will become people controlled by code

IF/ELSE

We take an after-sales process as an example. After a user purchases a product, he or she may contact the merchant for after-sales service due to wrong parts, missing parts, quality problems, inconsistent descriptions, etc., which may involve after-sales support services such as refund / return / replacement / reissue, and the merchant's after-sales service will also affect the user's preference for the merchant. In such a scenario, we assume the following pseudo code:

/* The buyer seeks different after-sales support according to different after-sales reasons */
if (serviceReason === 'Missing parts') {
  const action = randomIn(['refund', 'Reissue'])

  if (action === 'refund') {
    if (store.checked) {
      store.refund(somemoney)
      user.love(1) // Users prefer this store
    } else {
      user.love(-1)
    }
  } else if (action === 'Reissue') {
    if (store.checked) {
      store.mail(goods.AlphaStrike)
      user.love(2)
    } else {
      user.love(-2)
    }
  }
} else if (serviceReason === 'quality problem') {
  const action = randomIn(['refund', 'return goods', 'exchange goods'])

  if (action === 'refund') {
    if (store.checked) {
      store.refund(somemoney)
      user.love(3)
    } else {
      user.love(-3)
    }
  } else if (action === 'return goods') {
    if (store.checked) {
      user.mail(recievedGoods)
      store.refund(somemoney)
      user.love(4)
    } else {
      user.love(-4)
    }
  } else if (action === 'exchange goods') {
    if (store.checked) {
      user.mail(recievedGoods)
      store.mail(goods.AlphaStrike)
      user.love(5)
    } else {
      user.love(-5)
    }
  } else if (action === 'Description mismatch') {
    // do ...
  } else if (action === 'Other reasons') {
    // do ...
  }
}

In this scenario, each after-sales reason oriented after-sales support service content is different. For example, when the wrong parts are missing, the user cannot choose the replacement service. In this way, our judgment conditions become a two-dimensional list of [after-sales reasons * after-sales support services]. At this time, in addition to the different after-sales reasons and after-sales support services, the judgment conditions are properly upgraded to three-dimensional: [after sales reasons * after sales support services * user preferences]

Obviously, the if/else statement is inadequate in this business logic heavy place. The main reasons are as follows:

  • In other words, in the} else if (serviceReason = = = 'quality problem') {sentence, we usually need to find the judgment condition at the end of the line, and some content is not highlighted, so it is often mixed with the ordinary code of the next line, which is dazzling and unrecognizable

  • Code indenting is not clear. When the number of judgment layers increases or the content in curly braces is lengthened, it is always a burden to find the corresponding end position of if statement when reading the code

  • Luxurious line breaking, when the logic in the if/else statement is very short, like else{ user.love (- 5} This code, occupied the position of 3 lines appears too extravagant

Binomial operator / short circuit expression

In some simple expressions, you can use the binocular operator or short-circuit expression to simplify judgment, such as user.love This function can be called in a single sentence, so the following judgment can be simplified completely:

// Not simplified
if (store.checked) {
  store.refund(somemoney)
  user.love(1)
} else {
  user.love(-1)
}

// After simplification
store.checked && store.refund(somemoney)
user.love(store.checked ? 1 : -1)

Comma Operator

By using parentheses and comma operators, we can turn statements into expressions for execution. Flexible use of comma operators can make three items or short circuits support more complex situations:

if (action === 'exchange goods') {
  store.checked && (user.mail(recievedGoods), store.mail(goods.AlphaStrike))
  user.love(store.checked ? 5 : -5)
}

However, the above code is not recommended in the Standard specification. ESLint will warn you that you need to convert this line into if/else writing. Of course, you can also modify the ESLint specification to turn off the warning if you (and team members) like this writing

SWITCH/CASE

When dealing with complex multi branch judgment, most people will choose switch as an alternative to long if/else. What do you think of the following way?

/* The buyer can get different after-sales support according to different after-sales reasons */
switch (serviceReason) {
  case 'Missing parts':
    const action = randomIn(['refund', 'Reissue'])
    switch (action) {
      case 'refund':
        store.checked && store.refund(somemoney)
        user.love(store.checked ? 1 : -1)
        break
      case 'Reissue':
        store.checked && store.mail(goods.AlphaStrike)
        user.love(store.checked ? 2 : -2)
        break
    }
    break
  case 'quality problem':
    const action = randomIn(['refund', 'return goods', 'exchange goods'])
    switch (action) {
      case 'refund':
        store.checked && store.refund(somemoney)
        user.love(store.checked ? 3 : -3)
        break
      case 'return goods':
        store.checked && (user.mail(recievedGoods), store.refund(somemoney))
        user.love(store.checked ? 4 : -4)
        break
      case 'return goods':
        store.checked &&
          (user.mail(recievedGoods), store.mail(goods.AlphaStrike))
        user.love(store.checked ? 5 : -5)
        break
    }
    break
}

Unfortunately, it seems that the switch statement is not much better than if/else. I just want to emphasize that in the above example, all the key words related to judgment (such as switch, case, & &, which belong to the code highlight area) almost exist within the first two words at the beginning of each line. So far, in terms of the difficulty of searching judgment conditions alone, Switch is better than if/else, although this benefit will be gradually dissipated as the number of judgment branches increases (which is a pity)

In fact, there's another thing that I'm sorry about. The switch statement can't be used with the short-circuit operator. Here's an example of a mistake:

switch (serviceReason) {
  case 'quality problem':
    const action = randomIn(['refund', 'return goods', 'exchange goods'])

    store.checked && switch (action) { // It's illegal
      case 'refund': // ...
      case 'return goods': // ...
      case 'return goods': // ...
    }

    if (store.checked) { // It's legal
      switch (action) {
        case 'refund': // ...
        case 'return goods': // ...
        case 'return goods': // ...
      }
    }
}

It's a little uncomfortable. After all, using if judgment will increase indentation. Now let's take a more concise approach

Separation of configuration data and business logic

There is no precondition for the separation of configuration data and business logic (hereinafter referred to as the separation of configuration logic). Like the three-way short-circuit expression, you can start to use it in the code at any time. To use the separation of configuration logic, you usually need to define an object to store the data configuration, and at the same time define a flag as the configuration key. The configuration value can be a function, Array and other values of any type. Where the logic is executed, you only need to compare the judgment condition with the flag to obtain the business logic method under the current judgment condition:

// Business data configuration
const serviceHandler = {
  'Missing parts': {
    'refund': () => (store.checked && store.refund(somemoney), 1),
    'Reissue': () => (store.checked && store.mail(goods.AlphaStrike), 2)
  },
  'quality problem': {
    'refund': () => (store.checked && store.refund(somemoney), 3),
    'return goods': () => (store.checked && (user.mail(recievedGoods), store.refund(somemoney)), 4)
    'return goods': () => (store.checked && (user.mail(recievedGoods), store.mail(goods.AlphaStrike)), 5)
  }
}
// Business logic execution
const handler = serviceHandler[serviceReason] || {} // Search results are 'bottomed out'
const executor = handler[randomIn(Object.keys(handler))] || _ => _

const loveScore = executor() || 0
user.love(store.checked ? loveScore : -loveScore)

Do you have a bright feeling? I like it

In the above code snippet, we separate the data configuration of each state and the code executing the business logic at a certain level, so that the code length is much shorter. Through the comma operator, we can execute the business logic function at the same time, and return the image score of the user to the store

If you don't want to be so radical, maybe the following code is a good practice:

const serviceHandler = {
  'Missing parts': {
    'refund': () => ({ score: 1, cb: () => store.refund(somemoney) }),
    'Reissue': () => ({ score: 2, cb: () => store.mail(goods.AlphaStrike) })
  },
  'quality problem': {
    'refund': () => ({ score: 3, cb: () => store.refund(somemoney) }),
    'return goods': () => ({ score: 4, cb: () => (user.mail(recievedGoods), store.refund(somemoney) }),
    'return goods': () => ({ score: 5, cb: () => (user.mail(recievedGoods), store.mail(goods.AlphaStrike)) })
  }
}
const handler = serviceHandler[serviceReason] || {}
const executor = handler[randomIn(Object.keys(handler))] || _ => _
const handleRes = executor()

handleRes.cb()
user.love(store.checked ? handleRes.score : -handleRes.score)

More flexible data configuration

In the previous section, objects are used for data configuration. If there is a pity, that is, although the value of an attribute can be any type, the attribute itself can only be a string type, which makes it impossible for us to perform the function of logical separation of configuration in some scenarios. Imagine the following scenario: at the end of each month, The merchants will select the fans (the degree of love is greater than or equal to 100) to send the message "thank you" and give 10 yuan coupons, ordinary fans (score 0-100) to send the message "thank you", black fans (score less than 0) to send "sorry" and give 10 Yuan coupons

Wait, the liking score is variable!

Obviously, we can't use objects to process such data (as long as I'm not crazy), as follows:

const scoreMap = {
  '0'() {
    /* ... */
  },
  '1'() {
    /* ... */
  },
  '2'() {
    /* ... */
  },
  '3'() {
    /* ... */
  },
  '4'() {
    /* ... */
  },
  // ...
  '99'() {
    /* ... */
  },
  '100'() {
    sendMsg('thank u')
    sendCoupon(10)
  }
}

The bad news is: maybe it is

However, I have to stress that if the judgment conditions are complex enough - for example, businesses not only give coupons, but also send QQ Red Diamond / Green Diamond / yellow diamond / yellow diamond according to the scores of fans A bunch of gifts to judge - using if/else will still face the code confusion we mentioned in the first section

So the good news is that using configuration logic separation can deal with more complex and confusing scenarios (and you should use it as well). Do you remember the slight regret that we just mentioned that "attributes themselves can only be strings"? Now we are going to solve this problem! See the following code:

// Using Map for data configuration
const scoreHandler = new Map([
  [
    /^score_-[0-9]{1,2}$/,
    () => sendGift(['Green diamond', 'Blue Diamond', 'Red Diamond', 'Yellow diamond', 'Purple Diamond'])
  ],
  [/^score_[0-9]{1}$/, () => sendGift(['Blue Diamond'])],
  [/^score_1[0-9]{1}$/, () => sendGift(['Blue Diamond', 'Red Diamond'])],
  [/^score_2[0-9]{1}$/, () => sendGift(['Blue Diamond', 'Red Diamond', 'Yellow diamond'])],
  [/^score_3[0-9]{1}$/, () => sendGift(['Blue Diamond', 'Red Diamond', 'Yellow diamond', 'Purple Diamond'])]
  // ...
])

const userScore = 15
const validScore = `score_${userScore}`
let handle,
  entriess = scoreHandler.entries()
while ((({ value: handle, done } = entriess.next()), !done)) {
  const [scoreReg, handler] = handle
  if (scoreReg.test(validScore)) {
    handler() // Output result "blue diamond"
    entriess = { next: () => ({ value: null, done: true }) } // End cycle
  }
}

Using Map for data configuration is a great way to write, but if you must be compatible with IE browser, you must consider using other data structures to replace Map:

// Double array for data configuration
const validator = [
  [/^score_-[0-9]{1,2}$/],
  [/^score_1[0-9]{1}$/],
  [/^score_2[0-9]{1}$/]
]
const handler = [
  () => sendGift(['Green diamond', 'Blue Diamond', 'Red Diamond', 'Yellow diamond', 'Purple Diamond']),
  () => sendGift('Blue Diamond'),
  () => sendGift('Red Diamond')
]
// Executive logic omission
// Find the subscript of the verified element from the validator, and execute the callback function corresponding to the subscript in the handler array

// ... or other data structures

Several different code styles mentioned in the above two sections follow the concept of "configuration" to play a leading role in judging. In actual business, you can also try to mix configuration logic separation and conventional judgment conditional writing, in which the main consideration should be whether to judge the configuration leading or not

Postscript

This paper mainly introduces several writing methods that are used to replace if/else in common scenarios, such as binomial + comma operator, using object / Map / double array to separate configuration data and business logic. Although the idea of processing business logic is the same, the actual feeling of each method is different, which is better or worse. Please have a careful taste

Finally, if this article can bring you some code reverie and thinking, it's just a good hint to like * 2.jpg. If there's something wrong in this article, you are welcome to point out and add in the comments

Code performance

I haven't actually examined the performance cost of various writing methods. Those interested in it can try it 👍

Service recommendation

Posted by webtech123 on Fri, 19 Jun 2020 05:21:57 -0700