错误处理机制
我们编写的程序当然是越稳定,越安全越好。在后面的章节,我们会详细讨论如何避免程序中有可能发生的错误。但是,即便再怎么精细设计,在编写程序过程中也难免有考虑不周之处,遗留下某些潜在问题。在某些特殊条件下,这些潜在问题就可能导致程序出错。所以,我们在设计程序时,除了尽量思虑周全,力争减少各类错误之外,还必须在程序中采取一定的预防措施,这样,即使程序出现错误,也可以降低错 误带来的损害,并帮助开发者快速定位错误。这种预防措施就是错误处理机制。
在 布尔类型条件选择结构 一节中,我们曾经简要地介绍过:在 LabVIEW 中最为常见的布尔型条件结构是用来处理错误数据线的,这是一种使用最为普遍的错误处理机制。它普遍到了,LabVIEW 中大多数的底层子 VI 都带有这种错误处理机制,因此,我们有必要详细介绍一下这方面的内容。
此处所谓的错误是指程序运行时出现的错误。采用错误处理机制的目标有两个:第一是可以尽早的报告错误,最好还能够报告出错误发生的位置和发生的条件,以帮助程序员调试和修正错误;第二是降低错误带来的次级伤害,不要因为程序的错误而导致用户得到错误的结果。作为反例,我们介绍一种非常糟糕的错误处理机制:比如,我们编写了一个硬件测试程序,在程序运行的过程中,数据采集设备的初始化出现了异常,但是程序选择忽略这个错误,继续运行。结果是用户在程序界面上看到每个产品的检测结果都是合格的,但实际上,这只是因为数据采集硬件无法读取到劣质产品发出的异常信号而已,可能已经有次品混入最终发布的产品了。
LabVIEW 的默认错误处理
运行 LabVIEW 程序,如果某个函数或 VI 出现了错误,并且,也没有错误数据线与出错的函数或 VI 相连接,那么 LabVIEW 就会启动默认的错误处理机制。这时,程序会挂起,LabVIEW 在高亮显示出错的函数或子 VI 后弹出一个错误信息对话框对话框。比如下图中的程序,“打开文件”函数的错误输出没有与任何其它接线端相连,当试图用它打开一个不存在的文件时,LabVIEW 就会报错:
弹出的对话框上有两个按钮。如果用户点击“继续”按钮,LabVIEW 就会忽略这个错误,继续运行下面的程序;如果用户点击“停止”,LabVIEW 会停止程序运行。
LabVIEW 这个自动错误处理机制有时候也挺招人烦的。比如,有些错误本来是可以被忽略,不必理睬的。但是如果有对话框弹出,用户就不得不手动去关闭这个对话框。尤其是当程序发布给用户后,我们可能不希望最终用户运行程序运行过程中,时不时弹出个错误信息对话框来。其实这个自动错误处理功能是可以在 VI 属性对话框中被禁止的:
如果想在所有 VI 中禁用 LabVIEW 的默认错误处理机制,也可以在 LabVIEW 的选项对话框进行设置。打开 LabVIEW 选项对话框的“程序框图”页,找到“错误处理”一栏,可以禁用 LabVIEW 的默认错误处理机制。
错误簇
LabVIEW 中很多函数 和 VI 都有一对错误输入/输出参数,参数的类型是“错误簇”。错误簇由一个布尔类型数据(值为真时表示有错误)、一个数值型数据(错误代码)和一个字符串数据(错误信息)组成的:
当一个带有错误输入/输出的函数或 VI 在执行中出现错误时,错误输出参数就会返回一个错误簇数据。比如,下图中的程序:
程序调用“打开文件”函数,试图打开一个不存在的文件,“打开文件”函数的错误簇于是返回了“出错”。(关于文件操作,本书会在文件读写一节详细介绍。)在 VI 的“帮助”菜单中,或在错误簇控件的右键菜单中选择“解释错误”,会弹出一个错误解释对话框,列出了出错的位置,以及出错的原因。比如,上面的程序,“解释错误”对话框告诉我们“文件没有找到。文件可能是在其它的路径下……”:
程序中的错误,有些是可以预期的,有些是不可预期的。这两种错误的处理方式是有所不同的。
不可预期的错误
不可预期的错误是指,编程者之前没有预料到的,某个函数或 VI 在某些特殊情况下返回的错误。编程者更无法预料这些错误的代码。不可预期的错误也可以被称为“异常”,这是程序中错误处理机制主要需要预防的错误。如果发生了不可预期的错误,说明程序进入了非预期的状态,程序再运行下去,意义也不大了;更重要的是,程序继续运行下去很可能会导致更严重的后果,比如损坏一些重要数据,浪费资源,或者让使用者误以为程序的返回的结果是正常的等。
对待这类问题的简单处理方法就是,一旦有错误出现,程序的后续代码全部忽略,让程序尽快停止运行,并提示用户发生了异常错误。因此,可以按照在布尔类型条件选择结构一节中介绍过的方法,在需要预防出错的地方加一个条件结构,函数的错误输出连接在条件结构的分支选择器上,程序后续的代码全部放在条件结构的“无错误”分支中。这样,一旦函数出现错误,后续程序都不执行。比如下图中的程序,一旦打开文件出错,后面就无需再读取文件了:
这样的错误处理机制必须在每个函数后面都加一个条件结构,显然不适用于大型程序。在实际程序运行时,并非是一但错误出现,就必须立即退出程序的。有些简单的程序代码,任何情况下都不会给程序带来严重后果,也不会显著延误程序的退出时间。因此,即便程序出错,也可以继续执行完这些简单代码后再退出。在实际编程中,通常并不需要为每个函数的错误返回值都加一个错误处理条件结构,而只需要把判断对错的代码都放在较为底层的子 VI 中就可以了。由于上层程序没有判断程序出错的条件结构,所以,每个子 VI 在程序一开始,就首先判断一下,“错误输入”参数是否为“有错误”。如果是,说明程序已经发生异常,程序应该跳过子 VI 的主体代码,并把这个错误值继续传递下去。后续的子 VI 也同样会跳过所有的工作代码,直到程序退出为止。子 VI 的结构如下两图所示:
采用这种错误处理机制的子 VI 是一种极其常见的形式,LabVIEW 自带有此类 VI 的模板。需要创建这一类子 VI 时,可以在菜单中选择“文件 -> 新建”,然后在新建对话框中选取相应的“带错误处理的子 VI”模板即可:
说实话,这个模板中的内容有点太简单了,使用模板比自己创建一个条件结构省不了多少事。笔者经常就自己手动创建这样的 VI。
如果某个 VI 中所有的函数和子 VI 都已带有错误输入输出参数,那么通常,这个 VI 本身就可以不使用条件结构判断错误输入参数了,而是把这个错误输入传递到更底层的函数和子 VI 中去处理就好。比如下图中的 VI:
因为这个 VI 所使用的函数都已具备了错误处理机制,所以它本身不必再写一套错误处理条件结构。如果程序在“打开文件”时出错,“读取文本文件”并不会去读文件的,而是会跳过。LabVIEW 自带的函数、VI,凡是带有错误输出端的,都已带有了错误处理机制,可以放心的把错误数据交由它们处理。
程序中有些代码,是在无论是否出错的情况下都必须执行的。比如,程序打开了某些资源(文件、引用等),在程序退出前,不论是否有异常出现,都一定要把这些资源关闭。被打开的资源若不关闭,就可能会一直驻留在内存中,造成内存泄漏。在上图程序中,首先打开了一个文件,然后读文件,读文件的过程中也许会有错误产生,比如读取的文件长度超出设定值等。但是,即便有这类错误产生,关闭函数也应当被执行。不论过程中是否有错,打开的文件都应当在程序结束前被关闭。
不过用户并不需要在这里对“关闭文件”做特殊的处理。“关闭文件”函数本身已经做了相应的处理了,无论它的错误输入参数得到什么值,它都会保证关闭输入的文件(“引用句柄”参数)。但是,在自己编写错误处理机制时,就必须考虑到哪些代码在出错时依旧需要运行。
可预期的错误
可预期的错误,是那些编程者知道在某种情况下函数或 VI 会返回的错误。以下图中的程序为例:
运行这个程序,在执行到函数“打开/创建/替换文件”函数时,程序会弹出一个对话框,要求用户选择需要打开的文件。此时,用户也可能不选择任何文件而是直接按下“Cancel”(“取消”)按钮。用户的这个操作将导致“打开/创建/替换文件”VI 返回一个代码为 43 的错误。
笔者个人觉得,打开文件函数这个行为不太合理,我们可以在自己编写的程序中纠正这一不合理行为。既然按下“Cancel”按钮是允许用户使用的操作,编程人员就应当意识到这个代码为 43 的错误很可能会发生,但应该避免这个错误导致程序的非正常退出。所以,编程时需要对这个错误进行特殊处理。
上图的分支是针对用户点击“Cancel”按钮时可以采用的一种处理方式:不做读和关闭文件操作,但是判断一下,错误代码是否是 43,如果是,则忽略这个错误,不返回错误代码,以免影响后续程序(假设这是一个子 VI)。这种处理方式的“无错误”分支与中的代码与之前提到的普通错误处理方式相同:
LabVIEW 中已经自带了一些处理错误数据的函数和子 VI,比如“Clear Errors.vi”可用于清除某个或全部的错误。所以上面的处理程序可以简化为下图中的程序:
LabVIEW 中与错误处理相关的函数和 VI 在函数选板“编程 -> 对话框和用户界面”上。
自定义错误
前面介绍了如何处理从 LabVIEW 已有的函数或 VI 中得到错误信息。我们还可以根据自己的需求,给程序定义一些新的错误并在特定的情况下返回。
让自己的 VI 传出一个错误,非常简单,只需自己定义一个错误输出的簇就可以了。但更加可靠的做法是借助“编程 -> 对话框与用户界面 -> 错误代码至错误转换”VI,提供给这个 VI 错误代码和错误信息,它就可以输出一个对应的错误簇:
错误代码可以选择 LabVIEW 中已定义的错误。比如,LabVIEW 的错误代码 50 表示“信息超出范围”,它与输入值越界性质类似。也可以使用 5000 ~ 9999 和 -8999 ~ -8000 之间的某个数值,这两段数值是专门留给用户自定义错误代码的。
如果想要查看一个 LabVIEW 中某个错误代码是什么含义可以搜索 LabVIEW 帮助文档,或选择菜单项“帮助 -> 解释错误”,在解释错误对话框中输入一个错误代码,它就会给出相应的错误信息。
在程序中创建用户自定义错误代码和信息,是比较麻烦的,只适用于管理少量的用户自定义错误。如果一个项目需要定义大量的错误代码,可以考虑把所有的用户自定义错误代码和信息都集中保存在一个文本文件中。LabVIEW 自带了一个错误代码编辑工具专用于编辑保存在文件中的错误代码和信息。在 LabVIEW 的菜单中选择“工具 -> 高级 -> 编辑错误代码”可以打开这个工具:
在工具中添加所有的错误代码和错误信息,然后把信息保存在默认的路径下,[LabVIEW]\user.lib\errors\ 下面:
之后,重启 LabVIEW,所定义的错误代码和信息就会被加载进 LabVIEW 系统,在程序中就可以像使用系统错误代码一样使用这些用户自定义错误代码了。