本文首发于 vivo互联网技术 微信公众号
连接: https://mp.weixin.qq.com/s/sd2oX0Z_cMY8_GvFg8pO4Q
做者:杨昆
上篇 《如何编写高质量的 JS 函数(1) -- 敲山震虎篇 》介绍了函数的执行机制,此篇将会从函数的命名、注释和鲁棒性方面,阐述如何编写高质量的 JS 函数。javascript
从上图能够知道,命名和缓存是计算机科学中的两大难题。前端
本文要说的函数命名,虽然涉及到的范围较窄,但思想都同样,彻底能够借鉴到其余的形式中。java
以前阅读过代码大全中变量的相关章节,也针对性的了解过一些源码,根据个人经验总结,目前函数命名除了业界标准的问题外,还存在一些细节的问题,好比:node
下面进行简明扼要的分析。react
汉语拼音存在多义性;汉字翻译的辅助工具还不够普及,所以不能用汉语方式命名。git
最大的难点在于语法的正确使用场景。github
举个例子, react 的生命周期,以下:web
不少人都会有疑问,为何用 did 和 will 。express
举个例子:npm
答案就在下图:
注意上图中的 did 表明通常过去时,will 表明通常未来时。
而后咱们百科通常过去式和通常未来时,如图所示。
(1)通常过去时:
(2)通常未来时:
看上图的红箭头,did 表示通常过去时,时是指动做发生的时间,用在这里,突出了钩子的含义,一旦 mount 成功就执行此函数。will 同理。
这是个小特性,好比 shouldComponentUpdate , 为何 should 放在最前面。
由于这个函数返回的值是布尔值。那么咱们能够理解为这是个问句,经过问句的形式来告诉咱们,这里具备不肯定性,须要根据返回值来判断是否更新。
这是一个神器,用来搜索各类开源项目中的变量命名,以供参考。
对应名字的 VSCODE 插件也有。
ramda 源码中的一个函数,代码以下:
var forEachObjIndexed = _curry2(function forEachObjIndexed(fn, obj) { var keyList = keys(obj); var idx = 0; while (idx < keyList.length) { var key = keyList[idx]; fn(obj[key], key, obj); idx += 1; } return obj; }); export default forEachObjIndexed;
这个函数叫 forEachObjIndexed ,代码中看不出这个函数的命名含义,只能从从源码里面的函数注释中找答案。
函数注释以下图:
由此知道,若是函数命名存在困难,能够经过注释的方式,将函数的总体介绍和说明输出文档来解决这个问题。
函数命名很广泛的一个现象就是带各类前缀的函数名,以下:
- $xxx() - _xxx()
这种带各类前缀的函数名,看起来并很差看还别扭。核心缘由是 JS 语言不支持私有变量,致使只能使用 _ 或者 $ 来保证相应的对外不可见。
因此我把前端的函数命名分为两大类,以下:
第一类:不想暴露给外部访问的函数(好比只给内部使用)第二类:暴露给外部访问的函数(各类功能方法)
而Symbol 初始化的函数命名是一种特例,代码以下:
const ADD = Symbol('add') [ADD](a, b) { console.log('a + b') }
总结一下最佳实践:多学习初中高中英语语法,开源项目中的函数命名没有那么难理解,经过语法的学习和借助工具,函数命名基本能够解决,若是遇到没法清晰描述所写函数的目的的命名时,请务必给函数写上良好注释,无论函数名字有多长多难懂,只要有良好的注释,那就是能够接受的一件事情。
函数注释,一方面提升了可读性,另外一方面还能够生成在线文档。
一个高质量的函数,注释少不了,可是这并不表明全部的函数都须要注释。富有富的活法,穷有穷的潇洒,重要或者复杂的函数,能够写个好注释;简单或者不重要的函数,能够不写注释或者写一个简单的注释。
那么,目前函数的注释都有哪几种方式呢?
从图中能够看到 egg.js 的入口文件的注释特色是简单整洁。
继续看下图:
这是一个被抽象出来的基类,展现了做者 [Yiyu He] 当时写这个类的时候,其注释的风格有如下几点:
第一点:构造函数的注释规则,表达式语句的注释规则。第二点:注释的取舍,有一些变量能够不用注释,有些要注释,不要有那种要注释就要所有注释的思想。
再看两张有趣的图片:
看上面两张图的箭头,指向的都是同一个做者 [fengmk2] 。他的函数注释规则,第一张图没有空格,第二种有空格,还有对返回的 this 的注释,好比不少人习惯将 this 直接注释成 Object 类型。
说到函数注释,就不能不说到 lodash.js 。因为篇幅有限,本文就不作相应介绍了,你们自行按照上面的方式去了解。
有人说注释要很规范,方便给别人,好比用 jsdoc 等 。个人观点是,对一些不须要开源的 web 项目,没有必要用 jsdoc , 理由以下:
1.繁琐,须要按照 jsdoc 规则来。2.我的认为,jsdoc 有入侵性,文档规则须要写在代码中。
若是要写注释说明手册,对于大型项目,我推荐使用 apidoc , 由于 apidoc 入侵性不强,不要求把规则写在代码中,能够把全部规则写到一个文件中。
可是通常小项目,没有必要单独写一份 api 文档。若是是开源的大型项目,首先须要考虑是否有开源的官方网站,你会看到网上的一些开源项目官网好像很酷,其实这个世界上不缺的就是轮子,你也能够很快的作出这样的网站,
下面咱们来看看是如何作到的。
首先看一下 taro 源码,以下图:
这里就是生成一个静态网站的秘密,执行 npm run docs 就能够。用的是 docusaurus 包。
从上图中能够知道,文档的内容,来源于 docs 目录,里面都是 md 文件,开源项目的文档说明都在这里。
固然也有把对应的文档直接放到对应的代码目录下的,好比 ant-design 以下图:
就是直接把文档放在组件目录下了。
从这里咱们能够知道,目前流行的开源项目的官方网站是怎么实现的,以及文档该怎么写。
下面说说我本人对函数注释(只针对函数注释)的一些我的风格或者意见。
- Better Comments 给注释上色
- Document This 自动生成注释
- TODO Highlight 高亮 TODO ,并能够搜寻全部 TODO
下面是一张演示图:
个人观点是不影响可读性,复杂度低,对外界没有过分干涉的函数能够不写注释。
函数内,表达式语句的注释能够简单点。以下图所示,// 后面加简要说明。
function add(a, b) { // sum .... let sum = a + b }
function say() { // TODO: 编写 say 具体内容 console.log('say') }
function fix() { // FIXME: 删除 console.log方法 console.log('fix') }
通常分为普通函数和构造函数。
(1)普通函数注释:
/** * add * @param {Number} a - 数字 * @param {Number} b - 数字 * @returns {Number} result - 两个整数之和 */ function add(a, b) { // FIXME: 这里要对 a, b 参数进行类型判断 let result = a + b return (result) }
(2)构造函数注释:
class Kun { /** * @constructor * @param {Object} opt - 配置对象 */ constructor(opt = {}) { // 语句注释 this.config = opt } }
从开源项目的代码中能够发现,在遵照注释的基本原则的基础上,注释的风格多种多样;同一个做者不一样项目的注释风格也有所差异,但我会尽量的去平衡注释和不注释。
下图是一个段子:
最后一句,测试测了那么多场景,最后酒吧仍是炸了,怎么回事?
从中咱们能够看出,防护性编程的核心是:
把全部可能会出现的异常都考虑到,而且作相应处理。而我我的认为,防护性的程度要看其重要的程度。通常来讲,不可能去处理全部状况的,可是提升代码鲁棒性的有效途径就是进行防护性的编程。
我曾经接手过一个需求,重写微信小程序的登陆注册绑定功能,并将代码同步到其余小程序(和其余小程序的开发进行代码交接并协助 coder 平稳完成版本过渡)。
这个项目因为用户的基数很大,风险程度很高,须要考虑不少场景,好比:
如何去合理的完成这个需求仍是比较有难度的。
PS: 关于第4点的如何确保单个节点出问题,不会影响整个登陆流程,文末有答案。
下面我就关于函数鲁棒性,说一说我我的的一些见解。
在 ES6+ 到来后,函数的入参写法已经获得了质的提升和优化。看下面代码:
function print(obj = {}) { console.log('name', obj.name) console.log('age', obj.age) }
print 函数,入参是 obj 经过 obj = {} 来给入参设置默认的参数值,从而提升入参的鲁棒性。
同时会发现,若是入参的默认值是 {} ,那函数里面的 obj.name 就会是 undefined ,这也不够鲁棒,因此下面就要说说函数内表达式语句的鲁棒性了。
继续上个例子:
function print(obj = {}) { console.log('name:', obj.name || '未知姓名') console.log('age:', obj.age || '未知年龄') }
若是这样的话,表达式语句变得比较鲁棒性了,但还不够抽象,咱们换种方式稍微把表达式语句给解耦一下,代码以下:
function print(obj = {}) { const { name = '未知姓名', age = '未知年龄' } = obj console.log('name:', name console.log('age:', age) }
上述代码其实还能够再抽象,好比把 console.log 封装成 log 函数,经过调用 log(name) ,就能完成 console.log('name:', name) 的功能。
那如何去更好的处理各类异常,提升函数的鲁棒性呢,我我的有如下几点见解。
js 在 node.js 提供的运行时环境中运行,node.js 是用 C++ 写的。C++ 有本身的异常处理机制,也是有 try/catch 。即 js 的 try/catch 的底层实现是直接经过桥,调用 C++ 的 try/catch 。
而 C++ 的 try/catch 具备一些特性,如try/catch 只能捕捉当前线程的异常。这样就解释了为何 JS 的 try/catch 只能捕捉到同步的异常,而对于异步的异常就无能为力了(由于异步是放在另外一个线程中执行的)。
这里是个人推导,不表明确切答案。
这里我推荐一篇博客:《C++中try、catch 异常处理机制》 ,有兴趣的能够看看。
第一个方法:若是是同步操做,能够用 throw 来传递异常
看下面代码:
try { throw new Error('hello godkun, i am an Error ') console.log('throw 以后的处代码不执行') } catch (e) { console.log(e.message) }
首先 throw 是以同步的方式传递异常的,也就是 throw 要和使用 throw 传递错误的函数拥有相同的上下文环境。
若是上下文环境中,都没有使用 try/catch 的话,可是又 throw 了异常,那么程序大几率会崩溃。
若是是 nodejs ,此时应该再加一个进程级的 uncaughtException 来捕捉这种没有被捕捉的异常。一般还会加上 unhandledRejection 的异常处理。
第二个方法:若是是异步的操做
有三种方式:
怎么去选择哪一个方式呢?依据如下原则:
第三个方法:若是既有异步操做又有同步操做
最好的方式就是使用最新的语法:async/await 来结合 promise 和 try/catch 来完成对既有同步操做又有异步操做的异常捕捉。
第四个方法:处理异常的一些抽象和封装
对处理异常的函数进行抽象和封装也是提升函数质量的一个途径。如何对处理异常进行抽象和封装呢?有几个方式能够搞定它:
合理的处理异常,根据具体状况来肯定使用合理的方式处理异常
这里推荐一篇博客:《Callback Promise Generator Async-Await 和异常处理的演进》
好比登陆流程须要4个安全验证,按照一般的写法,其中一个挂了,那就所有挂了,可是这不够鲁棒性,如何去解决这个问题呢。
主要方案就使用将 promise 的链式写法换一种方式写,之前的写法是这样的:
伪代码以下:
auth().then(getIP).then(getToken).then(autoLogin).then(xxx).catch(function(){})
通过鲁棒调整后,能够改为以下写法:
伪代码以下:
auth().catch(goAuthErrorHandle).then(getIP).catch(goIPErrorHandle).then(function(r){})
通过微调后的代码,直接让登陆流程的鲁棒性提高了不少,就算出错也能够经过错误处理后,继续传递到下一个方法中。
我我的认为对异常的处理,仍是要根据实际状况来分析的。大概有如下几点见解:
要考虑项目可维护性,团队技术水平
我曾在一个需求中,使用了诸如函子等较为抽象的处理异常的方法,虽然秀了一把(做死),结果致使后续这块的需求改动,还得我本身来。
要提早预估好项目的复杂性和重要性。
好比在作一个比较重要的业务时,一开始没有想到异常处理须要这么细节,并且通常初版的时候,需求并无涉及到不少异常状况处理,可是后续需求迭代优化的时候,发现异常状况处理是如此的多,直接致使须要重写异常处理相关的代码。
因此之后在项目评估的时候,要学会尝试根据项目的重要性,来提早预留好坑位。
这也算是一种面对将来的编程模式。
关于函数的鲁棒性(防护性编程),本文主要介绍了前端或者是 nodejs 处理异常的常规方法。
处理异常不是一个简单的活,工做中还得结合业务去肯定合适的异常处理方式,总之,多多实践出真知。
更多内容敬请关注 vivo 互联网技术 微信公众号
注:转载文章请先与微信号:labs2020 联系。