Preamble: The next step is to open up a small volume of knowledge about best practices for applets, which are used as a starting point, but by no means limited to this. There will be several chapters “Named,” “Single Test,” “Code Style,” “Comments,” “Safe Production,” “Code Design,” “Performance,” “Internationalization,” and “Why Applets Development Best Practices.” Each will contain positive and negative examples and detailed explanations.

The examples in this article are typical problems found during CR. Hopefully, you will be able to avoid such problems in advance with this booklet and focus our CR more on business logic rather than such trivialities or lack of basic ideas, resulting in a high quality CR.

Code design 🧘♂️🧘♀️

Zen Python

The Zen of Python, by Tim Peters

Beautiful is better than ugly.

Explicit is better than implicit.

Simple is better than complex.

Complex is better than complicated.

Flat is better than nested.

Sparse is better than dense.

Readability counts.

Special cases aren’t special enough to break the rules.

Although practicality beats purity.

Errors should never pass silently.

Unless explicitly silenced.

In the face of ambiguity, refuse the temptation to guess.

There should be one– and preferably only one –obvious way to do it.

Although that way may not be obvious at first unless you’re Dutch.

Now is better than never.

Although never is often better than right now.

If the implementation is hard to explain, it’s a bad idea.

If the implementation is easy to explain, it may be a good idea.

Namespaces are one honking great idea — let’s do more of those!

Good code is code that is easy to delete

Good code means low coupling and high cohesion, and good code is easy to refactor.

Restricted permissions

To be Private over Public, Public APIS mean higher requirements for stability and code quality, need to be robust, need to consider all scenarios, need to single test and sufficient coverage to ensure; Bad for refactoring, Code should be easy to delete, compatibility needs to be considered, and developers are treading on thin ice.

“With great power comes great responsibility.” If you don’t want to consider any more scenarios, just keep it private for your current requirements.

Note that this clause is not in conflict with reusability, and the consequence of inadequate reusability is equivalent to premature optimization, which is the source of all evils. See Duplication is less costly than premature abstraction.

With great power comes great responsibility

Bad
/** * exception popup */
export const errorAlert = ({
  / /...
})
Copy the code
Good

After a global search this function is not referenced by other files, delete export and make it private 👍

/** * exception popup */
const errorAlert = ({
  / /...
})
Copy the code

The function design

Functions improve readability and reusability and comply with the DRY principle. All good functions are traceable, and all bad functions are bad in their own way.

Grouping of parameter objects

[Suggestion] : Three or more parameters should be in the form of object parameters. Secondly, grouping parameters of similar functions can make the function logic clearer.

Bad

STR is the target subject, which is mandatory. Other parameters, which are optional, are mixed together without focus and logic

/** * account class desensitization method *@param StartNum header how many bits start desensitization default 0 *@param LastNum tail number of non-desensitization default 0 */
interface IObjParamInterface {
  str: string; startNum? :number; lastNum? :number;
  formatStr: string;
}
export function accountDesensitization(objParam: IObjParamInterface) :string {
  // ...
}
Copy the code
Good

Separate body and configuration, separate concerns, clearer logic

/** * account class desensitization method *@param StartNum header how many bits start desensitization default 0 *@param LastNum tail number of non-desensitization default 0 */
export function mask(str = ' ', objParam: IMaskOptions = {}) :string {
  // ...
}

interfaceIMaskOptions { startNum? :number; lastNum? :number; formatStr? :string;
}
Copy the code

No side effect

[Suggestion] Functions with side effects are easy to cause unexpected problems and cause hidden problems at runtime, and functions without side effects are easier to test. Secondly, it is recommended to isolate side effects.

For example, please refer to “Safe Production” in applets best Practices 👷🏻♂️.

The principle of SRP

A module, class, or method does only one thing, no more, no less.

Bad

GetGuideCount not only does GET, but also does set and update things incidentally, violating SRP

GetGuideCount = getGuideCount; /** * getGuideCount = getGuideCount
getGuideCount () {
  my.getStorage({
    key: STORAGE_GUIDE_COUNT,
    success: res= > {
      Res.data returns null for the first time
      if (res.data === null || res.data < 3) {
        Res.data is expected to be null or a number
        const incrementByOne = (res.data === null ? 0 : res.data) + 1;

        my.setStorage({
          key: STORAGE_GUIDE_COUNT,
          data: incrementByOne,
        });

        / / in the state
        this.commit('updateGuideCount', incrementByOne); }}}); },Copy the code
Good

Split into two methods, let getGuideCount lean and simplify administration, more single responsibility, eliminate its side effects, let incrementGuideCount go full-time to do the update operation with side effects (isolation side effects). For additional benefits, eliminate duplicate judgments of res.data and NULL, 👍

{
  /** * get the first and second login count *@private* /
  async getGuideCount(): Promise<number> {
    const res = await getStorage(STORAGE_GUIDE_COUNT);

    return res.data || 0;
  },
	
  /** * Add new boot times *@private* /
  async incrementGuideCount(): Promise<void> {
    const guidCount = await this.getGuideCount();

    if (guidCount < 3) {
      const incrementByOne = guidCount + 1;
  
      my.setStorage({
        key: STORAGE_GUIDE_COUNT,
        data: incrementByOne,
      });
  
      this.commit('updateGuideCount', incrementByOne); }}}Copy the code

Time to abstract as a function

Close or complex logic can often be abstracted into functions to significantly improve readability.

1. Complex logic

Bad

The logic in line 4 is so complex that it’s hard to see what it means at a glance.

if (len && len > startNum + lastNum) {
  return (
    str.substring(0, startNum) +
    new Array(len - startNum - lastNum).fill(formatStr).join(' ') +
    str.substring(len - lastNum, len)
  );
}
Copy the code
Good

Extract into method repeat, through the function name to achieve self-description 👍.

function repeat(repeater = ' ', count = 1) :string {
  return new Array(count).fill(repeater).join(' ');
}

if (len && len > startNum + lastNum) {
  return (
    str.substring(0, startNum) +
    repeat(formatStr, len - startNum - lastNum) +
    str.substring(len - lastNum, len)
  );
}

Copy the code

Readability. 2.

The reason for the emphasis on wrapping functions is self-explanatory, as the function name is best documented. The second team is highly efficient in division of labor. If the caller and the developer of the internal logic of the function are two people, the caller does not need to modify the code when the logic changes, but can use it as a black box.

Bad

It is a mixture of different logical blocks without focus.

if (
  task &&
  task.taskTitle &&
  task.actionShowName &&
  task.prizedType &&
  Number(task.prizedAmount) &&
  Number(waitToGetInterestAmount)
) {
  // deal with the valid task
}
Copy the code
Good

Define the isValidTask function:

function isValidTask(task) {
  return task &&
    task.taskTitle &&
    task.actionShowName &&
    task.prizedType &&
    Number(task.prizedAmount)
  ;
}
Copy the code

With this function, the function name is self-explanatory and logically grouped, and the code is clearer 👍🏻.

if (isValidTask(task) && Number(waitToGetInterestAmount)) {
  // deal with the valid task
}
Copy the code

To Return as soon as possible

Placing exceptions or special logic at the top of the function and exiting the function as soon as possible can reduce nesting on the one hand and mental load on the reader of the code on the other.

Bad

Too much nested logic, entering the else branch must bearing in mind the previous if condition moment 🕷😓.

function foo() {
  if(result.data? .length >0) {
    // Filter uncertified vehicles
    if (result.data.some(item= >! item.certificated)) { commit('setState', { isCertificated: false });
    }else {
      // Filter out "certified vehicles"
      const vehicleInfoList = (result.data).filter(item= > item.certificated);
      // Check whether there are unsynchronized vehicles
      const showSyncTips = vehicleInfoList.filter(item= >! item.isSyncedCertFolder).length >0;
      commit('setState', { vehicleInfoList, showSyncTips, }); }}}Copy the code
Good

Exit functions early, lighten the mind of the code reader, and it’s also less nested and more pleasing to the eye. Another advantage of writing this way is that if the if part is new logic, the diff is less noisy.

function foo() {
  const { data = [] } = result;
  const uncertifiedPlateNumberExisting = data.some(item= >! item.certificated);if (uncertifiedPlateNumberExisting) {
    commit('setState', { uncertifiedPlateNumberExisting: true });

    return;
  }

  // Filter out "certified vehicles"
  const vehicleInfoList = data.filter(item= > item.certificated);

  // Check whether there are unsynchronized vehicles
  const showSyncTips = vehicleInfoList.filter(item= >! item.isSyncedCertFolder).length >0;

  commit('setState', {
    vehicleInfoList,
    showSyncTips,
  });
}
Copy the code

Backward logic is not desirable, but increases the difficulty of understanding

People tend to think in positive logic, and reverse logic usually leads to double negation and thus no obvious meaning.

let isError: boolean; // NOT: isNoError
let isFound: boolean; // NOT: isNotFound
Copy the code
Bad
<view a:if="{{ !visible }}"> B </view>
<view a:else> A </view>
Copy the code
Good

Using forward logic, show A if visible, B otherwise

<view a:if="{{ visible }}"> A </view>
<view a:else> B </view>
Copy the code
Bad

The comment for flag is “does the value corresponding to the placeholder not exist”, which is the reverse logic. Second, good naming does not require comments

function replaceAdditionInfo(url = ' ', params = {}) {
  let flag = false; // Whether the value corresponding to the placeholder does not exist
  const resultStr = url.replace(/#(\w+)#/g.function($, $1) {
    let res = params[$1];
    if(! res) flag =true;
    return res || ' ';
  });
  return flag ? ' ' : resultStr;
}
Copy the code
Good

Function renamed “replacePlaceholder”, sign bit using forward logic, logic more clear, understanding cost instantly reduced 👍

function replacePlaceholder(url = ' ', params = {}) {
  let placeholderExisting = true; // Use forward logic
  
  const resultStr = url.replace(/#(\w+)#/g.function($, $1) {
    const value = params[$1];
    
    if(! value) { placeholderExisting =false; }
    
    return value || ' ';
  });
  
  return placeholderExisting ? resultStr : ' ';
}
Copy the code
Bad

If the version number is smaller than X, CDP cannot be used. The reverse value of return False cannot be used makes the code difficult to understand.

const canIUseCdp = async() = > {const version = await getVersion();
  if (compareVersion(version, USABLE_VERSION) === -1) {
    return false;
  } else {
    return true; }};Copy the code
Good

Using forward logic, CDP can be used if it is greater than x, which is clear and intuitive 👍

const canIUseCdp = async() = > {const version = await getVersion();

  return compareVersion(version, USABLE_VERSION) > 0;
};
Copy the code

Principle of consistency

The meaning of the same variable must be the same in different contexts so as not to cause logical confusion.

Bad

IndexOfCurrentShowTab in the second line means that the server placed taskId in the first place, which should be constant; The ninth line is the subscript of the newly added task, and using the same variable for both causes logic confusion.

 // The default backend puts the corresponding taskId data at the top of the array
let currentTaskIdIndex = 0;

// If the corresponding taskId data is not put in the first place as agreed, it is considered an exception
if(showTabs[currentTaskIdIndex]? .taskId ! == currentTabTaskId) {const anotherTabTaskId = tabs[activeTab === 0 ? 1 : 0]? .taskId;// Try to add a new TAB, but avoid existing tabs to avoid two identical tabs
  currentTaskIdIndex = showTabs.findIndex(({ taskId = ' ' }) = >taskId ! == anotherTabTaskId);// ...
}
Copy the code
Good

Separate responsibilities using two variables, currentTaskIdIndex and newTaskIndex, and the logic becomes clear 👍

// The default backend puts the corresponding taskId data at the top of the array
let currentTaskIdIndex = 0;
let newTaskIndex;

// If the corresponding taskId data is not put in the first place as agreed, it is considered an exception
if(showTabs[currentTaskIdIndex]? .taskId ! == currentTabTaskId) {const anotherTabTaskId = tabs[activeTab === 0 ? 1 : 0]? .taskId;// Try to add a new TAB, but avoid existing tabs to avoid two identical tabs
  newTaskIndex = showTabs.findIndex(({ taskId = ' ' }) = >taskId ! == anotherTabTaskId);// ...
}
Copy the code

Eliminates magic numbers or strings

Essentially DRY (Don’t Repeat Yourself) and code is documentation and extensibility.

Don’t eliminate it with comments. Eliminate it with constants, enums, etc. The benefit is that the code is the document, and the subsequent modification can be closed uniformly, without searching all replacement.

Bad

If the status Start appears multiple times, can you ensure that the next time you or the new person takes over, you will not write a lowercase Start or Start? If the reconstruction needs to be changed to a more standard START, global search is also needed to ensure that there is no omission, and the labor cost is too high, resulting in the dare not to reconstruct.

Store({
  state: {
    deviceStatus: 'Start'.pageStatus: 'Start',},actions: {
    async openDeviceDoor({ commit }, payload) {
      // ...
      commit('changeDeviceStatus'.'Start');
      commit('changePageStatus'.'Start');
      // ...
    },
    async pollingDeviceStatus({ state, commit }, payload) {
      const { prevStatus, deviceId } = payload;
      // Different processes take different polling times, so garbage disposal should wait longer.
      let times = 0;
      switch (prevStatus) {
        case 'Start':
          times = 200;
          break;
        case 'Opened':
          times = 200;
          break;
        case 'Delivered':
          times = 200;
          break;
        default:}},},})Copy the code
Good

Using enumerations to centrally manage all states not only solves the spelling uncertainty caused by handwriting or copy, but also annotations to make the type of state more clear to the receiver, change all uses of a place without changing, so that the refactoring is more confident, and can isolate the backend “dirty” name 👍

const DoorStatusEnum = {
  START: 'Start'.OPENED: 'Opened'.DELIVERED: 'Delivered'.APPRAISED: 'Appraised'.CLASSIFIED: 'Classified'.FAILED: 'Failed'.// Isolate backend "dirty" naming
  SYSTEM_BUSY: 'systemBusy',}Copy the code

Redundant type annotations

[Suggestion] Use TS type derivation to eliminate redundant comments.

Bad

STR and count have default values, and their types can be seen at a glance

function repeat(str: string = ' ', count: number = 0) {
  let result = ' ';

  for (let index = 0; index < count; index += 1) {
    result += str;
  }

  return result;
}
Copy the code
Good
function repeat(str = ' ', count = 0) {
  let result = ' ';

  for (let index = 0; index < count; index += 1) {
    result += str;
  }

  return result;
}
Copy the code

The required type is missing

A function type definition is a contract, a constraint on the implementation of a function, and a complete type encourages developers to design functions that better meet expectations.

Bad

Missing type annotation for return value

function repeat(str = ' ', count = 0) {
  let result = ' ';

  for (let index = 0; index < count; index += 1) {
    result += str;
  }

  return result;
}
Copy the code
Good
function repeat(str = ' ', count = 0) :string {
  let result = ' ';

  for (let index = 0; index < count; index += 1) {
    result += str;
  }

  return result;
}
Copy the code

reference

  • Martinrue.com/my-engineer…