前言
为什么代码要整洁?
代码质量与整洁度成正比。有的团队在赶工期的时候,不注重代码的整洁,代码写的越来越糟糕,项目越来越混乱,生产力也跟着下降,那就必须找更多人来提高生产力,开发成本越来越高。
整洁的代码是怎样的?
清晰表达意图、消除重复、简单抽象、能通过测试。 换句话说:具有可读性、可重用性和可重构性。
命名
名副其实:不使用缩写、不使用让人误解的名称,不要让人推测。
// 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));
使用方便搜索的名称:避免硬编码,对数据用常量const记录。
// 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好读。
函数(方法)
删除重复的代码,don't repeat yourself。很多地方可以注意dry,比如偷懒复制了某段代码、try...catch或条件语句写了重复的逻辑。
// bad try { doSomeThing(); clearStack(); } catch (e) { handleError(e); clearStack(); } // good try { doSomeThing(); } catch (e) { handleError(e); } finally { clearStack(); }
形参不超过三个,对测试函数也方便。多了就使用对象参数。
同时建议使用对象解构语法,有几个好处:
能清楚看到函数签名有哪些熟悉,
可以直接重新命名,
解构自带克隆,防止副作用,
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混在一个函数就很难读了。(看前一个的例子)。
不使用布尔值来作为参数,遇到这种情况时,一定可以拆分函数。
// 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,没必要做多余类型判断。
注释
一般代码要能清晰的表达意图,只有遇到复杂的逻辑时才注释。
// 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 [] };