开发区域

MATLAB高级软件开发

我们都有问题

我们谁没有问题呢,阿米瑞特?让我们花点时间来承认这个事实,我认为我们总是可以更诚实地理解我们所有的独特问题和我们表现出的各种特质。虽然我们都可以相互理解和宽容,但我们面临的一些问题可能很重要,需要迅速解决,而有些问题我们也许可以选择在适当的时候解决。

...我们的代码也是如此。在任何严肃的代码库中,问题、bug和其他次优结构都会突然出现。你们中的许多人已经意识到MATLAB中的代码分析器的功能,它可以在MATLAB编辑器中直接提醒你的问题。然而,有时问题仍然可以通过一些微妙的方式潜入代码库,甚至包括当一个乐于助人的编辑试图告诉我我的代码中有问题时,没有采取行动。

然而,在R2022b中,用于识别和解决这些代码问题的编程接口有了很大的改进。事实上,这个新接口本身就是一个叫做codeIssues

这个新的代码分析器的API是一个词-强大。让我们来探索一下如何使用它来提高项目的质量。从另一个角度来看我们的标准质量-弹簧-阻尼器迷你代码库。最近我们在描述新的MATLAB构建工具.这个代码库非常简单。它包括一个模拟器,一个返回一些设计常数(弹簧常数,阻尼系数等)的函数,以及一些测试。

要开始这样的代码库,只需在你想分析的文件夹上调用codeIssues:

您可以看到,只需简单地调用codeIssues,就可以快速获得静态分析器所期望的所有细节的概述。您可以很容易地深入分析的文件、配置和发现的问题的非常方便的表格,以及通过编辑器中的suppression pragmas抑制的任何问题。如果你在MATLAB中,你甚至可以点击每个问题,在MATLAB编辑器中直接找到它,如果需要,它可以被修复或抑制。

现在,有了这个漂亮的API,再加上构建工具的引导,我们可以以一种更加健壮、自动化的方式锁定代码。我们可以大致从构建工具文章结尾的地方开始,使用以下带有mex和测试任务的构建文件:

函数计划=构建文件计划= buildplan(localfunctions);计划(“测试”).依赖项=“墨西哥人”;计划。DefaultTasks =“测试”结束函数mexTask (~)编译mex文件墨西哥人墨西哥人/ convec.c-outdir工具箱/结束函数testTask (~)%运行单元测试结果=运行测试;disp(结果);assertSuccess(结果);结束

让我们继续,通过创建一个名为codeIssuesTask

函数codeIssuesTask (~)获取工具箱下的所有问题allIssues = codeIssues(“工具箱”);断言没有错误渗透到代码库中。errorIdx = allIssues.Issues.Severity ==“错误”;errors = allIssues.Issues(errorIdx,:);otherIssues = allIssues.Issues(~errorIdx,:);如果~ isempty(错误)disp (“在代码中发现严重错误:”);disp(错误);其他的disp (“没有发现严重错误。”);结束显示所有其他问题如果~ isempty (otherIssues) disp (其他问题:“) disp (otherIssues);其他的disp (“也没有发现其他问题。(哇,真有你的!)”结束断言(isempty(错误));结束

这很简单,我们只是想找到“工具箱”文件夹下的所有问题,如果其中任何问题的严重程度为“错误”,则抛出断言错误。这是你可以应用到代码库中建立质量的最快方法。这可以静态地发现语法和其他错误,甚至不需要编写或运行单个测试。我们真的没有理由不把这个任务应用到每个项目中。它几乎不需要花费任何成本,并且可以非常有效地找到错误。在这一点上,让我们在构建文件中添加它作为默认任务:

计划。DefaultTasks = [“codeIssues”“测试”];

...有了这些,我们现在就把这个检查建立在我们的标准开发过程中。为了显示这一点,我们首先将一个带有错误的文件放入代码库中,然后我们可以调用构建工具:

函数syntaxError disp (“忘记了右括号!”
拷贝文件.changes / syntaxError.m工具箱/ syntaxError.m试一试buildtool前女友disp (ex.getReport (“基本”));结束
**启动代码问题失败!发现关键错误代码:位置严重描述CheckID LineStart LineEnd ColumnStart ColumnEnd FullFilename  _______________ ________ ______________________________________________________________________________ _______ _________ _______ ___________ _________ _______________________________________________________________________________________________ " syntaxError。m" error "A '('可能缺少一个结尾')',导致行尾语法无效。"EOLPAR 3 3 5 5 "/Users/acampbel/Library/CloudStorage/ onedriv - mathworks /repos/msd_blog2/toolbox/syntaxError. "m”其他问题:位置严重描述CheckID LineStart LineEnd ColumnStart ColumnEnd FullFilename  __________________________ ________ _____________________________________________ _______ _________ _______ ___________ _________ __________________________________________________________________________________________________________ " springMassDamperDesign。m“警告”赋给变量的值可能未使用。NASGU 4 4 3 3 "/Users/acampbel/Library/CloudStorage/OneDrive-MathWorks/repos/msd_blog2/toolbox/springMassDamperDesign。米”“springMassDamperDesign。m“警告”赋给变量的值可能未使用。NASGU 6 6 3 3 "/Users/acampbel/Library/CloudStorage/ onedriv - mathworks /repos/msd_blog2/toolbox/springMassDamperDesign。米 " ## ----------------------------------------------------------------------------- ## 错误使用断言# #断言失败。## ## buildfile>codeIssuesTask(第67行)错误## assert(isempty(errors));## ----------------------------------------------------------------------------- ** 失败的codeIssues错误使用buildtool构建失败了。

当构建工具看到我们有一些重要的语法错误需要处理时,它已经完成了停止开发过程的工作。

让我们删除这个错误,这样我们就可以看到“codeIssues”任务成功完成。当我们这样做时,我们还成功地执行了其他“mex”和“test”任务,以完成整个工作流。

删除工具箱/ syntaxError.mbuildtool
**启动代码问题没有发现严重错误。其他问题:位置严重描述CheckID LineStart LineEnd ColumnStart ColumnEnd FullFilename  __________________________ ________ _____________________________________________ _______ _________ _______ ___________ _________ __________________________________________________________________________________________________________ " springMassDamperDesign。m“警告”赋给变量的值可能未使用。NASGU 4 4 3 3 "/Users/acampbel/Library/CloudStorage/OneDrive-MathWorks/repos/msd_blog2/toolbox/springMassDamperDesign。米”“springMassDamperDesign。m“警告”赋给变量的值可能未使用。NASGU 6 6 3 3 "/Users/acampbel/Library/CloudStorage/ onedriv - mathworks /repos/msd_blog2/toolbox/springMassDamperDesign。m" **完成的代码问题**开始mex构建与“Xcode与Clang”。MEX完成成功。**开始测试设置ProjectFixture完成设置ProjectFixture:项目“msd”已经加载。不需要设置。__________运行convecTest。完成convecTest __________运行设计测试… Done designTest __________ Tearing down ProjectFixture Done tearing down ProjectFixture: Teardown is not required. __________ 1×4 TestResult array with properties: Name Passed Failed Incomplete Duration Details Totals: 4 Passed, 0 Failed, 0 Incomplete. 0.22349 seconds testing time. ** Finished test

最后一点

现在,我们已经为您设置了静态分析提供的保护。然而,虽然我们在静态分析错误上失败了,但我仍然对继续向代码中添加结构而导致静态分析警告感到不舒服,这通常指向程序中的实际问题。如果我们愿意,我们也可以因为警告而让构建失败,但我不想从这个想法开始。

很明显,我们希望这种保护带有完全真实的错误,而这些错误几乎总是bug。但是,当代码库已经有了警告清单时,我们就遇到了一个问题。如果能仔细检查库存并修复所有这些警告,那就太棒了。事实上,新的代码分析工具在许多情况下使这变得非常容易!然而,你现在可能还没有准备好。您的代码库可能很大,并且您可能希望或需要在此活动中投入更多时间。所以我们的第一次尝试只是因为“错误”严重程度的问题而导致构建失败。

然而,如果你了解我,你就会知道我喜欢偷偷地做最后一件事。如果我们接受代码库中当前所有的警告,但是想要锁定代码库,这样我们就不会引入新的警告了,那该怎么办呢?对我来说,这听起来是个好主意。然后,我们可以通过防止新警告的流入来减少警告,并通过与代码库交互来逐渐删除现有的警告。我们该怎么做呢?我们可以利用codeIssues编程API的强大功能!

我们可以通过捕获现有的警告并将其保存到已知问题的基线来实现这一点。作为MATLAB表格,这些问题以很好的表示形式保存在*.csv或*.xlsx文件中。以这种格式保存它们可以很容易地调整它们,在MATLAB之外打开它们,甚至删除已经修复的问题。

要做到这一点,我们只需要对问题表做一些调整。我们需要重写位置变量的相对路径,则删除FullFilename变量,并做出一个快速的数据类型调整,以允许良好的CSV'ing。相对文件名调整很重要,因为我们希望能够在不同的机器上比较这些结果,而完整路径可能在不同的环境中有所不同。这样的环境包括单个工程师的桌面以及CI系统中不同的构建代理。

该函数如下所示:

函数theTable = preprocessIssues(theTable)通过一些小的调整来创建一个有利于基线的问题表用相对路径覆盖位置字段,并删除绝对路径basePath = string(pwd) + filesep;茶。位置= erase(theTable.FullFilename, basePath); theTable.Properties.VariableNames{“位置”} =“RelativeFilename”;茶。FullFilename = [];将严重性转换为类别,这将很好地序列化为字符串茶。Severity = categorical(theTable.Severity);结束

...现在使用这个函数,我们可以在构建文件中创建一个新任务来生成一个新的基线:

函数captureWarningsBaselineTask (~)捕获当前的codeIssues警告并创建一个基线csv文件allIssues = codeIssues(“工具箱”);warningIdx = allIssues.Issues.Severity ==“警告”;warnings = allIssues.Issues(warningIdx,:);warnings = preprocessIssues(警告);如果~ isempty(警告)disp ("保存新的""knownIssues.csv""基线文件"+高度(警告)+“代码警告”) writetable(警告,“knownIssues.csv”);其他的disp (“没有为其创建基线的警告”结束结束

让我们开始吧!

buildtoolcaptureWarningsBaseline
**开始captureWarningsBaseline保存一个新的“knownIssues.csv”基线文件2代码警告**结束captureWarningsBaseline

很好,我现在看到了csv文件。我们可以看一下:

类型knownIssues.csv
RelativeFilename、严重性、描述、CheckID LineStart, LineEnd, ColumnStart, ColumnEnd工具箱/ springMassDamperDesign。m,警告,赋给变量的值可能未使用。NASGU 4、4、3、3工具箱/ springMassDamperDesign。m,警告,赋值给变量的值可能未使用,NASGU,6,6,3,3

美丽。在这种情况下,我们只有两个小警告,我还不想深入研究。然而,现在我们可以调整“codeIssues”任务,以防止我引入任何新的内容:

函数codeIssuesTask (~)获取工具箱下的所有问题allIssues = codeIssues(“工具箱”);断言没有错误渗透到代码库中。errorIdx = allIssues.Issues.Severity ==“错误”;errors = allIssues.Issues(errorIdx,:);otherIssues = allIssues.Issues(~errorIdx,:);如果~ isempty(错误)disp (“失败!在代码中发现严重错误:);disp(错误);其他的disp (“没有发现严重错误。”);结束加载已知警告基线newWarnings = [];如果isfile (“knownIssues.csv”) otherIssues = preprocessIssues(otherIssues);加载基线文件opts = detectImportOptions(“knownIssues.csv”);类型= varfun(@class, otherIssues,“OutputFormat”“细胞”);选择。VariableTypes =类型;knownIssues =可读(“knownIssues.csv”、选择);通过减去基线中的已知问题,找到新的警告otherIssues = setdiff(otherIssues, knownIssues);newWarningIdx = otherIssues。严重程度= =“警告”;newWarnings = otherIssues(newWarningIdx,:);如果~ isempty (newWarnings) disp (“失败!在代码中发现了新的警告:);disp (newWarnings);其他的disp (“没有发现新的警告。”);结束otherIssues = [knownIssues;otherIssues (~ newWarningIdx:)];结束显示所有其他问题如果~ isempty (otherIssues) disp (其他问题:“) disp (otherIssues);其他的disp (“也没有发现其他问题。(哇,真有你的!)”结束断言(isempty(错误));断言(isempty (newWarnings));结束

现在加载问题,并执行setdiff来忽略基线CSV文件中已经知道和捕获的问题。这样,至少从现在开始,我不会向代码库引入任何新的警告。从现在开始,一切只会变得更好。此外,如果我更改了一些具有现有警告的文件,我的构建工具很可能会对我大喊大叫,因为现有警告略有不同。例如,由于文件中所做的更改,它可能位于不同的行上。

如果发生这种情况,那太好了!让我在打开并修改文件时清除或抑制警告。这是一个功能,而不是一个bug。最坏的情况是,如果我真的不能立即查看它,我总是可以捕获一个新的基线,但是我喜欢这种方法,它可以帮助我在整个过程中清理代码。

这看起来像什么?让我们在代码库中添加一个带有新警告的文件:

函数codeWarning anUnusedVariable =“未使用”

...并调用构建:

拷贝文件.changes / codeWarning.m工具箱/ codeWarning.m试一试buildtool前女友disp (ex.getReport (“基本”));结束删除工具箱/ codeWarning.m
**启动代码问题没有发现严重错误。失败了!在代码中找到了新的警告:RelativeFilename严重性描述CheckID LineStart LineEnd ColumnStart ColumnEnd  _______________________ ________ _____________________________________________ _______ _________ _______ ___________ _________ " 工具箱/ codeWarning。m“警告”赋给变量的值可能未使用。NASGU 3 3 1其他16个问题:RelativeFilename严重性描述CheckID LineStart LineEnd ColumnStart ColumnEnd  __________________________________ ________ _____________________________________________ _______ _________ _______ ___________ _________ " 工具箱/ springMassDamperDesign。m“警告”赋给变量的值可能未使用。NASGU 4 4 3 3 "工具箱/springMassDamperDesign。m“警告”赋给变量的值可能未使用。NASGU 6 6 3 3  ## ----------------------------------------------------------------------------- ## 错误使用断言# #断言失败。## ## buildfile>codeIssuesTask(第68行)错误## assert(isempty(newWarnings));## ----------------------------------------------------------------------------- ** 失败的codeIssues错误使用buildtool构建失败了。

爱它!我现在不受自己的伤害了。我可以在我的标准工具箱开发过程中利用这一点,以帮助确保随着时间的推移,我的代码只会变得更好,而不是更糟。你也可以想象一下,当已知问题的警告消失时,调整它以失败或以其他方式通知,这样我们就有一定的压力来帮助确保随着时间的推移,封锁变得越来越严格。作为参考,下面是今天讨论的工作流使用的最终构建文件:

函数计划=构建文件计划= buildplan(localfunctions);计划(“测试”).依赖项=“墨西哥人”;计划。DefaultTasks = [“codeIssues”“测试”];结束函数mexTask (~)编译mex文件墨西哥人墨西哥人/ convec.c-outdir工具箱/结束函数testTask (~)%运行单元测试结果=运行测试;disp(结果);assertSuccess(结果);结束函数codeIssuesTask (~)获取工具箱下的所有问题allIssues = codeIssues(“工具箱”);断言没有错误渗透到代码库中。errorIdx = allIssues.Issues.Severity ==“错误”;errors = allIssues.Issues(errorIdx,:);otherIssues = allIssues.Issues(~errorIdx,:);如果~ isempty(错误)disp (“失败!在代码中发现严重错误:);disp(错误);其他的disp (“没有发现严重错误。”);结束加载已知警告基线newWarnings = [];如果isfile (“knownIssues.csv”) otherIssues = preprocessIssues(otherIssues);opts = detectImportOptions(“knownIssues.csv”);类型= varfun(@class, otherIssues,“OutputFormat”“细胞”);选择。VariableTypes =类型;knownIssues =可读(“knownIssues.csv”、选择);otherIssues = setdiff(otherIssues, knownIssues);newWarningIdx = otherIssues。严重程度= =“警告”;newWarnings = otherIssues(newWarningIdx,:);如果~ isempty (newWarnings) disp (“失败!在代码中发现了新的警告:);disp (newWarnings);其他的disp (“没有发现新的警告。”);结束otherIssues = [knownIssues;otherIssues (~ newWarningIdx:)];结束显示所有其他问题如果~ isempty (otherIssues) disp (其他问题:“) disp (otherIssues);其他的disp (“也没有发现其他问题。(哇,真有你的!)”结束断言(isempty(错误));断言(isempty (newWarnings));结束函数captureWarningsBaselineTask (~)捕获当前的codeIssues警告并创建一个基线csv文件allIssues = codeIssues(“工具箱”);warningIdx = allIssues.Issues.Severity ==“警告”;warnings = allIssues.Issues(warningIdx,:);warnings = preprocessIssues(警告);如果~ isempty(警告)disp ("保存新的""knownIssues.csv""基线文件"+高度(警告)+“代码警告”) writetable(警告,“knownIssues.csv”);其他的disp (“没有为其创建基线的警告”结束结束函数theTable = preprocessIssues(theTable)通过一些小的调整来创建一个有利于基线的问题表用相对路径覆盖位置字段,并删除绝对路径basePath = string(pwd) + filesep;茶。位置= erase(theTable.FullFilename, basePath); theTable.Properties.VariableNames{“位置”} =“RelativeFilename”;茶。FullFilename = [];将严重性转换为类别,这将很好地序列化为字符串茶。Severity = categorical(theTable.Severity);结束

这就是MATLAB代码分析的干净API,以及使用构建工具将其包含在开发过程中的标准方法。我几乎能感觉到质量越来越高了!

朋友们,在接下来的一段时间里,有很多东西可以写在博客上。这里还有更多关于我们如何利用新的和改进的工具来为您的MATLAB项目开发干净的构建和测试管道的讨论。我也是所以我们将很快分享一些非常棒的进展。请注意,我们将在这个博客上讨论高质量的测试和自动化开发。在您开发专业MATLAB项目时,请加入您的见解、工具和工作流程。




由MATLAB®R2022b发布

|

댓글

댓글을남기려면링크를클릭하여MathWorks계정에로그하거나계정을새로만드십시오。