Coding With Fun
Home Docker Django Node.js Articles Python pip guide FAQ Policy

Code optimization techniques: logical judgment


Jun 01, 2021 Article blog


Table of contents


If you want to be a qualified programmer, code optimization is a very important part of a high-quality piece of code that makes it easy for people to see, both for themselves and for others.

if else switch case is the most common conditional judgment statement in everyday development, this seemingly simple statement, when encountered in a complex business scenario, if not handled well, there will be a lot of logical nesting, poor readability and difficult to scale.

To write high-quality, maintainable code, let's start with the smallest and see where logical judgments can be optimized during front-end development.

Here are some optimization tips from JavaScript syntax and React JSX syntax.

JavaScript syntax

Nested hierarchy optimization

function supply(fruit, quantity) {
    const redFruits = ['apple', 'strawberry', 'cherry', 'cranberries'];
    // 条件 1: 水果存在
    if(fruit) {
        // 条件 2: 属于红色水果
        if(redFruits.includes(fruit)) {
            console.log('红色水果');
            // 条件 3: 水果数量大于 10 个
            if (quantity > 10) {
                console.log('数量大于 10 个');
            }
        }
    } else {
        throw new Error('没有水果啦!');
    }
}

Analysis of the above conditions to judge that there are three layers if condition nesting.

If return invalid condition is dropped in advance, it is easier to understand and maintain if else multi-nested hierarchy is reduced to one level.

function supply(fruit, quantity) {
    const redFruits = ['apple', 'strawberry', 'cherry', 'cranberries'];
    if(!fruit) throw new Error('没有水果啦'); // 条件 1: 当 fruit 无效时,提前处理错误
    if(!redFruits.includes(fruit)) return; // 条件 2: 当不是红色水果时,提前 return

    
    console.log('红色水果');

    
    // 条件 3: 水果数量大于 10 个
    if (quantity > 10) {
        console.log('数量大于 10 个');
    }
}

Optimal processing of multi-conditional branches

When you need to enumerate values to handle different business branch logic, the first reaction is to write down if else Let's take a look at:

function pick(color) {
  // 根据颜色选择水果
  if(color === 'red') {
      return ['apple', 'strawberry']; 
  } else if (color === 'yellow') {
      return ['banana', 'pineapple'];
  } else if (color === 'purple') {
      return ['grape', 'plum'];
  } else {
      return [];
  }
}

In the implementation above:

  • if else too many if else branches
  • if else better suited for conditional interval judgment, while switch case is more suitable for branch judgment for specific enumerated values

After optimizing the above code with switch case

function pick(color) {
  // 根据颜色选择水果
  switch (color) {
    case 'red':
      return ['apple', 'strawberry'];
    case 'yellow':
      return ['banana', 'pineapple'];
    case 'purple':
      return ['grape', 'plum'];
    default:
      return [];
  }
}

switch case case-optimized code looks neatly organized and clear-minded, but it's still lengthy. Continue to optimize:

  • With Object { key: value } structure, we can enumerate all cases in Object and then use key as an index to get content directly from Object.key or Object[key]

const fruitColor = {                                                                        
    red: ['apple', 'strawberry'],
    yellow: ['banana', 'pineapple'],
    purple: ['grape', 'plum'],
}
function pick(color) {
    return fruitColor[color] || [];
}

  • Using Map data structures, real key value key values are paired to structures;

const fruitColor = new Map()
.set('red', ['apple', 'strawberry'])
.set('yellow', ['banana', 'pineapple'])
.set('purple', ['grape', 'plum']);


function pick(color) {
  return fruitColor.get(color) || [];
}

Once optimized, the code is simpler and easier to extend.

For better readability, you can also define objects in a more semantic way, and then use Array.filter to achieve the same effect.

const fruits = [
    { name: 'apple', color: 'red' }, 
    { name: 'strawberry', color: 'red' }, 
    { name: 'banana', color: 'yellow' }, 
    { name: 'pineapple', color: 'yellow' }, 
    { name: 'grape', color: 'purple' }, 
    { name: 'plum', color: 'purple' }
];


function pick(color) {
  return fruits.filter(f => f.color == color);
}

(Recommended tutorial: JavaScript tutorial)

Simplify logical judgment with the new features of the array

Clever use of the new features of the array provided in ES6 also makes it easier for us to handle logical judgments.

Multi-condition judgment

When coding encounters multiple judgmental conditions, instinctively write down the following code (in fact, it is also the process-oriented coding that best expresses business logic).

function judge(fruit) {
  if (fruit === 'apple' || fruit === 'strawberry' || fruit === 'cherry' || fruit === 'cranberries' ) {
    console.log('red');
  }
}

But when type is coming up to 10 or more, can we just continue to add || to maintain the code?

Try Array.includes

// 将判断条件抽取成一个数组
const redFruits = ['apple', 'strawberry', 'cherry', 'cranberries'];
function judge(type) {
    if (redFruits.includes(fruit)) {
        console.log('red');
     }
}

Determines whether all items in the array meet a condition

const fruits = [
    { name: 'apple', color: 'red' },
    { name: 'banana', color: 'yellow' },
    { name: 'grape', color: 'purple' }
  ];


function match() {
  let isAllRed = true;


  // 判断条件:所有的水果都必须是红色
  for (let f of fruits) {
    if (!isAllRed) break;
    isAllRed = (f.color === 'red');
  }


  console.log(isAllRed); // false
}

In the above implementation, all items in the array are qualified primarily for processing.

Using Array.every allows you to implement this logic with ease:

const fruits = [
    { name: 'apple', color: 'red' },
    { name: 'banana', color: 'yellow' },
    { name: 'grape', color: 'purple' }
  ];


function match() {
  // 条件:所有水果都必须是红色
  const isAllRed = fruits.every(f => f.color == 'red');


  console.log(isAllRed); // false
}

Determine if there is an item in the array that meets the criteria

Array.some which handles scenarios primarily to determine whether there is a condition in the array that meets the criteria.

If you want to know if you have red fruit, you can use Array.some method directly:

const fruits = [
    { name: 'apple', color: 'red' },
    { name: 'banana', color: 'yellow' },
    { name: 'grape', color: 'purple' }
  ];


// 条件:是否有红色水果 
const isAnyRed = fruits.some(f => f.color == 'red');

There are many other new features of Array.find Array.slice Array.findIndex Array.reduce Array.splice and so on, that you can choose to use in a real-world scenario.

The default value of the function

Use the default parameters

const buyFruit = (fruit,amount) => {
     if(!fruit){
        return
  }
  amount = amount || 1;
  console.log(amount)
}

We often need to deal with some parameter defaults inside the function, the above code is no stranger to everyone, using the default parameters of the function, can be very good to help deal with this scenario.

const buyFruit = (fruit,amount = 1) => {
     if(!fruit){
        return
  }
  console.log(amount,'amount')
}

We can see how the default parameters are implemented through Babel translation.

 Code optimization techniques: logical judgment1

From the translation results above, it can be found that the default parameters are used only if the parameter is undefined

The results of the test are as follows:

buyFruit('apple','');  // amount
buyFruit('apple',null);  //null amount
buyFruit('apple');  //1 amount

So with the default parameter, we need to be aware that the default parameter amount=1 not the same as the amount amount || 1

Use deconstruction with default parameters

When function arguments are objects, we can simplify the logic by deconstructing them with default parameters.

Before:

const buyFruit = (fruit,amount) => {
    fruit = fruit || {};
    if(!fruit.name || !fruit.price){
        return;
    }
    ...
  amount = amount || 1;
  console.log(amount)
}

After:

const buyFruit = ({ name,price }={},amount) => {
  if(!name || !prices){
      return;
  }
  console.log(amount)
}

Complex data deconstruction

Deconstruction works well with default parameters when working with simpler objects, but in some complex scenarios we may be faced with more complex structures.

const oneComplexObj = {
    firstLevel:{
        secondLevel:[{
            name:"",
            price:""
        }]
    }
}

This time, if you deconstruct to get the value in the object.

const {
    firstLevel:{
        secondLevel:[{name,price]=[]
    }={}
} = oneComplexObj;        

Readability can be poor, and the default values for multi-layer deconstruction and data anomalies need to be considered.

In this case, if you use lodash library in your project, you can use the lodash/get method in it.

import lodashGet from 'lodash/get';


const { name,price} = lodashGet(oneComplexObj,'firstLevel.secondLevel[0]',{});

Policy patterns optimize branch logic processing

Policy patterns: Define a series of algorithms, encapsulate them one by one, and replace them with each other.

Use scenario: Policy patterns are object behavior patterns that can be used when encountering instance objects with the same behavior interface and different logical implementations within the behavior, or policy patterns when a group of objects can dynamically select one of several behaviors as needed, as an example of the second:

Before:

const TYPE = {
    JUICE:'juice',
    SALAD:'salad',
    JAM:'jam'
}
function enjoy({type = TYPE.JUICE,fruits}){
  if(!fruits || !fruits.length) {
        console.log('请先采购水果!');
           return;
    }
  if(type === TYPE.JUICE) {
    console.log('榨果汁中...');
      return '果汁';
  }
  if(type === TYPE.SALAD) {
      console.log('做沙拉中...');
      return '拉沙';
  }
  if(type === TYPE.JAM) {
    console.log('做果酱中...');
      return '果酱';
  }
  return;
}


enjoy({type:'juice',fruits});

Use ideas: Define policy objects to encapsulate different behaviors, provide policy selection interfaces, and invoke corresponding behaviors when different rules occur.

After:

const TYPE = {
    JUICE:'juice',
    SALAD:'salad',
    JAM:'jam'
}


const strategies = {
    [TYPE.JUICE]: function(fruits){
        console.log('榨果汁中...');
        return '果汁';
    },
    [TYPE.SALAD]:function(fruits){
        console.log('做沙拉中...');
        return '沙拉';
    },
    [TYPE.JAM]:function(fruits){
        console.log('做果酱中...');
        return '果酱';
    },
}


function enjoy({type = TYPE.JUICE,fruits}) {
    if(!type) {
        console.log('请直接享用!');
           return;
    }
    if(!fruits || !fruits.length) {
        console.log('请先采购水果!');
           return;
    }
    return strategies[type](fruits);
}


enjoy({type:'juice',fruits});

(Recommended micro-class: JavaScript micro-course)

Framework Article React JSX Logical Judgment Optimization

JSX is a JavaScript syntax extension that looks like XML JSX is typically used in React to describe interface information, and ReactDOM.render() JSX interface information to the page.

JavaScript expressions are supported in JSX and it is common to loop through output subcompletes, trot expression judgments, and more complex direct abstractions of a function.

With so many JavaScript expressions written in JSX the overall code looks a bit cluttered. Try to optimize it!

JSX-Control-Statements

JSX-Control-Statements is a Babel plug-in that extends JSX ability to handle conditional judgment and looping as labels.

If label

<If> label content is rendered only when condition is true which is equivalent to the simplest troy expression.

Before:

{ condition() ? 'Hello World!' : null }

After:

<If condition={ condition() }>Hello World!</If>

Note: <Else /> has been discarded and complex conditions can be determined <Choose> tags.

Choose label

< Include At Least One <When> > Label, Optional < <Otherwise> > Label Under The <Choose> > Label.

<When> label content is rendered only when condition is true which is equivalent to an if conditional judgment branch.

<Otherwise> label is equivalent to the last else branch.

Before:

{ test1 ? <span>IfBlock1</span> : test2 ? <span>IfBlock2</span> : <span>ElseBlock</span> }

After:

<Choose>
  <When condition={ test1 }>
    <span>IfBlock1</span>
  </When>
  <When condition={ test2 }>
    <span>IfBlock2</span>
  </When>
  <Otherwise>
    <span>ElseBlock</span>
  </Otherwise>
</Choose>

For label

<For> label needs to declare of each attribute.

of receives objects that can be accessed using iterators.

each represents the current pointing element at the time of iterator access.

Before:

{
  (this.props.items || []).map(item => {
      return <span key={ item.id }>{ item.title }</span>
  })
}

After:

<For each="item" of={ this.props.items }>
   <span key={ item.id }>{ item.title }</span>
</For>

Note: <For> label cannot be used as a root element.

With label

<With> labels provide the ability to pass variable parameters.

Before:

renderFoo = (foo) => {
    return <span>{ foo }</span>;
}


// JSX 中表达式调用
{
    this.renderFoo(47)
}

After:

<With foo={ 47 }>
  <span>{ foo }</span>
</With>

Using these label optimization codes can reduce the explicit JavaScript expressions that exist in JSX and make our code look cleaner, but the ability of these label encapsulations needs to be converted to equivalent JavaScript expressions at compile time.

summary

These are some common summaries of logical judgment optimization techniques. Of course, writing high-quality, maintainable code, in addition to logical judgment optimization, but also need to have clear comments, well-meaning variable naming, reasonable code structure splitting, logical layering and decoupling, and a higher level of logical abstraction of the appropriate business, and so on, I believe you also have some of their own experience in this regard.