JavaScript 代码整洁技巧

为什么代码要整洁?

代码质量与整洁度成正比。有的团队在赶工期的时候,不注重代码的整洁,代码写的越来越糟糕,项目越来越混乱,生产力也跟着下降,那就必须找更多人来提高生产力,开发成本越来越高。

整洁的代码是怎样的?

清晰表达意图、消除重复、简单抽象、能通过测试。 换句话说:具有可读性、可重用性和可重构性。

命名

  1. 名副其实:不使用缩写、不使用让人误解的名称,不要让人推测。

    // 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));// goodlocations.forEach(location => doSomeThing(location));
  1. 使用方便搜索的名称:避免硬编码,对数据用常量const记录。

    // bad: 86400000指的是?setTimeout(goToWork, 86400000);// good: 86400000是一天的毫秒数const MILLISECONDS_PER_DAY = 60 * 60 * 24 * 1000;setTimeout(goToWork, MILLISECONDS_PER_DAY);
  1. 类名应该是名词,方法名应该是动词。

    // badfunction visble() {}// goodfunction getVisble() {}
  1. 多个变量属于同一类型的属性,那就他们整合成一个对象。同时省略多余的上下文。

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

其他:

  • 不要写多余的废话,比如theMessagethe可以删除。

  • 统一术语。比如通知一词,不要一会在叫notice,一会叫announce

  • 用读得通顺的词语。比如getElementById就比 useIdToGetElement好读。

函数(方法)

  • 删除重复的代码,don't repeat yourself。很多地方可以注意dry,比如偷懒复制了某段代码、try...catch或条件语句写了重复的逻辑。

 // badtry {doSomeThing();clearStack();} catch (e) {handleError(e);clearStack();}// goodtry {doSomeThing();} catch (e) {handleError(e);} finally {clearStack();}
  • 形参不超过三个,对测试函数也方便。多了就使用对象参数。

    • 同时建议使用对象解构语法,有几个好处:

      1.  能清楚看到函数签名有哪些熟悉,
      2.  可以直接重新命名,
      3.  解构自带克隆,防止副作用,
      4.  Linter检查到函数未使用的属性。
      
// bad
function createMenu(title, body, buttonText, cancellable) {}// good
function createMenu({ title, body, buttonText, cancellable }) {}
  • 函数只做一件事,代码读起来更清晰,函数就能更好地组合、测试、重构。

// 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);
}
  • 自顶向下地书写函数,人们都是习惯自顶向下读代码,如,为了执行A,需要执行B,为了执行B,需要执行C。如果把A、B、C混在一个函数就很难读了。(看前一个的例子)。

  • 不使用布尔值来作为参数,遇到这种情况时,一定可以拆分函数。

 // badfunction createFile(name, temp) {if (temp) {fs.create(`./temp/${name}`);} else {fs.create(name);}}// goodfunction createFile(name) {fs.create(name);}function createTempFile(name) {createFile(`./temp/${name}`);}
  • 避免副作用。

  • 副作用的缺点:出现不可预期的异常,比如用户对购物车下单后,网络差而不断重试请求,这时如果添加新商品到购物车,就会导致新增的商品也会到下单的请求中。

  • 集中副作用:遇到不可避免的副作用时候,比如读写文件、上报日志,那就在一个地方集中处理副作用,不要在多个函数和类处理副作用。

  • 其它注意的地方:
    • 常见就是陷阱就是对象之间共享了状态,使用了可变的数据类型,比如对象和数组。对于可变的数据类型,使用immutable等库来高效克隆。

    • 避免用可变的全局变量。

  •   // bad:注意到cart是引用类型!const addItemToCart = (cart, item) => {cart.push({ item, date: Date.now() });};// goodconst addItemToCart = (cart, item) => {return [...cart, { item, date: Date.now() }];};
  • 封装复杂的判断条件,提高可读性。

  •  // badif (!(obj => obj != null && typeof obj[Symbol.iterator] === 'function')) {throw new Error('params is not iterable')}// goodconst 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时方便验证。

    • 可以添加埋点,和错误处理。

    • 可以延时加载对象的属性。

  // goodfunction makeBankAccount() {let balance = 0;function getBalance() {return balance;}function setBalance(amount) {balance = amount;}return {getBalance,setBalance};}const account = makeBankAccount();account.setBalance(100);
  • 使用私有成员。对外隐藏不必要的内容。

  // badconst 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// goodfunction 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}// goodclass 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,其实应该是20rectangle.render(area);});}const rectangles = [new Rectangle(), new Rectangle(), new Square()];// 这里替换了renderLargeRectangles(rectangles);// good: 取消正方形和长方形继承关系,都继承Shapeclass 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) - 定义是"客户不应被迫使用对其而言无用的方法或功能"。常见的就是让一些参数变成可选的。

 // badclass Dog {constructor(options) {this.options = options;}run() {this.options.run(); // 必须传入 run 方法,不然报错}}const dog = new Dog({}); // Uncaught TypeError: this.options.run is not a functiondog.run()// goodclass Dog {constructor(options) {this.options = options;}run() {if (this.options.run) {this.options.run();return;}console.log('跑步');}}
  • 依赖倒置原则(DIP) - 程序要依赖于抽象接口(可以理解为入参),不要依赖于具体实现。这样可以减少耦合度。

 // badclass OldReporter {report(info) {// ...}}class Message {constructor(options) {// ...// BAD: 这里依赖了一个实例,那你以后要换一个,就麻烦了this.reporter = new OldReporter();}share() {this.reporter.report('start share');// ...}}// goodclass 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工具,这里就不展开说了。

最后

接受第一次愚弄

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

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/199928.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

C/C++,图算法——求强联通的Tarjan算法之源程序

1 文本格式 #include <bits/stdc.h> using namespace std; const int maxn 1e4 5; const int maxk 5005; int n, k; int id[maxn][5]; char s[maxn][5][5], ans[maxk]; bool vis[maxn]; struct Edge { int v, nxt; } e[maxn * 100]; int head[maxn], tot 1; vo…

Unity UGUI控件之Horizontal Layout Group

Horizontal Layout Group是Unity中的UGUI控件&#xff0c;用于在水平方向上对子对象进行布局。 主要有一下作用&#xff1a; 水平布局&#xff1a;Horizontal Layout Group将子对象按照水平方向进行布局&#xff0c;可以控制子对象的排列顺序和间距。自动调整尺寸&#xff1a…

不到1000行代码,PyTorch团队让Llama 7B提速10倍

在过去的一年里&#xff0c;生成式 AI 发展迅猛&#xff0c;在这当中&#xff0c;文本生成一直是一个特别受欢迎的领域&#xff0c;很多开源项目如 llama.cpp、vLLM 、 MLC-LLM 等&#xff0c;为了取得更好的效果&#xff0c;都在进行不停的优化。 作为机器学习社区中最受欢迎框…

面试就是这么简单,offer拿到手软(四)—— 常见java152道基础面试题

面试就是这么简单&#xff0c;offer拿到手软&#xff08;一&#xff09;—— 常见非技术问题回答思路 面试就是这么简单&#xff0c;offer拿到手软&#xff08;二&#xff09;—— 常见65道非技术面试问题 面试就是这么简单&#xff0c;offer拿到手软&#xff08;三&#xff…

WIN10下解决HIVE 初始化MYSQL表报错:Unknown version specified for initialization

今天本地WINDOWS装HIVE&#xff0c;走到最后一步初始化数据库死活不通过&#xff1a; D:\hive\hive-rel-release-3.1.3\bin\ext>hive --service schematool -dbType mysql -initSchema --verbose SLF4J: Class path contains multiple SLF4J bindings. SLF4J: Found bind…

flask 上传文件

from flask import Flask, request, render_template,redirect, url_for from werkzeug.utils import secure_filename import os from flask import send_from_directory # send_from_directory可以从目录加载文件app Flask(__name__)#UPLOAD_FOLDER media # 注意&#xff…

深入理解强化学习——马尔可夫决策过程:占用度量-[代码实现]

分类目录&#xff1a;《深入理解强化学习》总目录 在文章《深入理解强化学习——马尔可夫决策过程&#xff1a;占用度量-[基础知识]》我们介绍了占用度量的基础知识&#xff0c;本文我们编写代码来近似估计占用度量。这里我们采用近似估计&#xff0c;即设置一个较大的采样轨迹…

会声会影2024购买多少钱 会声会影在哪里购买

掌握视频编辑技术&#xff0c;能为我们的工作和生活带来很多帮助。例如&#xff1a;将我们精心编辑的视频&#xff0c;上传到抖音、快手等平台进行变现&#xff1b;通过天马行空的视频创意&#xff0c;摇身一变成为B站up主。因此&#xff0c;拥有一款像会声会影这样的视频编辑软…

信号可靠性剖析

问题 基于信号发送的进程间通信方式可靠吗&#xff1f;&#xff1f;&#xff1f; 信号查看(kill -l) 信号的分类 不可靠信号 (传统信号) 信号值在 [1, 31] 之间的所有信号 可靠信号 (实时信号) 信号值在 [SIGRTMIN&#xff0c;SIGRTMAX]&#xff0c;即&#xff1a;[34&…

计算机组成原理学习-总线总结

复习本章时&#xff0c;思考以下问题&#xff1a; 1)引入总线结构有什么好处&#xff1f;2)引入总线结构会导致什么问题&#xff1f;如何解决&#xff1f;

Squid安装与配置(ip代理)

继前面一篇Tinyproxy安装与配置(ip代理)&#xff0c;在实际使用中会发现在请求一些网站时会被拒绝&#xff0c;那是因为Tinyproxy其实不支持所谓的高匿代理。所以这次用功能更加丰富的squid试试。 1、安装 yum install squid -y yum install httpd-tools -y2、配置 1、备份原…

变电站设计综合应用软件

产品概述 变电站设计综合应用软件,以下称为软件,是一款面向智能变电站虚拟二次回路设计和光纤回路设计的单机版桌面应用软件。软件为用户提供了直观易用、一键安装、功能齐全的轻量级的设计支撑。像常规的工具化软件一样,该软件在开始设计时需要通过新建一个项目,开启一段…

Oracle初始化参数文件pfile和spfile

pfile &#xff1a;Oracle 9i之前&#xff0c;ORACLE一直采用PFILE方式存储初始化参数&#xff0c;该文件为文本文件&#xff0c;可以在操作系统级别修改。当spfile文件修改出现错误导致oracle无法启动时&#xff0c;可以使用 pfile文件启动数据库 spfile&#xff1a;从Oracle…

Python 元组详解(tuple)

文章目录 1 概述1.1 性质1.2 下标1.3 切片 2 常用方法2.1 访问&#xff1a;迭代、根据下标2.2 删除&#xff1a;del2.3 运算符&#xff1a;、*2.4 计算元组中元素个数&#xff1a;len()2.5 返回元组中元素最大值&#xff1a;max()2.6 返回元组中元素最小值&#xff1a;min()2.7…

Linux快速给用户改密码

由于巡检过程中需要修改部分用户名密码&#xff0c;这些强密码包含大小写、数字和特殊符号&#xff0c;完全没有规律&#xff0c;让我手动输是不可能的&#xff0c;于是使用以下命令来输入&#xff0c;但是为了不在history里面留下痕迹&#xff0c;所以先关闭了历史命令功能&am…

【C++】const关键字的详解!!

&#x1f490; &#x1f338; &#x1f337; &#x1f340; &#x1f339; &#x1f33b; &#x1f33a; &#x1f341; &#x1f343; &#x1f342; &#x1f33f; &#x1f344;&#x1f35d; &#x1f35b; &#x1f364; &#x1f4c3;个人主页 &#xff1a;阿然成长日记 …

Android MVVM+coroutine+retrofit+flow+hilt

文章目录 Android MVVMcoroutineretrofitflowhilt概述依赖注入层数据层视图层模型视图层代码下载 Android MVVMcoroutineretrofitflowhilt 概述 代码结构&#xff1a; 依赖注入层 数据库&#xff1a; Module InstallIn(SingletonComponent::class) class DBModule {Singleto…

数据结构与算法-D2D3线性表之顺序表

线性表&#xff1a;包含若干数据元素的一个线性序列&#xff0c;特征如下&#xff1a; 1&#xff09;对非空表&#xff0c;a0是表头&#xff0c;无前驱&#xff1b; 2&#xff09;an-1是表尾&#xff0c;无后继&#xff1b; 3&#xff09;其他元素仅且仅有一个前驱&#xff0c;…

【PTA题目】6-1 猴子吃桃-递归 分数 10

6-1 猴子吃桃-递归 分数 10 全屏浏览题目 切换布局 作者 ZZULI 单位 郑州轻工业大学 小猴子第一天摘下桃子若干&#xff0c;当即吃掉一半&#xff0c;还不过瘾&#xff0c;又多吃一个&#xff0c;第二天又将剩下的桃子吃掉一半多一个&#xff0c;以后每天吃掉前一天剩下的一…

完美洗牌问题学习笔记

问题背景 完美洗牌&#xff1a;一副 52 张的排序好的扑克牌&#xff0c;从中间分为两半&#xff0c;每部分各 26 张。假设每次都分为左右两部分&#xff0c;然后将 右部分的牌和左部分的牌按顺序交错穿插&#xff0c;每张左部分牌后面加入一张右部分的&#xff0c;依序加入所有…