「为什么代码要整洁?」——代码整洁度对于项目质量的影响,让我们通过这边文章来教你js和ts的代码整洁技巧

11 阅读5分钟

       chunk: file.slice(cur, cur + size)      });      cur += size;    }    return chunkList  }    function saveFileInfoInLocalStorage(file, chunkList) {    localStorage.setItem(file, file.name);    localStorage.setItem(chunkListLength, chunkList.length);  }


* 自顶向下地书写函数,人们都是习惯自顶向下读代码,如,为了执行A,需要执行B,为了执行B,需要执行C。如果把AB、C混在一个函数就很难读了。(看前一个的例子)。
* 不使用布尔值来作为参数,遇到这种情况时,一定可以拆分函数。



// 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}); }


* 避免副作用。


	+ 常见就是陷阱就是对象之间共享了状态,使用了可变的数据类型,比如对象和数组。对于可变的数据类型,使用**immutable**等库来高效克隆。
	+ 避免用可变的全局变量。
	+ 副作用的缺点:出现不可预期的异常,比如用户对购物车下单后,网络差而不断重试请求,这时如果添加新商品到购物车,就会导致新增的商品也会到下单的请求中。
	+ 集中副作用:遇到不可避免的副作用时候,比如读写文件、上报日志,那就在一个地方集中处理副作用,不要在多个函数和类处理副作用。
	+ 其它注意的地方:



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


* 封装复杂的判断条件,提高可读性。



// 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') }


* 在方法中有多条件判断时候,为了提高函数的可扩展性,考虑下是不是可以使用能否使用多态性来解决。



// 地图接口可能来自百度,也可能来自谷歌 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));


其他


* 如果用了**TS**,没必要做多余类型判断。


### 注释


1. 一般代码要能清晰的表达意图,只有遇到复杂的逻辑时才注释。



// 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. 警示作用,解释此处不能修改的原因。



// hack: 由于XXX历史原因,只能调度一下。 setTimeout(doSomething, 0)


1. TODO注释,记录下应该做但还没做的工作。另一个好处,提前写好命名,可以帮助后来者统一命名风格。



class Comment {  // todo: 删除功能后期实现  delete() {} }


1. 没用的代码直接删除,不要注释,反正**git**提交历史记录可以找回。



// 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. 避免循规式注释,不要求每个函数都要求jsdoc,jsdoc一般是用在公共代码上。



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


### 对象


* 多使用**getter****setter**(getXXX和setXXX)。好处:


	+**set**时方便验证。
	+ 可以添加埋点,和错误处理。
	+ 可以延时加载对象的属性。



// good function makeBankAccount() {  let balance = 0;  function getBalance() {    return balance;  }  function setBalance(amount) {    balance = amount;  }  return {    getBalance,    setBalance  }; } const account = makeBankAccount(); account.setBalance(100);


* 使用私有成员。对外隐藏不必要的内容。



  // 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;      }    };   }


### 类


**solid**


* **单一职责原则 (SRP)** - 保证“每次改动只有一个修改理由”。因为如果一个类中有太多功能并且您修改了其中的一部分,则很难预期改动对其他功能的影响。



  // 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()) {        // ...      }    }   }


* **开闭原则 (OCP)** - 对扩展放开,但是对修改关闭。在不更改现有代码的情况下添加新功能。比如一个方法因为有switch的语句,每次出现新增条件时就要修改原来的方法。这时候不如换成多态的特性。



  // 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      });    }   }


* **里氏替换原则 (LSP)**


	+ 如果S是T的子类,则T的**对象**可以替换为S的对象,而不会破坏程序。
	+ 所有引用其父类**对象方法**的地方,都可以透明的替换为其子类对象。
	+ 也就是,保证任何父类对象出现的地方,用其子类的对象来替换,不会出错。下面的例子是经典的正方形、长方形例子。
	+ 两个定义



  // 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);


* **接口隔离原则 (ISP)** - 定义是客户不应被迫使用对其而言无用的方法或功能。常见的就是让一些参数变成可选的。



// 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('跑步');    }  }


* **依赖倒置原则(DIP)** - 程序要依赖于抽象接口(可以理解为入参),不要依赖于具体实现。这样可以减少耦合度。



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

收集整理了一份《2024年最新物联网嵌入式全套学习资料》,初衷也很简单,就是希望能够帮助到想自学提升的朋友。 img img

如果你需要这些资料,可以戳这里获取

一个人可以走的很快,但一群人才能走的更远!不论你是正从事IT行业的老鸟或是对IT行业感兴趣的新人

都欢迎加入我们的的圈子(技术交流、学习资源、职场吐槽、大厂内推、面试辅导),让我们一起学习成长!