想分享这个主题好久了,前些天和好朋友聊天的时候也提到过,我以为新手最像新手的地方就在于新手考虑事情老是不够全面,这体如今不少方面,好比设计上、技术选型上等等。今天我想分享给你们的是其中一个方面:异常处理。javascript
正好前些时间在手机上看到这么一则小故事:前端
故事很可笑,可能你们看完都会以为这群程序员也太蠢了,连这种情况都没有考虑到。其实在咱们工做中,也经常会出现只关注于正常流程处理的开发,而忽略一些非正常的流程的状况。java
其实讨论程序异常处理的话题比较大,我大概分红三个方面来讲,一个是程序的容错,一个是业务上的容错,另外一个是安全方面。node
程序的容错是咱们写代码过程当中应该首要考虑的地方,好比当你要编写一个模块给别人使用时,若是没有考虑到一些特殊状况的输入,可能模块就没法正常使用了。程序员
须要考虑容错实际上是至关常见的场景,好比这里我写了一个好玩的“睡排序”,假设要给别人使用。面试
代码比较简单,你们可能也能看出里面常见的几个可能抛异常的地方。数据库
首先是 data.whatever.inner.array
嵌套太深了,极可能读取不到。这里咱们经常使用缺省参数。如 ES6 中的写法:json
第二个可能有问题的地方是 done 函数的调用,done 极可能外部没有传递。这里咱们经常使用短路运算的方式避免:后端
最后还有一个可能比较隐晦的地方,这里是对数字进行排序,因此咱们应该考虑数组中每一项的类型是否合法,也就是类型校验。类型校验又是另一个话题,这里只提一下对数字的校验方法。跨域
你们用的比较多的应该是 isNaN 方法了,这是挂载在 window 上的方法。其实 ES6 也提供了一个 isNaN 方法,并挂载在了 Number 上,也就是 Number.isNaN。这二者的区别在于 window.isNaN 传入 undefined、非空字符串等等其实并非 NaN 的参数时也返回 true,而 Number.isNaN 会首先判断参数是否为 number,因此咱们更推荐使用 Number.isNaN。
上面是一个比较简单的例子,咱们针对性地提出了三点改进。有了这些改进,咱们的程序不会报错,但在咱们日常的开发过程当中其实有些异常是不可避免的,咱们必需要针对可能出现的异常进行异常捕获,说到这里,你们可能最早想到的就是 try...catch 语句了。
try catch 是你们用的比较多的,这里就不详细展开说了,只提一下你们可能忽略的地方,就是 try catch 其实有三种形式:
finally 块是无论异常与否都会进入的地方,咱们经常在这里面作一些清理工做,在前端多是把一些变量置为 null 避免内存泄露,后端多是关闭数据库链接等等。
另外须要注意的是在 finally 中 return 的值将做为 try catch 语句的总体返回值,无论在 try 或者 catch 中是否已经 return 了。
Promise 的异常捕获是我在面试中比较常问的问题。Promise 是异步的,因此对其 try catch 是没有做用的。
咱们通常使用 catch 方法去处理 Promise 的 reject 状态,这里须要知道的是 Promise 的 catch 方法只是一种特殊的 then 方法,catch 方法等同于调用 then 方法但把第一个参数置为 undefined。
另外提一个新手可能会犯的错误,在 then 方法中的 onResolved 回调中抛出的异常只会让返回的新 Promise 的状态置为 reject,而不会让同一 then 方法中的 onRejected 方法执行。
async/await 是比 Promise 使用起来更方便也更容易理解的语法,它让异步的执行有了同步的写法。对于 async/await 的异常捕获,我以为只要理解两个地方就行:
async 函数能够当作普通函数调用,也可使用 await 表达式调用。看成为普通函数调用时,如上所述,该函数返回一个 Promise,咱们使用 Promise 的异常捕获便可:
当使用 await 表达式调用时,会使 async 函数暂停执行,等待表达式中的 Promise 解析完成后继续执行 async 函数并返回解决结果。因此咱们可使用 try catch 进行捕获:
其实以上说到的都算针对性的异常捕获,但在实际开发中,咱们总会碰到咱们没考虑到的程序异常,这里可使用一个全局的异常捕获方法进行处理。
在浏览器里,提供了 window.onerror 事件,它会捕获程序中出现的未被捕获到的同步或异步的错误。
在事件处理函数中,能获取到错误信息、当前 URL、代码行数、列数等,很是详细。
篇幅缘由,nodejs 的异常捕获就不展开说了,在 process 上有三个事件:
说完程序里的容错,咱们接着说一下业务上的容错。其实在本文开头提到的例子就是一个典型的业务上没有进行容错的例子。其实业务上的容错更多的是产品须要考虑的问题,但做为开发,咱们也须要去理解业务,并能敏锐地发现一些业务中可能碰到的问题,避免开发到一半须要推翻重来。
代码上抛出异常只会让你的程序不可用,说白了用户体验就不会好。但一旦业务上发生问题,那么影响的可能就是产品的业务线了。
举个例子,咱们以前作了个项目,由于订单的状态太多,用户会有不少不一样的操做来使订单转移到不一样的状态,不一样的状态又会反应出不一样的操做。最后就在不一样状态的切换中乱套了,致使上线以后收到了不少用户状态错误的反馈。
这跟开发每每没有什么太大的联系,而是对业务的理解。
最后跟你们聊一下安全。咱们说了这么多,其实侧重的都是正经常使用户的使用,但在实际生产环境中,咱们必须还要考虑一些恶意的输入。
篇幅缘由也不展开说了。。简单提一下这几种攻击。
面试的时候我也会尝试性问一下对方在安全方面考虑的问题。通常会从跨域开始问,而后天然而然提到 jsonp,jsonp 可让咱们进行跨域请求,但其中是否会有安全问题?毕竟别人也能经过 jsonp 访问你的网站了。
另外,当用户登陆了 a.com 以后,打开了一个 evil.com,在 evil.com 里向 a.com 发送请求,是会携带 a.com 的 cookie 的,包括 jsonp 请求,这里的安全问题又要如何避免?
通常咱们会使用添加 token 的方式来防止 CSRF 攻击,token 能够隐藏在表单中,或者在请求头里携带,做为一个合法请求的校验。
这个多是前端接触比较多的攻击,通常分为反射型的 XSS 攻击和存储型的 XSS 攻击。区别在于存储型的 XSS 攻击会使恶意代码存放在服务端,致使全部能看到这段代码的用户都受影响。
XSS 攻击的诱导常见于一些恶意连接,当用于访问一些奇怪的网址(好比邮箱里的垃圾邮件、广告连接),可能会跳转到具备 XSS 漏洞的网站,从而引起安全问题。
通常咱们须要考虑对用户的输入进行合法校验,包括前端和后端。
SQL 注入是后端同窗须要着重考虑的问题,对于一些小白代码,特别常见于一些学校的管理后台,极可能有 SQL 注入的漏洞,举个最简单的例子:
connection.query(`SELECT username from user where username = "${username}" and password = "${password}"`, function () {
// ...
})
复制代码
若是后端对用户输入没有进行任何过滤,直接是这么校验用户登陆的话,那么只要用户猜出用户名,如常见的 admin,而后使用 " or "1" = "1,就能正常登陆了。
说了这么多,其实今天的主题就一个,那就是但愿你们在之后的开发中,必定要多考虑异常的状况,不要想固然地觉得用户必定会按照正常的流程走进来。固然,这也须要产品同窗考虑各类可能出现的业务状况、测试同窗进行各类 case 的测试。在你们共同的努力下,才能保证整个产品不会出现大的漏洞。
若是发现以上内容有任何不正确的地方,或者想一块儿探讨的,欢迎在评论区留言~