Front-end code optimization: How to optimize the if judgment in the code from the business scenario of system differentiated processing

Recently, there has been a technical scenario of unifying the three terminals. The main reason is that the hybrid web pages on the mobile terminal were expected to be directly served on the PC client without considering UI adaptation. When evaluating the modifications, I found a piece of code that is worth pondering:

if (platform === 'iphone') {
    
    
  location.href = iphoneClientUrl;
} else {
    
    
  location.href = gphoneClientUrl;
}

whereplatform is the current system identifier obtained from the platform judgment function, and its value is such as'iphone' (iPhone), 'gphone' (Android), iphoneClientUrl/gphoneClientUrl are the URL Schemes client protocol jump addresses of iPhone and Android applications respectively.

We know that it is common to differentiate between different systems/applications. For example, different protocols are called here. So what problems does this code face in the current scenario of adapting to PC operation?

Problem 1. Unreasonable cover-up

First of all, if it is run directly on the PC client, this code will directly enter the execution branch of else, that is, it will call the Android client protocol jump address (gphoneClientUrl). This situation is most likely impossible to adjust and may easily lead to execution abnormalities, such as jumping to a blank page.

So the first problem with this code is thatthe Android logic execution code cannot be used as the backend for the final else, and the PC runs Android Mobile code is prone to errors.
In order to correct this problem, the previous code can be changed to:

if (platform === 'iphone') {
    
    
  location.href = iphoneClientUrl;
} else if (platform === 'gphone') {
    
    
  location.href = gphoneClientUrl;
} else {
    
    
  // 兜底处理
  console.log('当前系统未支持此协议调用');
}

Here, a cover-up processing for unidentified platforms is added to avoid directly running the cover-up processing on the mobile side.

*This type of back-and-forth judgment can be found in the codes of many major manufacturers. The following is a paragraph from Baidu:
p-baidu

Question 2. There is no better way to follow the "opening and closing principle"

In order to adapt to the needs of the current PC client, this code now also needs to judge and process the protocol of the PC client, such as:

if (platform === 'iphone') {
    
    
  location.href = iphoneClientUrl;
} else if (platform === 'gphone') {
    
    
  location.href = gphoneClientUrl;
} else if (platform === 'windows') {
    
    
  location.href = windowsClientUrl;
} else {
    
    
  // 兜底处理
  console.log('当前系统未支持此协议调用');
}

Then the problem may come again. If you want to adapt to Mac, iPad, Linux or even Hongmeng systems, this code needs to be adjusted, such as:

if (platform === 'iphone') {
    
    
  location.href = iphoneClientUrl;
} else if (platform === 'gphone') {
    
    
  location.href = gphoneClientUrl;
} else if (platform === 'windows') {
    
    
  location.href = windowsClientUrl;
} else if (platform === 'mac') {
    
    
  location.href = macClientUrl;
} else if (platform === 'ipad') {
    
    
  location.href = ipadClientUrl;
} else if (platform === 'linux') {
    
    
  location.href = linuxClientUrl;
} else if (platform === 'harmony') {
    
    
  location.href = harmonyClientUrl;
} else {
    
    
  // 兜底处理
  console.log('当前系统未支持此协议调用');
}

In other words, every time you want to adapt to a new system, you need to add an else judgment, so this code isnot better to follow< a i=2>"Opening and closing principle", not easy to maintain. Because the main logic of this code is to perform protocol jumps according to different platforms, and our changes only add a new platform processing, and the main code should not be modified.
In addition, such code also makes the focus of this code lost, from the original focus on jumping according to the url to the focus on jumping through each branch.

So how should this code be adjusted? Put the adjusted reference code first:

const PLATFORM_CLIENT_URLS = {
    
    
  iphone: iphoneClientUrl,
  gphone: gphoneClientUrl,
  windows: windowsClientUrl,
  mac: macClientUrl,
  ipad: ipadClientUrl,
  linux: linuxClientUrl,
  harmony: harmonyClientUrl,
};

// 调用体
function jumpToClientUrl(platform) {
    
    
  const clientUrl = PLATFORM_CLIENT_URLS[platform];

  if (clientUrl) {
    
    
    location.href = clientUrl;
  } else {
    
    
    // 兜底处理
    console.log('当前系统未支持此协议调用');
  }
}

Here we use object literalsPLATFORM_CLIENT_URLS to close each system and its corresponding protocol address, abstracting the main logic of protocol jump according to different platforms to . This object can also be placed in the configuration file and run Decoupling the runtime code allows adjustments without even changing the runtime code. jumpToClientUrlPLATFORM_CLIENT_URLS

Of course, this case is relatively simple, so what should we do if we encounter a slightly more complex scenario?

Scenario 1. When the judgment conditions or corresponding execution processes of each judgment branch are different.

Continuing the scenario of the previous code, first look at the situation where the judgment conditions are different, such as assuming

  • iPhone needs to be greater than iOS10 (osVersion >= 10)
  • Android needs to be between Android 6 and 10 (osVersion >= 6 && osVersion <= 8)
  • windows Must order Windows 8.1 version (osVersion === 8.1)

In this case, the object literal method just now cannot be processed directly, so how should it be adapted?

Extract the judgment branch and adjust the object literal just now:

const PLATFORM_CLIENT_SCHEMA = {
    
    
  iphone: {
    
    
    rule: osVersion => osVersion >= 10,
    url: iphoneClientUrl,
  },
  gphone: {
    
    
    rule: osVersion => osVersion >= 6 && osVersion <= 8,
    url: gphoneClientUrl,
  },
  windows: {
    
    
    rule: osVersion => osVersion === 8.1,
    url: windowsClientUrl,
  },
  mac: {
    
    
    rule: osVersion => osVersion >= 0,
    url: macClientUrl,
  },
};

// 调用体
function jumpToClientUrl(platform, osVersion) {
    
    
  const clientSchema = PLATFORM_CLIENT_SCHEMA[platform];
  let jumpClientUrl = '';

  // 如果有规则且判断通过
  if (clientSchema?.rule?.(osVersion)) {
    
    
    jumpClientUrl = clientSchema.url;
  }

  if (jumpClientUrl) {
    
    
    location.href = jumpClientUrl;
  } else {
    
    
    // 兜底处理
    console.log('当前系统未支持此协议调用');
  }
}

We have made structural adjustments to the system scenarios that need to be judged separately, and have separated the fields used for special judgmentsrule. We also maintain that adapting a new system only requires adjusting the objects (< /span> function. PLATFORM_CLIENT_RULES_AND_URLS) without modifying the jumpToClientUrl

Let’s look at the scenario of inconsistent execution. If

  • iPhone opens a pop-up window (Alert.show())
  • Android calls the js method instead of jumping (callAndroidNative(gphoneClientUrl))
  • windows iswindow.open()opening site (window.open(windowsClientUrl))

In this case, you can continue to separate and proceed with the judgment condition just now:

Extract the execution statement and adjust the object literal just now:

const PLATFORM_CLIENT_SCHEMA = {
    
    
  iphone: {
    
    
    rule: osVersion => osVersion >= 10,
    url: iphoneClientUrl,
    run: () => Alert.show(),
  },
  gphone: {
    
    
    rule: osVersion => osVersion >= 6 && osVersion <= 8,
    url: gphoneClientUrl,
    run: () => callAndroidNative(gphoneClientUrl),
  },
  windows: {
    
    
    rule: osVersion => osVersion === 8.1,
    url: windowsClientUrl,
    run: () => window.open(windowsClientUrl),
  },
  mac: {
    
    
    rule: osVersion => osVersion >= 0,
    url: macClientUrl,
  },
};

// 调用体
function jumpToClientUrl(platform, osVersion) {
    
    
  const clientSchema = PLATFORM_CLIENT_SCHEMA[platform];
  let jumpClientUrl = '';

  // 如果有规则且判断通过
  if (clientSchema?.rule?.(osVersion)) {
    
    
    // 如果有单独执行条件
    if (clientSchema.run) {
    
    
      return clientSchema.run();
    }
    jumpClientUrl = clientSchema.url;
  }

  if (jumpClientUrl) {
    
    
    location.href = jumpClientUrl;
  } else {
    
    
    // 兜底处理
    console.log('当前系统未支持此协议调用');
  }
}

has further adjusted the structure of system scenarios that need to be processed separately, and separated the fields used for special processing run. It also maintains that adapting a new system only requires adjusting the object (< /span> function. PLATFORM_CLIENT_SCHEMA) without modifying the jumpToClientUrl

Scenario 2. Consider expanding application scenarios. The series of optimizations we just made are essentially only in a small application scenario. How can we generalize similar system judgment and processing?

We can defineabstract classorinterface, The attribute information, various judgments and execution methods of each system are included in the implementation class of this abstract class, and the consumption processing of each scenario is placed >Consumer Category. Then it can be used by consumers through similar strategy mode, template mode or even adapter mode. The unified abstraction of judgment conditions and execution logic can be achieved through strategies such as the strategy pattern to improve the scalability, reusability and readability of the overall code.

Then let’s take the strategy mode as an example to implement a simple cross-end api encapsulation (because there is no abstract class/interface syntax in js, we will use ts to achieve the code effect below):

strategy pattern

As a software design pattern, the strategy pattern means that the object has a certain behavior, but in different scenarios, this behavior has different implementation algorithms. For example, everyone has to "pay personal income tax", but "pay personal income tax in the United States" and "pay personal income tax in the Republic of China" have different tax calculation methods. ——WikiPedia-Strategy Mode

Let’s first review the concept of Strategy Pattern: In Strategy Pattern, the behavior of a class or its algorithm can be changed at runtime. This type of design pattern is a behavioral pattern.
In the strategy pattern, we create objects that represent various strategies and a context object whose behavior changes as the strategy object changes. The strategy object changes the execution algorithm of the context object. The strategy pattern is the ability to encapsulate a series of "interchangeable" algorithms and select one of them based on user needs.
The core of the implementation of the strategy pattern is: Separate the use of the algorithm from the implementation of the algorithm. The implementation of the algorithm is left to the strategy class. The use of algorithms is left to the environment class, which will select the appropriate algorithm according to different situations.

UML such as:
p-uml

  • advantage:
    1. Algorithms can be switched freely.
    2. Avoid using multiple conditional judgments.
    3. Good scalability.
  • shortcoming:
    1. Strategy categories will increase.
    2. All strategy classes need to be exposed to the outside world.

The strategy mode is very suitable for the processing of our previous system environment judgment. The following is an implementation demo:

Strategy mode implements system judgment and processing

interface:

interface PlatformStrategy {
    
    
  // 跳转场景
  jumpClient(): void;

  // 其他场景、如设置标题
  setTitle(): void;
}

Implementation class (strategy class):

class IphoneStrategy implements PlatformStrategy {
    
    
  jumpClient(osVersion: number) {
    
    
    if (osVersion >= 10) {
    
    
      Alert.show();
    } else {
    
    
      console.log('当前系统未支持此协议调用(iOS版本小于10)');
    }
  }

  setTitle(title: string) {
    
    
    document.title = `${
      
      title}(iPhone)`;
  }
}

class GphoneStrategy implements PlatformStrategy {
    
    
  jumpClient(osVersion: number) {
    
    
    if (osVersion >= 6 && osVersion <= 8) {
    
    
      callAndroidNative(gphoneClientUrl);
    } else {
    
    
      console.log('当前系统未支持此协议调用(安卓版本小于6或大于8)');
    }
  }

  setTitle(title: string) {
    
    
    setAndroidTitle(title);
  }
}

class OtherStrategy implements PlatformStrategy {
    
    
  jumpClient(osVersion: number) {
    
    
    console.log('当前系统未支持此协议调用');
  }

  setTitle(title: string) {
    
    
    console.log('当前系统未支持此协议调用');
  }
}

Consumer category (environmental category):

class PlatformCustom {
    
    
  platformStrategy: PlatformStrategy;
  constructor(platformStrategy: PlatformStrategy) {
    
    
    this.platformStrategy = platformStrategy;
  }

  setHomePageTitle() {
    
    
    this.platformStrategy.setTitle('主页');
  }
  setHomeRuleTitle() {
    
    
    this.platformStrategy.setTitle('规则页');
  }

  jumpClient() {
    
    
    this.platformStrategy.jumpClient(osVersion);
  }
}

use:

const NowPlatformStrategy = STRATEGY_MAP[platform] || OtherStrategy;
const platformCustomer = new PlatformCustom(new NowPlatformStrategy());

// ...
platformCustomer.setHomePageTitle();

// ...
platformCustomer.jumpClient();

It can be found that when we define and use consumer categories, we do not need to deal with the environment of each system, and we do not need to modify the usage or consumer categories when adapting to new systems. We follow the "opening and closing principle" very well.

In addition, in the field of large front-end, this type of model is also suitable for the encapsulation of cross-end APIs. You can see that the encapsulation of various cross-end frameworks (such as Taro) more or less follows the strategy pattern/adapter pattern.

Summarize

Optimization suggestions for this article

Based on the business scenarios differentiated and judged by the front-end system and the optimization of a piece of code, the front-end optimization suggestions proposed this time include the following:

  1. We need to rationally design the cover-up processing to avoid directly calling incompatible code when adapting to new scenarios;
  2. In scenarios involving more judgments, we can use abstract methods to process and follow the opening and closing principle;
  3. Strategy mode/adapter mode/template mode can be applied to some unified processing scenarios, such as cross-end unified judgment logic;
  4. We need to continue to learn design patterns and think about practical applications on the front end
*possible problems

The above types have made various abstractions for if processing. Are there any hidden problems in this situation?

If we insist on talking about hidden dangers, there are the following two hidden dangers that are almost not worth mentioning:

  1. More enumerations/objects/classes are created, taking up space. ;
  2. The cost of understanding the code may be increased, and the if else cannot be read smoothly.

think

What other scenarios can we do abstract optimization of if statements in advance? For example, you need to deal with various banks, various cities, various types of fruits, various fund codes, etc...
Do we need to introduce design patterns in advance when dealing with these scenarios? If necessary, what are the judgment conditions?


Recommended reading

The above classic books all contain content on optimization of if statements.

Guess you like

Origin blog.csdn.net/qq_24357165/article/details/133856195