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.. finallyThe 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 👍