A competent developer is writing code for his future and the next “Bro” not just to implement features. For this reason, writing code that is easy to understand, easy to change, and easy to extend is the goal of every wise developer
In this article, the focus will be on JavaScript, but the same principles apply to other programming languages
Each writing method has its own applicable scenarios, so please choose the appropriate method according to the actual situation
Variable naming
Named variables should be able to reveal their intent
- Bad:
functin Example() {
if (!this.haveOffer) { // Do you have an offer
scrollTo(0.1150)
return
}
if (!this.earlyPractice) { // Whether to practice in advance
scrollTo(0.1150)
return
}
// doSomething...
}
Copy the code
Although the if statement is annotated, the naming is not intuitive, and there is a fixed naming format for such conditional statements: isXX, for example, isOfferValid
- Good:
functin Example() {
// Do you have an offer
if (!this.isHasOffer) {
scrollTo(0.1150);
return;
}
// Whether to practice in advance
if (!this.isEarlyPractice) {
scrollTo(0.1150);
return;
}
// doSomething...
}
Copy the code
No extra words should be added
- Bad
let nameValue;
let theProduct;
Copy the code
- Good
let name;
let product;
Copy the code
Unnecessary context should not be added
- Bad
const user = {
userName: "John".userSurname: "Doe".userAge: "28"
};
Copy the code
- Good
const user = {
name: "John".surname: "Doe".age: "28"
};
Copy the code
The default parameters
The default parameter is for undefined only
We usually use default arguments, but often people forget when functions use default arguments. Let’s look at the output of the following two functions
const Example = (val, amount = 1) = > {
if(! val){return
}
console.log(amount)
}
const Example2 = (val, amount) = > {
const temp = amount || 1
if(! val){return
}
console.log(temp)
}
Example('val'.null); // null
Example2('val'.null); / / 1
Copy the code
If in doubt, take a look at the Babel escape of the Example function
"use strict"
function Example(val) {
var amount = arguments.lenght > 1 && arguments[1]! = =undefined ? arguments[1] : 1;
if(! val){return
}
console.log(amount)
}
Copy the code
* * function only can use the default parameters for undefined parameters, * *, therefore, in using the default parameters is not equal to the amount | | 1
Use deconstruction with default parameters
When a function takes an object, you can use deconstruction to simplify logic (for simpler objects)
- Bad
const Example = (user, amount) = > {
const userInstance = user || {};
if(! userInstance.name || ! userInstance.age){return;
}
// doSomething...
}
Copy the code
- Good
const Example = ({ name, age } = {}, amount) = > {
if(! name || ! age){return;
}
// doSomething...
}
Copy the code
As an extension, if a function has more than two arguments, ES6’s destructuring syntax is also recommended, regardless of the order of the arguments
// Bad:
function Example( name, age, amount ) {
// doSomething...
}
// Good:
function Example( { name, age, amount } ) {
// doSomething...
}
createMenu({
name: 'nacy'.age: 14', amount: 1'});Copy the code
Optimization of conditional statements
Conditional statement logic is very common in ordinary code, but many people because of the convenience, will write not too brief conditional statement, resulting in code readability is very poor, and is not easy to expand, let’s look at how to write a better conditional statement
Avoid “not” conditionals
In the case of the following example, the use of “non-” conditional statements results in poor readability
- Bad
function isNotHasPath(path) {
// doSomething...
}
if(! isNotHasPath(path)) {// doSomething...
}
Copy the code
- Good
function isHasPath(path) {
// doSomething...
}
if (isHasPath(path)) {
// doSomething...
}
Copy the code
Exit nesting early
if… Else is the syntax we use most, when there is only one if… Else is easy to understand, if there are multiple if… Else (switch can also be used), and multiple branches and nesting can be painful if out of control
On the other hand, if there are many levels of nesting, it becomes difficult to understand the logic of the deeper return statements
- Bad1
const printAnimalDetails = animal= > {
let result;
if (animal.type) {
if (animal.name) {
if (animal.gender) {
result = `${name} - ${gender} - ${type}`;
} else {
result = "No animal gender"; }}else {
result = "No animal name"; }}else {
result = "No animal type";
}
return result;
};
Copy the code
Of course, you wouldn’t normally encounter code like this (too extreme), even for this simple logic, the code is difficult to understand, imagine what would happen if we had more complex logic? A lot of the if… Else statement, so modifying this code is equivalent to rewriting it
If you prefix the else section, you reduce the level of nesting and make your code more readable
- Good1
const printAnimalDetails = ({type, name, gender } = {}) = > {
if(! type) {return 'No animal type';
}
if(! name) {return 'No animal name';
}
if(! gender) {return 'No animal gender';
}
return `${name} - ${gender} - ${type}`;
}
Copy the code
The following example is one of the more common constructs encountered…
- Bad2
function Example(animal, quantity) {
const animals = ['dog'.'cat'.'hamster'.'turtle'];
if (animal) {
if (animals.includes(animal)) {
console.log(`${animal}`);
if (quantity) {
console.log('quantity is valid'); }}}else {
console.log('animal is invalid'); }}Copy the code
- Good2
function Example(animal, quantity) {
const animals = ['dog'.'cat'.'hamster'.'turtle'];
if(! animal) {console.log('animal is invalid');
}
if (animals.includes(animal)) {
console.log(`${animal}`);
if (quantity) {
console.log('xxxxx'); }}}Copy the code
Of course, many people believe that if… Else statements are easier to understand, which helps them follow the flow of the program with ease. However, with multiple layers of nesting, exiting nesting early is better for readability (if the function is too complex, consider whether it can be split).
Array.includes replaces several criteria
- Bad
function Example(animal) {
if (animal === 'dog' || animal === 'cat') {
console.log(`this is a ${animal}`);
}
// doSomething...
}
Copy the code
This seems acceptable considering that only two types are matched, but what if we wanted to add another matching condition? If you add more OR statements, the code becomes more difficult to maintain. For the above multi-conditional case, you can use array.includes
- Good
function Example(animal) {
const animals = [
'dog'.// Comments can be added here
'cat' // Comments can be added here
];
if (animals.includes(animal)) {
console.log(`this is a ${animal}`);
}
// doSomething...
}
Copy the code
At this point, if you want to add more types, you just need to add a new item. Similarly, if the array of type is referenced in more than one place, place the array in a common location for other code blocks to use
Use arrays to match all conditions
- Bad
The following code checks to see if all animals in the array are CAT
const animals = [
{ name: 'dog'.age: 1 },
{ name: 'cat'.age: 2 },
{ name: 'cat'.age: 3}];const isCheckAllCat = (animalName) = > {
let isValid = true;
for (let animal of animals) {
if(! isValid) {break;
}
isValid = animal.name === animalName;
}
return isValid;
}
console.log(isCheckAllCat('cat')); // false
Copy the code
We can rewrite it using the every method
- Good
const isCheckAllCat = (animalName) = > {
return animals.every(animal= > animal.name === animalName);
}
console.log(isCheckAllCat('renault'));
Copy the code
Corresponding methods include some and find
Use Object(Map) enumeration
Developers should often encounter matching statements like P02 and replaceWith when modifying old code. If you don’t comment, you don’t know what P02 and replaceWith stand for
if (info.haveOffer === 'Y') {... }if (info.interviewPhase === 'P02') {... }if(info.feedbackId ! = ='socialRecruitmentNotDevelop') {... }if (attr === 'replaceWith') {... }if (file.status === 'fail') {... }if (tab === 'titleCertificate') {... }Copy the code
- Bad
getTechInterviewFeedBack (info) {
if(! info) {console.log('info invalid');
return ' '
}
if (info.interviewPhase === 'P01') {
if (info.feedbackId === 'socialRecruitment') {
return 'first stag interview on social'
} else if (info.feedbackId === 'schoolRecruiment') {
return 'first stag interview on school'}}if (info.interviewPhase === 'P02') {
if (info.feedbackId === 'socialRecruitment') {
return 'second stag interview on social'
} else if (info.feedbackId === 'schoolRecruiment') {
return 'second stag interview on school'}}// More if... The else statement...
}
Copy the code
This function contains many matching statements. First, we do not know what the matching string represents, and second, how do we avoid the error if other blocks of code or other files use the same string to match?
- Good
/** Interview stage */
const INTERVIEW_PHASE_TYPE_MAP = {
firstTech: 'P01'.// Technical side
secondTech: 'P02' // Technical side 2
}
/** Feedback type */
const FEEDBACK_TYPE_MAP = {
social: 'socialRecruitment'./ / club
school: 'schoolRecruiment' / / the school recruit
}
/** Get technical feedback */
getTechInterviewFeedBack (info) {
const { interviewPhase, feedbackId } = info
const isInterviewPhaseOnFirstTech = interviewPhase === INTERVIEW_PHASE_TYPE_MAP.firstTech
const isInterviewPhaseOnSecondTech = interviewPhase === INTERVIEW_PHASE_TYPE_MAP.secondTech
const isFeedbackFromSocial = feedbackId === FEEDBACK_TYPE_MAP.social
const isFeedbackFromSchool = feedbackId === FEEDBACK_TYPE_MAP.school
if(! info) {console.log('info invalid');
return ' '
}
// The first technical aspect
if (isInterviewPhaseOnFirstTech) {
if (isFeedbackFromSocial) {
return 'first stag interview on social'
}
if (isFeedbackFromSchool) {
return 'first stag interview on school'}}// The second technical aspect
if (isInterviewPhaseOnSecondTech) {
if (isFeedbackFromSocial) {
return 'second stag interview on social'
}
if (isFeedbackFromSchool) {
return 'second stag interview on school'}}// More if... The else statement...
}
Copy the code
Note: the above code will continue to be optimized, please refer to the section “Reasonable packaging” for details
Using Object to declare constants for these matches provides two benefits:
(1) It is convenient for multiple code blocks and multiple code files to use the same matching value, convenient for unified management, synchronous modification, and easy to expand
(2) Reduce the debugging cost caused by the incorrect spelling of ****
(3) You can annotate **key:value** on **Object** to improve code readability
Optimize the mapping using Object(Map)
Object and Map have a natural mapping relationship. Key <=> Value can be very useful in some scenarios
- Bad
function Example(val) {
switch (val) {
case 'BEFORE_CREATE':
return 'QuickGuide';
case 'CREATED':
return 'CompleteToDoGuide';
case 'FINISHED':
return 'Finished';
default:
return ' ';
}
// doSomething...
}
Copy the code
- Good
/** write a comment */
const SOURCE_MAP {
BEFORE_CREATE: 'QuickGuide'./ / comment
CREATED: 'CompleteToDoGuide'./ / comment
FINISHED: 'Finished'./ / comment
};
// Instead of a simple conditional judgment
function resourceInfo (val) {
return SOURCE_MAP[val] || ' ';
}
Copy the code
Use optional chains and invalid merges
Optional chains allow us to work with tree structures without explicitly checking for the presence of intermediate nodes, and null-value merging is very effective in conjunction with optional chains, which can be used to ensure non-existent defaults
- Bad
const street = user.address && user.address.street;
const street = user && user.address || 'default address';
// Common with some interface fetching functions
$page.search.getJobs(this.params).then(res= > {
if (res && res.datas && res.datas.length) {
// doSomething...
} else {
// doSomething...
}
}).catch(err= > {
// doSomething...
})
Copy the code
- Good
conststreet = user.address? street;conststreet = user? .address ??'default address';
Copy the code
This?? What if…? If the value on the left is null or undefined, the value on the right is returned. The optional chain makes the code look cleaner. The optional chain also supports the DOM API, which means you can do the following:
const value = document.querySelector('div#animal-dom')? .value;Copy the code
Reasonable packaging
Refining function
In the process of development, we are thinking how to encapsulate components, most of the time how to encapsulate function, the function is the function of independent and pure, if a function is too complex, have to add some comments to make this function is easy to read, that this function is very necessary to refactor, a common optimization is the content of independence will be able to operate, Put it in a separate function, and then introduce it
The main advantages of this are as follows.
- Avoid super-large functions
- Separate functions facilitate code reuse
- Independent functions are easier to overwrite
- A separate function with a good name acts as a comment in itself
Many people know the benefits of function encapsulation, but everyone has a different idea of “independent” and “pure,” as in the following code, which fetches data through an interface and distributes it
// Vue
async fetchPhaseList () {
try {
this.loading = true
const params = this.paramsFormatter()
const { success, data, msg } = await $demandManage.queryDemandHcInfo(
params
)
if(! success || ! data) {throw new Error(msg)
}
const resData = Array.from(data || [])
this.tableData = this.dataFormatter(resData)
} catch (err) {
this.$message.error(Failed to get list data (${err.toString()}) `)}finally {
this.loading = false}}Copy the code
It seems to make sense to say that getting a PhaseList and processing it as a whole is a whole, but in my opinion, the function is not “pure” enough, and there are several elements inside the function that make the code look tangled
try.. catch.. finally
The result is that the function is split into three pieces, so you have three{}
- After the interface returns data, it also checks the validity of the data, resulting in another data
{}
The visible code itself is actually not complicated, the function is very simple, just get the data, judge the validity, and then assign to tableData, and finally on this basis to actively catch exceptions. Still, the code is “messy” at first sight. If I had to change the logic of the data assignment, I would have skimmed the whole function for fear of affecting something else
“Pure data processing” should be distinguished from “business logic”. Combined with the above code, “pure data processing” is to obtain data through the interface, and judge the validity of data, and this validity judgment is standardized (that is to say, everyone is to judge whether success is true. And whether data has value), “business logic” is to get valid data, according to the function of the corresponding data processing. In general, the probability of “pure data processing” being modified twice is low, while that of “business logic” is on the contrary. And the “pure data processing” part of this function is obviously more than the “business logic”, which obviously brings resistance to the subsequent business modification
Therefore, we need to split the functions into “pure data processing” and “business logic” by creating a common exception catching function (in the utils directory) before splitting.
Note: Exception catching is more recommended at the Ajax level
/** * Catch code block error message *@param {function} cb- Main logic (Ajax-based) *@param {string} fileName- The name of the code to call the catch function *@param {string} functionName- Call the function name of the catch function */
async catchErrorMessage (cb, fileName = 'null', functionName = 'null') {
if (cb) {
try {
await cb()
} catch (err) {
this.$message.error(err.toString())
const path = location && location.pathname || 'null'
// Report to sentry
this.$Sentry({
extra: {
error_From_path: path, // Error page path
error_From_fileName: fileName || 'null'.error_From_functionName: functionName || 'null'
},
level: 'error'})}}}// "Data processing"
async queryPhaseList (params) {
try {
const { success, data, msg } = await $demandManage.queryDemandHcInfo(
params
)
if(! success || ! data) {throw new Error(msg)
}
const resData = Array.from(data || [])
return this.dataFormatter(resData)
} catch (err) {
this.$message.error(Failed to get list data (${err.toString()}) `)}}// "business logic"
async handlePhaseList () {
this.loading = true
const params = this.paramsFormatter()
await utils.catchErrorMessage(async() = > {this.tableData = await queryPhaseList(params)
})
this.loading = false
}
Copy the code
A typical page has multiple interfaces and similar code segments, so “pure data processing” as a non-business logic function can be managed in a separate file, leaving only the “business logic” function inside the component, which is what we really care about
Merge duplicate code blocks
If a function has conditional statements inside it that contain duplicate code, it may be necessary to optimize to separate out the duplicates
- Bad
// Vue
async fetchPhaseList () {
try {
this.loading = true
// ...
const { success, data, msg } = await $demandManage.queryDemandHcInfo(
params
)
this.loading = false
if(! success || ! data) {throw new Error(msg)
}
// ...
} catch (err) {
this.$message.error('Request failed (${err.toString()}) `)
this.loading = false}}Copy the code
The above code wants to set loading to false after the interface returns and the code block fails
- Good
// Vue
async fetchPhaseList () {
try {
this.loading = true
// ...
const { success, data, msg } = await $demandManage.queryDemandHcInfo(
params
)
// ...
} catch (err) {
this.$message.error('Request failed (${err.toString()}) `)}finally {
this.loading = false // set to false}}Copy the code
Distill conditional statements into functions
This principle does not mean that a conditional statement should be refined into a function, but it needs to be judged according to the actual scene. If there are several conditions in a function, and each judgment content is complex and these conditions are independent, then it can be refined into a function and see the example directly
- Bad
/** Get technical feedback */
getTechInterviewFeedBack (info) {
const { interviewPhase, feedbackId } = info
const isInterviewPhaseOnFirstTech = interviewPhase === INTERVIEW_PHASE_TYPE_MAP.firstTech
const isInterviewPhaseOnSecondTech = interviewPhase === INTERVIEW_PHASE_TYPE_MAP.secondTech
const isFeedbackFromSocial = feedbackId === FEEDBACK_TYPE_MAP.social
const isFeedbackFromSchool = feedbackId === FEEDBACK_TYPE_MAP.school
if(! info) {console.log('info invalid');
return ' '
}
// The first technical interview
if (isInterviewPhaseOnFirstTech) {
if (isFeedbackFromSocial) {
return 'first stag interview on social'
}
if (isFeedbackFromSchool) {
return 'first stag interview on school'}}// Second technical interview
if (isInterviewPhaseOnSecondTech) {
if (isFeedbackFromSocial) {
return 'second stag interview on social'
}
if (isFeedbackFromSchool) {
return 'second stag interview on school'}}// More if... The else statement...
}
Copy the code
This example is an example from the previous section on conditional optimization
You can see that the function already expresses its function fairly clearly, but what if the function does more? Or will other situations be added later? This code is going to get more and more complex and become a giant function
Taking the above code as an example, the function contains two cases (first technical interview, second technical interview), which are not related to each other and can therefore be isolated
- Good
/** Get technical feedback */
function getFirstTechInterviewFeedBack(type) {
const isFeedbackFromSocial = type === FEEDBACK_TYPE_MAP.social
const isFeedbackFromSchool = type === FEEDBACK_TYPE_MAP.school
if (isFeedbackFromSocial) {
return 'first stag interview on social'
}
if (isFeedbackFromSchool) {
return 'first stag interview on school'
}
return ' '
}
/** Get technical feedback */
function getSecondTechInterviewFeedBack(type) {
const isFeedbackFromSocial = type === FEEDBACK_TYPE_MAP.social
const isFeedbackFromSchool = type === FEEDBACK_TYPE_MAP.school
if (isFeedbackFromSocial) {
return 'second stag interview on social'
}
if (isFeedbackFromSchool) {
return 'second stag interview on school'
}
return ' '
}
/** Get technical feedback */
function getTechInterviewFeedBack (info) {
if(! info) {console.log('info invalid');
return ' '
}
const { interviewPhase, feedbackId } = info
const isInterviewPhaseOnFirstTech = interviewPhase === INTERVIEW_PHASE_TYPE_MAP.firstTech
const isInterviewPhaseOnSecondTech = interviewPhase === INTERVIEW_PHASE_TYPE_MAP.secondTech
if (isInterviewPhaseOnFirstTech) {
return getFirstTechInterviewFeedBack(feedbackId)
}
if (isInterviewPhaseOnSecondTech) {
return getSecondTechInterviewFeedBack(feedbackId)
}
// More if... The else statement...
}
Copy the code
Avoid global methods whenever possible
If you want to modify global/public methods, be careful! For example, if you want to add a print method to the Array prototype to print all the elements in order, but the next person wants to add a print method to the Array prototype, and he wants to print all the elements in reverse order, but he doesn’t know that you have the definition when he adds it, then you will have a conflict. Array is a JS-defined object, and developers often use this method. If you want to expand, it is only for the sake of your business. You should not affect the definition of Array objects, but use polymorphism and inheritance to expand
- Bad
Array.prototype.print = function print(ArrayList) {
for(let i = 0; i < ArrayList.length; i++) {
console.log(ArrayList[i])
}
};
Copy the code
- Good
class BetterArray extends Array {
print(ArrayList) {
for(let i = 0; i < ArrayList.length; i++) {
console.log(ArrayList[i])
}
}
};
Copy the code
If you want to expand further, just expand the BetterArray method
Remove deprecated code
In a word, delete! If you do not delete it now, you can add detailed TODO: to describe it
conclusion
I hope reading this article will help you. Feel free to correct any errors in the comments section
If you find this article helpful, 🏀🏀 leave your precious 👍