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

简介: 警示作用,解释此处不能修改的原因。

警示作用,解释此处不能修改的原因。

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


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

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


没用的代码直接删除,不要注释,反正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 []
};


避免循规式注释,不要求每个函数都要求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
 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 });



其他


优先使用 ES2015/ES6 类而不是 ES5 普通函数。


多使用方法链。


多使用组合而不是继承。


错误处理


不要忽略捕获的错误。而要充分对错误做出反应,比如console.error()到控制台,提交错误日志,提醒用户等操作。


不要漏了catch promise中的reject。


格式


可以使用eslint工具,这里就不展开说了。


最后


接受第一次愚弄

让程序一开始就做到整洁,并不是一件很容易的事情。不要强迫症一样地反复更改代码,因为工期有限,没那么多时间。等到下次需求更迭,你发现到代码存在的问题时,再改也不迟。


入乡随俗

每个公司、项目的代码风格是不一样的,会有与本文建议不同的地方。如果你接手了一个成熟的项目,建议按照此项目的风格继续写代码(不重构的话)。因为形成统一的代码风格也是一种代码整洁。


相关文章
|
5天前
|
JSON JavaScript 前端开发
JavaScript原生代码处理JSON的一些高频次方法合集
JavaScript原生代码处理JSON的一些高频次方法合集
|
20天前
|
开发框架 JavaScript 安全
js开发:请解释什么是Express框架,以及它在项目中的作用。
Express是Node.js的Web开发框架,简化路由管理,支持HTTP请求处理。它采用中间件系统增强功能,如日志和错误处理,集成多种模板引擎(EJS、Jade、Pug)用于HTML渲染,并提供安全中间件提升应用安全性。其可扩展性允许选用合适插件扩展功能,加速开发进程。
|
20天前
|
缓存 JavaScript 前端开发
js开发:请解释什么是Webpack,以及它在项目中的作用。
Webpack是开源的JavaScript模块打包器,用于前端项目构建,整合并优化JavaScript、CSS、图片等资源。它实现模块打包、代码分割以提升加载速度,同时进行资源优化和缓存。借助插件机制扩展功能,并支持热更新,加速开发流程。
15 4
|
20天前
|
JSON 前端开发 JavaScript
16个重要的JavaScript代码
16个重要的JavaScript代码
29 1
|
20天前
|
JavaScript 前端开发 编译器
js开发: 请解释什么是Babel,以及它在项目中的作用。
**Babel是JavaScript编译器,将ES6+代码转为向后兼容版本,确保在旧环境运行。它在前端构建中不可或缺,提供语法转换、插件机制、灵活配置及丰富的生态系统,支持代码兼容性和自定义编译任务。**
17 6
|
22天前
|
JavaScript
当当网新用户注册界面——JS代码
当当网新用户注册界面——JS代码
7 0
|
22天前
|
JavaScript
当当网首页——JS代码
当当网首页——JS代码
8 1
|
JavaScript 前端开发 数据格式
|
2月前
|
JavaScript
Node.js【GET/POST请求、http模块、路由、创建客户端、作为中间层、文件系统模块】(二)-全面详解(学习总结---从入门到深化)
Node.js【GET/POST请求、http模块、路由、创建客户端、作为中间层、文件系统模块】(二)-全面详解(学习总结---从入门到深化)
27 0
|
2月前
|
消息中间件 Web App开发 JavaScript
Node.js【简介、安装、运行 Node.js 脚本、事件循环、ES6 作业队列、Buffer(缓冲区)、Stream(流)】(一)-全面详解(学习总结---从入门到深化)
Node.js【简介、安装、运行 Node.js 脚本、事件循环、ES6 作业队列、Buffer(缓冲区)、Stream(流)】(一)-全面详解(学习总结---从入门到深化)
69 0