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…