为什么代码要整洁?
代码质量与整洁度成正比。有的团队在赶工期的时候,不注重代码的整洁,代码写的越来越糟糕,项目越来越混乱,生产力也跟着下降,那就必须找更多人来提高生产力,开发成本越来越高。
整洁的代码是怎样的?
清晰表达意图、消除重复、简单抽象、能通过测试。 换句话说:具有可读性、可重用性和可重构性。
// 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));
// bad: 86400000指的是?
setTimeout(goToWork, 86400000);
// good: 86400000是一天的毫秒数
const MILLISECONDS_PER_DAY = 60 * 60 * 24 * 1000;
setTimeout(goToWork, MILLISECONDS_PER_DAY);
// bad
function visble() {}
// good
function getVisble() {}
// 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",
};
其他:
不要写多余的废话,比如theMessage
的the
可以删除。
统一术语。比如通知一词,不要一会在叫notice
,一会叫announce
。
用读得通顺的词语。比如getElementById
就比 useIdToGetElement
好读。
// bad
try {
doSomeThing();
clearStack();
} catch (e) {
handleError(e);
clearStack();
}
// good
try {
doSomeThing();
} catch (e) {
handleError(e);
} finally {
clearStack();
}
形参不超过三个,对测试函数也方便。多了就使用对象参数。
同时建议使用对象解构语法,有几个好处:
// 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混在一个函数就很难读了。(看前一个的例子)。
不使用布尔值来作为参数,遇到这种情况时,一定可以拆分函数。
// 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}`);
}
避免副作用。
副作用的缺点:出现不可预期的异常,比如用户对购物车下单后,网络差而不断重试请求,这时如果添加新商品到购物车,就会导致新增的商品也会到下单的请求中。
集中副作用:遇到不可避免的副作用时候,比如读写文件、上报日志,那就在一个地方集中处理副作用,不要在多个函数和类处理副作用。
其它注意的地方:
// 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));
其他
// 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 []
};
// hack: 由于XXX历史原因,只能调度一下。
setTimeout(doSomething, 0)
class Comment {
// todo: 删除功能后期实现
delete() {}
}
// 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 []
};
// bad or good?
/**
* @param {number[]} nums
* @param {number} target
* @return {number[]}
*/
const twoSum = function(nums, target) {}
多使用getter和setter(getXXX和setXXX)。好处:
// 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
// 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()) {
// ...
}
}
}
// 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)
两个定义
// 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);
// 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('跑步');
}
}
// 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工具,这里就不展开说了。
让程序一开始就做到整洁,并不是一件很容易的事情。不要强迫症一样地反复更改代码,因为工期有限,没那么多时间。等到下次需求更迭,你发现到代码存在的问题时,再改也不迟。
每个公司、项目的代码风格是不一样的,会有与本文建议不同的地方。如果你接手了一个成熟的项目,建议按照此项目的风格继续写代码(不重构的话)。因为形成统一的代码风格也是一种代码整洁。
参考:
《代码整洁之道》 github.com/ryanmcdermo…[1] (里面有很多例子。有汉化但没更新)
来自:xuwentao https://juejin.cn/post/7224382896626778172
文章引用微信公众号"Vue中文社区",如有侵权,请联系管理员删除!