background

Complex conditional judgments are often encountered in daily development. The general approach is to use if/else, or more elegantly, switch to implement multiple conditional judgments. If more and more conditions lead to more and more bloated code, how to do it in a more elegant way?

case

Let’s start with some code:

const clickHandler = (status) = > {
  if(status === 1) {
    sendLog('processing')
    jumpTo('IndexPage')}else if(status === 2) {
    sendLog('fail')
    jumpTo('FailPage')}else if(status === 3) {
    sendLog('fail')
    jumpTo('FailPage')}else if(status === 4) {
    sendLog('success')
    jumpTo('SuccessPage')}else if(status === 5) {
    sendLog('cancel')
    jumpTo('CancelPage')}else {
    sendLog('other')
    jumpTo('Index')}}Copy the code

Optimization of 1

From the above code, it can be seen that the function is used to send logs and jump to the corresponding page according to the status. You can easily refactor using switch:

const clickHandler = (status) = > {
  switch (status) {
    case 1:
      sendLog('processing')
      jumpTo('IndexPage')
      break
    case 2:
    case 3:
      sendLog('fail')
      jumpTo('FailPage')
      break
    case 4:
      sendLog('success')
      jumpTo('SuccessPage')
      break
    case 5:
      sendLog('cancel')
      jumpTo('CancelPage')
      break
    default:
      sendLog('other')
      jumpTo('Index')}}Copy the code

This looks a lot cleaner than if/else. If you are careful, you will find that the logic of casE2 and case3 is the same,

Optimization of 2

In the daily code development, basically most students write like this. That’s fine, but it’s not very elegant. There is a point: the essence of programming, data structure + algorithm, any algorithm contains two parts, Logic + Control

  • The Logic part is really an algorithm
  • The Control part only affects the efficiency of problem solving.

If we can effectively separate the Logic and Control parts, the code will be much easier to maintain and improve.

For example, we tried to separate the code using the following method:

const actions = {
  '1': ['processing'.'IndexPage'].'2': ['fail'.'FailPage'].'3': ['fail'.'FailPage'].'4': ['success'.'SuccessPage'].'5': ['cancel'.'CancelPage'].'default': ['other'.'Index']}const clickHandler = (status) = > {
  let action = actions[status] || actions['default'], 
    LogName = action[0],
    pageName = action[1]
  sendLog(LogName)
  jumpTo(pageName)
}
Copy the code

In this form, it is actually a Domain Specific Language (DSL) parser. The description of the DSL is a Logic, the function clickHandler is the Control part, the code is greatly simplified,

summary

The following ideas can be summarized:

  • State Machine
    • State definition
    • State transition condition
    • State of the action
  • DSL – Domain Specific Language
    • HTML, SQL, regular expression……
  • Programming paradigm
    • Object-oriented: Delegate, bridge, decorator, MVC…….
    • Functional programming: trim, pipe, splice
    • Logical derivation programming

Optimization of 3

Let’s see if there’s another way to write it. The answer is yes

const actions = new Map([['1'['processing'.'IndexPage']],
  ['2'['fail'.'FailPage']],
  ['3'['fail'.'FailPage']],
  ['4'['success'.'SuccessPage']],
  ['5'['cancel'.'CancelPage']],
  ['default'['other'.'Index']]])const clickHandler = (status) = > {
  let action = actions.get(status) || actions.get('default')
  sendLog(action[0])
  jumpTo(action[1])}Copy the code

1 the new demand

There is a new requirement, which used to determine the status of status, but now also need to determine the identity of the user.

const clickHandler = (status, identity) = > {
  if(identity == 'guest') {
    if(status === 1) {
      // to do something
    } else if (status === 2) {
      // to do something
    } else if (status === 3) {
      // to do something
    } else if (status === 4) {
      // to do something
    } else if (status === 5) {
      // to do something
    } else {
      // to do something}}else if(identity == 'master') {
    if(status === 1) {
      // to do something
    } else if (status === 2) {
      // to do something
    } else if (status === 3) {
      // to do something
    } else if (status === 4) {
      // to do something
    } else if (status === 5) {
      // to do something
    } else {
      // to do something}}}Copy the code

If/else is used to solve the problem (the logic inside is not written because the code is too long). But when you have two levels of criteria, if you still use if/else, the amount of code doubles. Now, how do we write more elegantly?

const actions = {new Map([['guest_1', () = > {/* to do something */}],
  ['guest_2', () = > {/* to do something */}],
  ['guest_3', () = > {/* to do something */}],
  ['guest_4', () = > {/* to do something */}],
  ['guest_5', () = > {/* to do something */}],
  ['master_1', () = > {/* to do something */}],
  ['master_2', () = > {/* to do something */}],
  ['master_3', () = > {/* to do something */}],
  ['master_4', () = > {/* to do something */}],
  ['master_5', () = > {/* to do something */}],
  ['default', () = > {/* to do something */}}]])Copy the code

The logic of the above code is:

  • Concatenate two conditions into a string
  • Take the concatenated conditional stringkey, with the handler function as the valueMapObject to find and execute

Of course, you can also do this with an Object (which is also common)

const actions = {
  'guest_1': (a)= > {/* to do something */},
  'guest_2': (a)= > {/* to do something */},
  'guest_3': (a)= > {/* to do something */},
  'guest_4': (a)= > {/* to do something */},
  'guest_5': (a)= > {/* to do something */},
  'master_1': (a)= > {/* to do something */},
  'master_2': (a)= > {/* to do something */},
  'master_3': (a)= > {/* to do something */},
  'master_4': (a)= > {/* to do something */},
  'master_5': (a)= > {/* to do something */},
  'default': (a)= > {/* to do something */}}Copy the code

For those of you who may feel that concatenating the query condition into a string is not very elegant, there is another alternative, which is to use a Map Object with an Object as the Key:

const actions = new Map([[{identity: 'guest'.status: 1= > {}, ()/* to do something */}],
  [{identity: 'guest'.status: 2= > {}, ()/* to do something */}]
  [{identity: 'guest'.status: 3= > {}, ()/* to do something */}]])const clickHandler = (identity, status) {
  let action = [...actions].filter((key, value) = > {key.identity === identity && key.status === status})
  action.forEach(([key, value]) = > {value.call(this)})}Copy the code

Would it be a little more elegant.

The difference between a Map and an Object: A Map can use any type of data as its key

2 new demand

In the guest case, the processing logic for status 1 through 4 is the same, and the worst case is:

functionA(){
  // to do something
}
functionB(){
  // to do something
}
const actions = new Map([[{identity: 'guest'.status: 1}, functionA],
  [{identity: 'guest'.status: 2}, functionA],
  [{identity: 'guest'.status: 3}, functionA],
  [{identity: 'guest'.status: 4}, functionA],
  [{identity: 'guest'.status: 5}, functionB],
])

const clickHandler = (identity, status) {
  let action = [...actions].filter((key, value) = > {key.identity === identity && key.status === status})
  action.forEach(([key, value]) = > {value.call(this)})}Copy the code

This is pretty much what you need, but it’s still a bit uncomfortable to write the functionA four times. But what if there are 3 states of identity and 30 states of status? Don’t…

If this is the case, you can also use regular expressions, such as:

functionA(){
  // to do something
}
functionB(){
  // to do something
}
const actions = new Map([[/^guest_[1-4]$/, functionA],
  [/^guest_5$/, functionA],
])

const clickHandler = (identity, status) {
  let action = [...actions].filter((key, value) = > {key.test(`${identity}_${status}`)})
  action.forEach(([key, value]) = > {value.call(this)})}Copy the code

The advantages of using a Map are obvious, as you can use regular expression types as keys to meet more requirements. If the requirement becomes, all guest cases should send a buried log code, and different status cases should also be handled separately. So we can consider writing this:

functionA(){
  // to do something
}
functionB(){
  // to do something
}
functionC(){
  // to do something
}
const actions = new Map([[/^guest_[1-4]$/, functionA],
  [/^guest_5$/, functionA],
  [/^guest_.$/, functionC],
])

const clickHandler = (identity, status) {
  let action = [...actions].filter((key, value) = > {key.test(`${identity}_${status}`)})
  action.forEach(([key, value]) = > {value.call(this)})}Copy the code

Taking advantage of the nature of array loops, logic that meets regular expression conditions is executed. This allows both the common logic and the separate logic to be executed

conclusion

The core of this article is how to separate Logic and Control. If all the programs can be well separated, the maintainability of the code will be greatly improved. In addition to running code, readability is also important!

This article moves on to a code refactoring example with multiple layers of if/else nesting