您还未登录! 登录 | 注册 | 帮助  

您的位置: 首页 > 软件开发专栏 > 开发技术 > 正文

JavaScript静态代码分析的初学者指南

发表于:2021-05-06 作者:陈峻 来源:51cto

您是否经历过编写代码的困惑?您的代码库是否存在着前后不一致的现象?在每次审核代码时,您是否会感到焦虑?如果您对这些问题的任何一项回答为“是”的话,那么静态代码分析应该能够为您提供帮助。

所谓静态代码分析,是指在执行代码之前,对其进行分析的过程。通过集成静态代码分析器,开发人员可以优化整个工作流程。

本文将向您介绍什么是JavaScript静态代码分析,为什么要使用它,以及如何在项目中通过快速设置来实现它。

什么是静态代码分析?

顾名思义,静态代码分析是对处于静态、或非执行状态的代码进行分析。它在效果上等同于让另一名开发人员去阅读和审查您的代码。当然,其效率可能不及自动化工具。

与测试有何不同?

无论是单元测试、功能测试、集成测试、视觉测试、还是回归测试,所有这些类型的测试都是通过运行代码,将结果与已知的预期输出状态进行比较,以检查是否一切正常。可以说,测试是将您的代码视为黑盒子,通过为其提供输入,来验证执行后的输出,以确保代码能够按照预期运行。

然而,静态代码分析主要是针对程序代码的可读性、一致性、错误处理、类型检查、以及缩进排版等方面进行分析。可以说,静态分析并非关注代码能否提供预期的输出,而是关注代码本身的编写方式。它是对源代码质量(而非功能性)的分析。

总而言之,测试着眼于检查代码是否有效,而静态分析则会检查代码是否编写正确。在理想情况下,您应该在项目中,让测试和静态分析相辅相成。

为什么要使用静态代码分析?

从程序格式化小工具,到漏洞扫描器,甚至程序审查器,任何能够读取源代码,对其进行解析,并提出改进建议的工具,都属于静态代码分析器。下面,让我们看看有哪些工作场景会用到静态代码分析。

全面审查

人是会犯错的。据统计,只有15%的JSHint(一种流行的JavaScript代码审查工具)代码库的安装过程,能够顺利通过。正所谓“当局者迷,旁观者清”,“第二双眼睛”往往可以发现您在自己的代码中,那些永远不会察觉到的问题。据此,开发人员不但可以了解项目中的某些内置功能,而且能够提出更好、更便捷的实现方法。

那么,我们是否能引入更多的“眼睛”、更全面的“扫描”呢?通常,静态分析器是由庞大的开源社区提供支持的。这就意味着,所有为该工具做出贡献的人,都可以间接地检查了您的代码,以发现任何遗漏的错误。

例子:

如下代码段展示了一个让用户挑选水果的例子。如果用户不做选择,则默认为“Mango”。


  1. let fruits = ['Apple', 'Banana', 'Cherry', 'Mango'] 
  2. function getFruit(index) { 
  3.     index = index || 3 // Everybody likes mangoes 
  4.     return fruits[index] 

只要用户的输入不为0,那么各种测试都能顺利通过。


  1. getFruit()  // Mango 
  2. getFruit(2) // Cherry 
  3. getFruit(0) // Mango (expected Apple!) 

而由于0的存在,用户无法选择Apple。因此针对null和undefined之类的虚假值(falsy value),您应该改用null-coalescing(空值合并)运算符--??(请参见如下代码段)。


  1. let fruits = ['Apple', 'Banana', 'Cherry', 'Mango'] 
  2.  
  3. function getFruit(index) { 
  4.  
  5. index = index ?? 3 // Everybody likes mangoes 
  6.  
  7. return fruits[index] 
  8.  
  9. }

风格统一

每个开发人员都可以采用自己的风格去编写代码。但是,在许多开发人员需要协同工作时,通过风格指南的一致性来约束大家的编程,就显得极为重要了。不过,我们不可能要求开发人员记住指南中的数百条规则,并依靠人工进行逐行对照与检查。因此,我们需要依靠自动化来完成。

每一种语言都有自己的代码校验器(lint),例如:JavaScript有ESLint(https://eslint.org/)、Python有Black(https://black.readthedocs.io/en/stable/)、而Ruby有RuboCop(https://github.com/rubocop-hq/rubocop)。这些校验器可以确保您的代码遵循相关的风格规则,进而让代码更加整洁。例如,RuboCop便可以通过检测和修复错误,以确保函数与变量名具有更好的原子一致性。

例子:

如下JavaScript代码段,旨在从列表中打印水果的名称。同时,该列表在整个程序中应保持不变。


  1. var fruits = ['Apple', 'Banana', 'Cherry', 'Mango'] 
  2. console.log(fruits[0]) 

通过配置,ESLint可以确保尽可能地使用常量,以避免对代码产生副作用。


  1. const fruits = ['Apple', 'Banana', 'Cherry', 'Mango'] 
  2. console.log(fruits[0]) 

如上述代码所示,倘若我们用let和const两个关键字来替换var,则会让代码更加易于调试。

立即发现问题

以测试为驱动的开发实践,往往强调编写各种用于测试的用例。但是,为了尽可能地覆盖所有输入,开发人员需要花费时间和精力去编写测试用例。最终,测试用例不但十分臃肿,而且需要花费数小时,才能完成大型代码库的构建。

而静态代码分析器则不会遇到此类问题。您不需要编写测试用例,即可导入整个预设的代码库。同时,由于无需执行代码,因此静态分析器的运行速度会更快。实际上,许多代码校验器都能够与代码编辑器相集成,并在您输入代码时,实时地突显代码中的问题。

例子:

大多数静态分析器,尤其是linters和formatter,不仅会指出代码问题,而且可以解决问题。同时,诸如Python的Black和JavaScript的ESLint还能够与IDE相集成。在用户保存代码时,它们便可以自动修复当前已编辑的文件。这种实时提高代码质量的方式,已广受开发者的欢迎。

例子:

ESLint带有一个可用来修复常见问题的参数—fix,可用来修复:不必要的分号、尾部空格、以及多余的逗号。在如下代码段中,一个·代表着一个空格。


  1. var fruits = [ 
  2.     'Apple', 
  3.   'Banana', 
  4.   'Cherry',·· 
  5.     'Mango' 
  6. ]; 

而在运行了带有—fix的ESLint后,代码会变成:


  1. const fruits = [ 
  2.     'Apple', 
  3.     'Banana', 
  4.     'Cherry', 
  5.     'Mango', 

修复依赖项

在构建应用程序时,您不可避免地会用到由其他开发人员所构建的框架和工具。当然,其他开发人员也可能会用到更多第三方的框架。如此下去,就算是一个简单的Vue.js应用,也可能在其node_modules/的目录中,被放置了成千上万个软件包。

然而,在这种“叠罗汉”式的依赖链中,您的应用程序会受到最薄弱的依赖性的制约。而作为另一种静态分析器的漏洞扫描程序,正好可以通过强大的漏洞签名数据库的优势,对依赖关系树中的每个依赖项进行检查。而所有被发现的漏洞,都能够被扫描程序通过一条命令,来予以更新和修复。

例子:

GitHub会使用Dependabot来扫描依赖项。而npm会使用npm audit命令来扫描漏洞。Dependabot和npm audit在它们的新版本中,都提供了带有自动化漏洞修复功能的更新包。

自动化重复性事务

相对于繁琐地手动遍历与审查代码,各种linter、formatters、以及拼写检查器(spell checkers)能够大幅简化整个过程。那么它们又是怎么实现的呢?首先,预提交的钩子(pre-commit hook)将确保在检入VCS(版本控制系统)之前,对代码已进行校验和格式化。其次,构建管道或GitHub工作流形式的项目级自动化,会在每次提交时检测代码的质量,并突显代码自身的问题。最后,由于代码的各种小问题已经被处理了,因此审查人员可以去关注代码逻辑等其他问题。可见,软件自动化不但可以减少人工的工作量,还能够提高审查的速度,以及遍历的覆盖率。

项目示例

下面,让我们来逐步创建一个项目示例。首先,我们为项目创建一个新的目录。在该目录中,我们通过npm init向导(如下图所示),逐步完成Node.js包的初始化。


  1. $ mkdir wuphf.com 
  2. $ cd wuphf.com 
  3. $ npm init 

我们使用如下简单的命令来安装ESLint。


  1. $ npm install eslint 

我们运行如下命令,来激活ESLint向导。


  1. $ ./node_modules/.bin/eslint --init 

该向导会通过与您的互动,完成在项目中如何使用ESLint的相关问题。此本例中,请选择Airbnb的规则集。在完成设置后,该目录中将出现一个.eslintrc.js的文件。

下面的代码段展示了一个控制台应用。在此,我们可以自定义规则,并关闭针对它的各种警告。


  1. module.exports = { 
  2.   env: { 
  3.     es2021: true, 
  4.     node: true, 
  5.   }, 
  6.   extends: [ 
  7.     'airbnb-base', 
  8.   ], 
  9.   parserOptions: { 
  10.     ecmaVersion: 12, 
  11.   }, 
  12.   overrides: [ 
  13.         
  14.       files: ['*.js'], 
  15.       rules: { 
  16.         'no-console': 'off', 
  17.       }, 
  18.     }, 
  19.     ], 
  20. }; 

最后,我们将此文件提交到版本控制系统中,以便ESLint持续扫描项目中的所有JS文件。此外,我建议您通过安装Husky(https://typicode.github.io/husky/#/),以实现在每次提交之前,运行一次代码校验的作业,以保证不会有不良的代码被检入VCS中。

使用DeepSource来实现自动化

作为一个静态代码分析器,DeepSource(https://deepsource.io/)可以在代码库中查找问题,并自动修复它们。目前,它可以支持大多数主流编程语言,并能够集成到GitHub、GitLab和Bitbucket中。为了在项目中设置DeepSource,您可以将名为.deepsource.toml的文件放置在存储库的根目录中,以便对项目进行持续扫描。

原文标题:Beginner's Guide to JavaScript Static Code Analysis,作者:Dhruv Bhanushali