捕捉常见的问题
寻找错误并修复它们不仅仅是我的一种激情,更是一种强迫症。几年前,作为一名QA开发人员,我为Wolfram语言创建了MUnit单元测试框架,这是一个用于编写和运行语言单元测试的框架。从那时起,我创造了更多的工具来帮助开发人员编写更好的Wolfram语言代码,同时在这个过程中无缝地检查出错误。
编写好的测试需要大量的知识和大量的时间。由于我们需要能够尽快测试和解决问题,以便按期发布新功能,我们转向静态分析,以便能够做到这一点。
什么是静态分析?
静态分析是在运行源代码之前对其进行检查的过程,以试图预测其行为并发现问题。作为一种测试方法,它是非常有用的。在代码运行时发现问题并不总是可行的。运行代码的成本也很高--如果代码失败了,那就更是如此。
考虑到构成Wolfram语言的大量代码(有120万行的内核启动Wolfram语言代码,横跨1900个文件,还有85万行的paclet Wolfram语言代码,横跨3700个文件),必须要有一个策略来测试所有这些代码的错误。Wolfram对Wolfram语言的每一个角落都有专门的测试--其中有些是我写的!
CodeInspector paclet 是那些重要的静态分析工具之一,它使开发人员能够完成更好的工作。CodeInspector包含在最近发布的Mathematica 12.2中,它可以扫描Wolfram语言代码并报告问题,而不需要用户手动运行paclet。CodeInspector 与 CodeParser 和 CodeFormatter 一起构成 CodeTools 套件,供内部和外部用户使用,以提高其 Wolfram Language 代码的质量。
一般来说,静态分析不能发现程序中所有可能的bug(这是通过Rice定理对停止问题的不可控性所产生的结果)。但是,静态分析仍然可以提供大量的重要信息
例如,很容易看出这里的测试中不需要&&True。
这可能是遗留的调试代码,或者仅仅是逻辑上的一个错误。静态分析工具可能会警告说,&& True不需要,可以去掉或改成别的东西。虽然静态分析工具不能辨别作者的意图,但它们可以找到值得调查的 "可能的问题 "的类别。
创建一个静态分析工具来测试Wolfram语言中的错误,有一系列非常具体的挑战。作为一种编码语言,Wolfram语言具有难以置信的动态和灵活性。虽然这通常被认为是对开发人员的一种奖励,但它确实使抽象建模非常困难。函数可以在运行时被重新定义,而且在Wolfram语言中精确定义一个值的概念也很复杂。
鉴于语言本身的局限性,CodeInspector基于语法树的模式匹配进行轻量级静态分析。这类似于其他语言的 "提示工具"。事实上,CodeInspector paclet的原名是Lint! 但很快就发现,它所做的工作不仅仅是检查,所以它被改名为CodeInspector)。)
CodeInspector目前有大约两百条内置规则,可以应用于被检查的代码。这些规则从常见的语法问题(如缺少逗号)到更隐蔽的问题(如在符号求解器中使用Q函数)。许多规则包括修复代码的建议。
使用 CodeInspector
CodeInspector 包含在 Mathematica 12.2 中。如果您使用的是旧版本的Mathematica,您可以通过评估以下内容获得CodeInspector:
为了以编程方式获得以下代码片断中所有问题的列表:
...您可以运行这个测试:
要获得测试中发现的所有问题的可视化摘要,请使用CodeInspectSummarize(包含在CodeInspector paclet中):
您甚至可以在命令行上使用CodeInspectSummarize:
有多种方法可以控制CodeInspectSummarize的输出。为了做到这一点,我们需要对问题进行分类,这本身就是一个有趣的问题!这是因为我们需要在以可查询的方式公开问题的许多属性与建立一个易于人类使用的系统之间取得适当的平衡。这是因为我们需要在以可查询的方式暴露问题的许多属性与拥有一个易于人类消费和理解的系统之间取得适当的平衡。
我使用两个维度,至少现在是这样:严重程度和信心等级。如果输出显示有问题,严重性表示每个问题有多严重。这个问题会不会影响到用户?它是否会意外地发射核弹头?知识就是力量,特别是当您需要了解手头问题的影响时。
ConfidenceLevel表示该问题实际上是一个问题而不是一个假阳性的信心水平。ConfidenceLevel是一个介于0.0和1.0之间的真实值。ConfidenceLevel→0.0意味着对所报告的问题完全没有信心,而ConfidenceLevel→1.0意味着眼前肯定有问题,比如函数中不匹配的括号。ConfidenceLevel为0.5意味着大约有一半的时间出现这种问题,是一个假阳性。在括号不匹配的情况下,ConfidenceLevel是1.0。CodeInspector中更多的实验性规则会有更低的ConfidenceLevel,当我添加启发式方法来消除假阳性时,我会增加问题的ConfidenceLevel。为我的目的重新使用 ConfidenceLevel 符号可能是对符号的滥用,但它很方便。
因为Wolfram语言是如此的动态,很难判断一个所谓的bug实际上是一个错误。即使在前面的示例中,If语句也可能是故意编写的。仅语法错误,例如:
......可以百分百确定地被标记出来。请注意,即使是 "明显的 "问题,如:
...不一定有 ConfidenceLevel → 1.0。因此,CodeInspector报告的每个问题都有一个相关的ConfidenceLevel,表明该问题实际上是一个问题的信心。
默认情况下,CodeInspectSummarize 会报告 95% 或更高置信度的问题。
还有四种与问题相关的不同严重程度:
这些严重程度应该与ConfidenceLevel同时解释。只有当问题不是假阳性时,严重程度才有意义。
CodeInspector 如何运行
Wolfram语言有一个强大的内置模式匹配器,它可以用来对表达式进行静态分析。
我设计了 CodeInspector 的规则引擎,以包括对被检查代码的相对位置的了解,因此我们可以在语法树上移动到父节点并提出其他问题。这在编写规则以确保某些语法出现在其他容器语法的词法中时很有用。
例如:
这说明了一个常见的错误:忘记了&。
从#的位置开始,我们沿着树寻找一个匹配的&:
没有发现&,所以报告了一个问题。注意这个规则的置信度较低,我需要指定ConfidenceLevel → 0.8才能看到它。
您可以根据您关心的语法从不同的规则中选择。例如,如果您想用一条规则来查找实数加到整数上的情况,那么您就不关心1.2+3与Plus[1.2, 3]的具体语法。
语法有三个不同的层次:
捕捉常见的问题
示例1:
在这个例子中,我忘了在行末加一个分号,所以整个表达式被当作a=1*a+b处理。这是不正确的,会导致代码运行时的无限递归:
示例2:
在这个例子中,我忘了给PatternTest插入一个问号。
CodeInspector会捕捉到Q函数被当作头的情况,并建议插入一个问号:
捕捉更多不明显的问题
示例3:
在这个例子中,我试图用 ImageDimensions 的输出来指定 ImageSize,但这两个函数的单位并不相同。ImageSize 选项期望的是点,但 ImageDimensions 则返回像素:
真实世界的问题
CodeInspector 会定期在 Wolfram Research 的开发人员编写的内部代码上运行。以下是最近遇到的两个问题,被 CodeInspector 发现并修复。这些问题很微妙,通过编写测试很难发现。
问题1:
需要用括号来包住整个右边的内容。原来的代码相当于:
这当然不是作者的本意。
问题2:
inc后面的额外下划线_意味着{__}被当作inc的可选值。但我们的目的是让 inc 匹配模式 {__}。CodeInspector能够发现这些问题,并在发布代码前将其修复。
CodeInspector 工作流程
CodeInspectSummarize 报告指定文件的问题的方式与报告指定字符串的问题的方式完全相同。
由于Wolfram语言代码是解释的,因此没有编译步骤,可能不清楚何时是扫描问题的最佳时机。在实践中,我发现在构建小程序的时候是扫描的好时机。
我已经为CMake编写了脚本,在构建paclet之前扫描每个Wolfram Language文件。下面是当我的代码中有错别字时,我试图构建 CodeInspector paclet 本身时的情况:
因此,我可以看到我的代码中的错别字,并立即在源代码中修复它。否则,我就会用糟糕的代码构建paclet,并在试图运行代码时遇到奇怪的错误。这凸显了尽快发现并修复问题的众多原因之一--通过测试CodeInspector本身来证明CodeInspector的意义。
CodeInspector中不断有新的规则加入,您可以在GitHub上的CodeInspector资源库中查看。目前的许多规则都是受到用户建议的启发,所以如果您有任何想法或建议,请在评论区告诉我。
京ICP备09015132号-996 | 网络文化经营许可证京网文[2017]4225-497号 | 违法和不良信息举报电话:4006561155
© Copyright 2000-2023 北京哲想软件有限公司版权所有 | 地址:北京市海淀区西三环北路50号豪柏大厦C2座11层1105室