"Why should the code be clean?"——The impact of code cleanliness on project quality, let us use this article to teach you js and ts code cleanliness skills to make your project more outstanding

foreword

Why should the code be clean?

Code quality is directly proportional to cleanliness. Some teams don’t pay attention to the cleanliness of the code when they are rushing to meet the deadline. The code is getting worse and worse, the project is getting more and more chaotic, and the productivity is also declining. Then more people must be found to improve productivity, and the development cost is getting higher and higher. high.

What does clean code look like?

Clear intent, eliminate repetition, simple abstraction, can pass the test. In other words: be readable, reusable, and reconfigurable.

name

  1. Live up to the name: no abbreviations, no misleading names, no speculation.

// bad: 啥?
const yyyymmdstr = moment().format(YYYY/MM/DD);
// bad: 缩写
const cD = moment().format(YYYY/MM/DD);

// good:
const currentDate = moment().format(YYYY/MM/DD);
const locations = [Austin, New York, San Francisco];
// bad:推测l是locations的项
locations.forEach(l => doSomeThing(l));

// good
locations.forEach(location => doSomeThing(location));
  1. Use searchable names: avoid hardcoding, and use constant const records for data.

// bad: 86400000指的是?
setTimeout(goToWork, 86400000);

// good: 86400000是一天的毫秒数
const MILLISECONDS_PER_DAY = 60 * 60 * 24 * 1000;
setTimeout(goToWork, MILLISECONDS_PER_DAY);
  1. Class names should be nouns and method names should be verbs.

// bad
function visble() {}

// good
function getVisble() {}
  1. Multiple variables belong to the same type of property, then they are integrated into one object. Also omit redundant context.

// bad:可以整合
const carMake = Honda,
const carModel = Accord,
const carColor = Blue,

// bad: 多余上下文
const Car = {
 carMake: Honda,
 carModel: Accord,
 carColor: Blue,
};

// good
const Car = {
 make: Honda,
 model: Accord,
 color: Blue,
};

other:

  • Don't write redundant nonsense, such as theMessagecan thebe deleted.

  • Uniform terms. For example, the word "notification" should not be called for a while notice, and called for a while announce.

  • Use easy-to-read words. For example getElementById, it is  useIdToGetElementeasier to read.

function (method)

  • Remove duplicate code, don't repeat yourself . You can pay attention to dry in many places, such as copying a certain piece of code lazily, writing repeated logic in try...catch or conditional statements.

 // bad
 try {
    doSomeThing();
    clearStack();
 } catch (e) {
    handleError(e);
    clearStack();
 }
 
 // good
 try {
   doSomeThing();
 } catch (e) {
   handleError(e);
 } finally {
   clearStack();
 }
  • There are no more than three formal parameters, which is also convenient for testing functions. If there are more, use the object parameter.

  • At the same time, it is recommended to use object destructuring syntax, which has several advantages:

    1. Can clearly see the familiarity of the function signature,

    2. can be renamed directly,

    3. Deconstruction comes with cloning to prevent side effects,

    4. Linter checks for unused attributes in functions.

// bad
function createMenu(title, body, buttonText, cancellable) {}

// good
function createMenu({ title, body, buttonText, cancellable }) {}
  • Functions do only one thing, the code is clearer to read, and the functions can be better composed, tested, and refactored.

 // bad: 处理了输入框的change事件,并创建文件的切片,并保存相关信息到localStorage
 function handleInputChange(e) {
   const file = e.target.files[0];
   
   // --- 切片 ---
   const chunkList = [];
   let cur = 0;
   while (cur < file.size) {
     chunkList.push({
     chunk: file.slice(cur, cur + size)
   });
   cur += size;
 }
 // --- 保存信息到localstorage ---
 localStorage.setItem(file, file.name);
 localStorage.setItem(chunkListLength, chunkList.length);
 }
 
 // good: 将三件事分开写,同时自顶而下读,很舒适
 function handleInputChange(e) {
   const file = e.target.files[0];
   const chunkList = createChunk(file);
   saveFileInfoInLocalStorage(file, chunkList);
 }
 
 function createChunk(file, size = SLICE_SIZE) {
   const chunkList = [];
   let cur = 0;
   while (cur < file.size) {
     chunkList.push({
       chunk: file.slice(cur, cur + size)
     });
     cur += size;
   }
   return chunkList
 }
 
 function saveFileInfoInLocalStorage(file, chunkList) {
   localStorage.setItem(file, file.name);
   localStorage.setItem(chunkListLength, chunkList.length);
 }
  • Writing functions from top to bottom, people are used to reading code from top to bottom, for example, in order to execute A, you need to execute B, and in order to execute B, you need to execute C. If you mix A, B, and C in one function, it will be difficult to read. (see previous example).

  • Don't use boolean values ​​as parameters, and when you encounter this situation, you can definitely split the function.

// bad
function createFile(name, temp) {
  if (temp) {
    fs.create(`./temp/${name}`);
  } else {
    fs.create(name);
  }
}

// good
function createFile(name) {
  fs.create(name);
}

function createTempFile(name) {
  createFile(`./temp/${name}`);
}
  • Avoid side effects.

    • The common trap is that objects share state and use mutable data types, such as objects and arrays. For mutable data types, use libraries such as immutable for efficient cloning.

    • Avoid mutable global variables.

    • Disadvantages of side effects: Unexpected exceptions occur. For example, after the user places an order in the shopping cart, the network is poor and the request is continuously retried. At this time, if new products are added to the shopping cart, the newly added products will also be placed in the order Requesting.

    • Centralized side effects: When encountering unavoidable side effects, such as reading and writing files and reporting logs, then handle side effects in one place instead of multiple functions and classes.

    • Other places to pay attention to:

// bad:注意到cart是引用类型!
const addItemToCart = (cart, item) => {
  cart.push({ item, date: Date.now() });
};
// good
const addItemToCart = (cart, item) => {
  return [...cart, { item, date: Date.now() }];
};
  • Encapsulate complex judgment conditions to improve readability.

// bad
if (!(obj => obj != null && typeof obj[Symbol.iterator] === 'function')) {
  throw new Error('params is not iterable')
}

// good
const isIterable = obj => obj != null && typeof obj[Symbol.iterator] === 'function';
if (!isIterable(promises)) {
  throw new Error('params is not iterable')
}
  • When there are multiple conditions in the method, in order to improve the scalability of the function, consider whether it can be solved by using polymorphism.

// 地图接口可能来自百度,也可能来自谷歌
const googleMap = {
  show: function (size) {
    console.log('开始渲染谷歌地图', size));
  }
};

const baiduMap = {
  render: function (size) {
    console.log('开始渲染百度地图', size));
  }
};

// bad: 出现多个条件分支。如果要加一个腾讯地图,就又要改动renderMap函数。
function renderMap(type) {
  const size = getSize();
  if (type === 'google') {
    googleMap.show(size);
  } else if (type === 'baidu') {
    baiduMap.render(size);
  }
};
renderMap('google')

// good:实现多态处理。如果要加一个腾讯地图,不需要改动renderMap函数。
// 细节:函数作为一等对象的语言中,作为参数传递也会返回不同的执行结果,也是“多态性”的体现。
function renderMap (renderMapFromApi) {
  const size = getSize();
  renderMapFromApi(size);
}
renderMap((size) => googleMap.show(size));

other

  • If TS is used , there is no need to make redundant type judgments.

note

  1. The general code should be able to clearly express the intention, and only comment when encountering complex logic.

// good:由于函数名已经解释不清楚函数的用途了,所以注释里说明。
// 在nums数组中找出 和为目标值 target 的两个整数,并返回它们的数组下标。
const twoSum = function(nums, target) {
 let map = new Map()
 for (let i = 0; i < nums.length; i++) {
   const item = nums[i];
   const index = map.get(target - item)
   if (index !== undefined){
     return [index, i]
   }
   map.set(item, i)
 }
 return []
};

// bad:加了一堆废话
const twoSum = function(nums, target) {
 // 声明map变量
 let map = new Map()
 // 遍历
 for (let i = 0; i < nums.length; i++) {
   const item = nums[i];
   const index = map.get(target - item)
   // 如果下标为空
   if (index !== undefined){
     return [index, i]
   }
   map.set(item, i)
 }
 return []
};
  1. A warning, explaining why this cannot be modified.

// hack: 由于XXX历史原因,只能调度一下。
setTimeout(doSomething, 0)
  1. TODO comments, record the work that should be done but has not been done. Another advantage, writing the naming in advance can help the latecomers to unify the naming style.

class Comment {
 // todo: 删除功能后期实现
 delete() {}
}
  1. Delete useless code directly, do not comment, anyway, the git submission history can be retrieved.

// bad: 如下,重写了一遍两数之和的实现方式
// const twoSum = function(nums, target) {
//     for(let i = 0;i<nums.length;i++){
//         for(let j = i+1;j<nums.length;j++){
//             if (nums[i] + nums[j] === target) {
//                 return [i,j]
//             }
//         }
//     }
// };

const twoSum = function(nums, target) {
 let map = new Map()
 for (let i = 0; i < nums.length; i++) {
   const item = nums[i];
   const index = map.get(target - item)
   if (index !== undefined){
     return [index, i]
   }
   map.set(item, i)
 }
 return []
};
  1. Avoid conforming comments, do not require jsdoc for each function , jsdoc is generally used in public code.

// bad or good?
/**
* @param {number[]} nums
* @param {number} target
* @return {number[]}
*/
 const twoSum = function(nums, target) {}

object

  • Use getters and setters more (getXXX and setXXX). benefit:

    • It is convenient to verify when setting .

    • Buried points and error handling can be added.

    • Object properties can be loaded lazily.

// good
function makeBankAccount() {
 let balance = 0;
 function getBalance() {
   return balance;
 }
 function setBalance(amount) {
   balance = amount;
 }
 return {
   getBalance,
   setBalance
 };
}
const account = makeBankAccount();
account.setBalance(100);
  • Use private members. Hide unnecessary content from the outside world.

  // bad
  const Employee = function(name) {
    this.name = name;
  };
  Employee.prototype.getName = function getName() {
    return this.name;
  };
  
  const employee = new Employee(John Doe);
  delete employee.name;
  console.log(employee.getName()); // undefined
  
  // good
  function makeEmployee(name) {
   return {
     getName() {
       return name;
     }
   };
  }

kind

solid

  • Single Responsibility Principle (SRP)  - Guarantee that "every change has only one reason to change". Because if you have too many functions in a class and you modify some of them, it is difficult to predict the impact of the change on other functions.

  // bad:设置操作和验证权限放在一起了
  class UserSettings {
   constructor(user) {
     this.user = user;
   }
   changeSettings(settings) {
     if (this.verifyCredentials()) {
       // ...
     }
   }
   verifyCredentials() {
     // ...
   }
  }
  
  // good: 拆出验证权限的类
  class UserAuth {
   constructor(user) {
     this.user = user;
   }
   verifyCredentials() {
     // ...
   }
  }
  
  class UserSettings {
   constructor(user) {
     this.user = user;
     this.auth = new UserAuth(user);
   }
   changeSettings(settings) {
     if (this.auth.verifyCredentials()) {
       // ...
     }
   }
  }
  • Open Closed Principle (OCP)  - Open for extension, but closed for modification. Add new functionality without changing existing code. For example, because a method has a switch statement, the original method must be modified every time a new condition appears. At this time, it is better to replace it with polymorphic features.

  // bad: 注意到fetch用条件语句了,不利于扩展
  class AjaxAdapter extends Adapter {
   constructor() {
     super();
     this.name = ajaxAdapter;
   }
  }
  
  class NodeAdapter extends Adapter {
   constructor() {
     super();
     this.name = nodeAdapter;
   }
  }
  
  class HttpRequester {
   constructor(adapter) {
     this.adapter = adapter;
   }
   fetch(url) {
     if (this.adapter.name === ajaxAdapter) {
       return makeAjaxCall(url).then(response => {
         // transform response and return
       });
     } else if (this.adapter.name === nodeAdapter) {
       return makeHttpCall(url).then(response => {
         // transform response and return
       });
     }
   }
  }
  function makeAjaxCall(url) {
   // request and return promise
  }
  function makeHttpCall(url) {
   // request and return promise
  }
  
  // good
  class AjaxAdapter extends Adapter {
   constructor() {
     super();
     this.name = ajaxAdapter;
   }
   request(url) {
     // request and return promise
   }
  }
  
  class NodeAdapter extends Adapter {
   constructor() {
     super();
     this.name = nodeAdapter;
   }
   request(url) {
     // request and return promise
   }
  }
  
  class HttpRequester {
   constructor(adapter) {
     this.adapter = adapter;
   }
   fetch(url) {
     return this.adapter.request(url).then(response => {
       // transform response and return
     });
   }
  }
  • Liskov Substitution Principle (LSP)

    • If S is a subclass of T, objects of T can be replaced with objects of S without breaking the program.

    • All references to the method of its parent class object can be transparently replaced by its subclass object.

    • That is, to ensure that any place where an object of the parent class appears is replaced by an object of its subclass without error. The following examples are classic square and rectangular examples.

    • two definitions

  // bad: 用正方形继承了长方形
  class Rectangle {
   constructor() {
     this.width = 0;
     this.height = 0;
   }
   setColor(color) {
     // ...
   }
   render(area) {
     // ...
   }
   setWidth(width) {
     this.width = width;
   }
   setHeight(height) {
     this.height = height;
   }
   getArea() {
     return this.width * this.height;
   }
  }
  
  class Square extends Rectangle {
   setWidth(width) {
     this.width = width;
     this.height = width;
   }
   setHeight(height) {
     this.width = height;
     this.height = height;
   }
  }
  
  function renderLargeRectangles(rectangles) {
   rectangles.forEach(rectangle => {
     rectangle.setWidth(4);
     rectangle.setHeight(5);
     const area = rectangle.getArea(); // BAD: 返回了25,其实应该是20
     rectangle.render(area);
   });
  }
  const rectangles = [new Rectangle(), new Rectangle(), new Square()];// 这里替换了
  renderLargeRectangles(rectangles);
  
  // good: 取消正方形和长方形继承关系,都继承Shape
  class Shape {
   setColor(color) {
     // ...
   }
   render(area) {
     // ...
   }
  }
  class Rectangle extends Shape {
   constructor(width, height) {
     super();
     this.width = width;
     this.height = height;
   }
   getArea() {
     return this.width * this.height;
   }
  }
  
  class Square extends Shape {
   constructor(length) {
     super();
     this.length = length;
   }
   getArea() {
     return this.length * this.length;
   }
  }
  
  function renderLargeShapes(shapes) {
   shapes.forEach(shape => {
     const area = shape.getArea();
     shape.render(area);
   });
  }
  const shapes = [new Rectangle(4, 5), new Rectangle(4, 5), new Square(5)];
  renderLargeShapes(shapes);
  • Interface Segregation Principle (ISP)  - Definition is that clients should not be forced to use methods or functionality that are not useful to them. It is common to make some parameters optional.

// bad
 class Dog {
   constructor(options) {
     this.options = options;
   }
   run() {
     this.options.run(); // 必须传入 run 方法,不然报错
   }
 }
 const dog = new Dog({}); // Uncaught TypeError: this.options.run is not a function
  
 dog.run()
 
 // good
 class Dog {
   constructor(options) {
     this.options = options;
   }
   run() {
   if (this.options.run) {
     this.options.run();
     return;
   }
     console.log('跑步');
   }
 }
  • Dependency Inversion Principle (DIP)  - The program depends on the abstract interface (which can be understood as an input parameter), not on the specific implementation. This reduces coupling.

// bad
 class OldReporter {
   report(info) {
     // ...
   }
 }
 
 class Message {
   constructor(options) {
     // ...
     // BAD: 这里依赖了一个实例,那你以后要换一个,就麻烦了
     this.reporter = new OldReporter();
   }
   share() {
     this.reporter.report('start share');
     // ...
   }
 }
 
 // good
 class Message {
   constructor(options) {
     // reporter 作为选项,可以随意换了
     this.reporter = this.options.reporter;
   }
   share() {
     this.reporter.report('start share');
     // ...
   }
 }
 class NewReporter {
   report(info) {
     // ...
   }
 }
 new Message({ reporter: new NewReporter });

other

  • Prefer ES2015/ES6 classes over ES5 normal functions.

  • Use method chaining more often.

  • Use composition more than inheritance.

error handling

  • Do not ignore caught errors. Instead, it is necessary to fully respond to errors, such as console. error () to the console, submitting error logs, reminding users, and other operations.

  • Don't miss the reject in the catch promise .

Format

You can use the eslint tool, so I won’t go into it here.

at last

accept the first fool

It is not an easy task to make the program clean from the beginning. Don't change the code repeatedly like obsessive-compulsive disorder, because the construction period is limited and there is not so much time. Wait until the next requirement changes and you find a problem with the code, it’s not too late to change it.

Do as the Romans do

The code style of each company and project is different, and there will be differences from the suggestions in this article. If you take over a mature project, it is recommended to continue writing code according to the style of this project (without refactoring). Because forming a unified code style is also a kind of clean code.

Guess you like

Origin blog.csdn.net/qq_48652579/article/details/130967286