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

news/2024/10/30 23:18:33/

前言

为什么代码要整洁?

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

整洁的代码是怎样的?

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

命名

  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));// good
locations.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. 类名应该是名词,方法名应该是动词。

// bad
function visble() {}// good
function 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事件,并创建文件的切片,并保存相关信息到localStoragefunction 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混在一个函数就很难读了。(看前一个的例子)。

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

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

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

对象

  • 多使用gettersetter(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);
  • 使用私有成员。对外隐藏不必要的内容。

  // 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.ppmy.cn/news/118270.html

相关文章

Java中常见转换-数组与list互转、驼峰下划线互转、Map转Map、List转Map、进制转换的多种方式

场景 Java中数组与List互转的几种方式 数组转List 1、最简单的方式,Arrays.asList(array); 创建的是不可变列表&#xff0c;不能删除和新增元素 String[] array new String[]{"a","b"};List<String> stringList Arrays.asList(array);System.ou…

java汽车的风挡玻璃_悄悄告诉你:擦拭风挡玻璃的内表面可防雾

冬季天气寒冷&#xff0c;致使燃油的流动性变差&#xff0c;润滑油的黏性增加&#xff0c;冷却水易冻结。因此&#xff0c;在冬季这样恶劣的条件下使用汽车&#xff0c;若方法不得当&#xff0c;会降低汽车使用寿命&#xff0c;甚至会发生事故。对此&#xff0c;专业技师对这个…

2021年全球与中国汽车和运输玻璃行业市场规模及销售渠道分析

2021年全球与中国汽车和运输玻璃行业市场规模及销售渠道分析 本报告研究全球与中国市场汽车和运输玻璃的发展现状及未来发展趋势&#xff0c;分别从生产和消费的角度分析汽车和运输玻璃的主要生产地区、主要消费地区以及主要的生产商。重点分析全球与中国市场的主要厂商产品特…

中国汽车零部件行业需求预测及投资前景建议报告2022-2028年版

中国汽车零部件行业需求预测及投资前景建议报告2022-2028年版 【报告目录】: 第一章 2019-2022年汽车工业发展概述 1.1 2019-2021年全球汽车工业整体分析 1.1.1 全球汽车产量分析 1.1.2 全球汽车销量分析 1.1.3 全球汽车品牌竞争 1.1.4 美国汽车市场分析 1.1.5 德…

为什么电源纹波那么大?

某用户在用500MHz带宽的示波器对其开关电源输出5V信号的纹波进行测试时&#xff0c;发现纹波和噪声的峰峰值达到了900多mV&#xff08;如下图所示&#xff09;&#xff0c;而其开关电源标称的纹波的峰峰值<20mv。虽然用户电路板上后级还有LDO对开关电源的这个输出再进行稳压…

汽车内外饰设计工程师是做什么的?有无发展前景-予菲汽车学习营分享

汽车内外饰设计工程师是做什么的&#xff1f;有无发展前景-予菲汽车学习营分享 汽车内外饰设计工程师是做什么的&#xff1f; 汽车内外饰设计工程师获取前人汽车内外饰设计工程经验系统总结。经常会碰到很多想从事汽车内外饰设计工作而不知怎么学习入门&#xff0c;或是已经在…

当软件定义汽车成为趋势,未来汽车是否可以理解为四个轮子上的超级计算机?

文章目录 浅谈汽车软件行业汽车软件的现状和发展方向本文首发于EE汽车荟&#xff0c;在微信公众号搜索“EE汽车荟”可以查看。简介&#xff1a;本文就目前比较热的“汽车软件”话题&#xff0c;做一些讨论。也试图回答大家比较关心的三个问题。内容主要有三方面&#xff1a;1&a…

汽车电子——常见的英文缩写(更新中)

目录 一、前言 二、Item缩写 三、部分item详解 一、前言 如今智能驾驶驾驶系统正高速发展&#xff0c;传感器据说会增长到上千个&#xff0c;域控制器的发展会简化ECU的数量&#xff0c;但是该有的传感器必不可少&#xff0c;了解它们是有必要的。 二、Item缩写 EZS-----点火…