代码编写规范和技巧
在编写程序时,除了需要它满足运行正确这个最基本的要求外,还会要求它具有良好的可读性和可维护性。具有良好可读性和可维护性的程序代码都有一些共同之处。实际上,本书的各个章节中,都针对具体问题,各有侧重地介绍了如何编写美观和健壮的程序代码。下面再具体介绍一些编写程序代码时比较通用的规范和技巧。
简洁的程序框图
一个程序,要想让别人容易看明白,首先就应该简洁。如果一个程序框图上只有四五个子 VI 或函数、一两个结构,那么,基本上一眼就可以看明白程序的内容了。如果一个程序框图上有四五十个子 VI、函数,各种循环和条件结构层层嵌套,数据线纵横交错,恐怕看不了几眼就会头晕眼花,更别说轻易弄懂它的意思了。
理想状态下,一个 VI 的程序框图上就应该只包括四五个子 VI 或函数、一两个结构。并且,这个 VI 的程序框图应该符合常用的程序结构或模式。看一眼就可知道,这个 VI 的程序框图采用的是状态机结构或循环事件结构等。一般程序阅读者对这些常用结构模式是有一定了解的。在阅读程序时,若能够套用已有的知识,就可以大大加快理解速度。
一个功能很复杂的程序,不可能只用四五个函数就完成。那么,应该考虑尽可能把大的功能拆分成多个子 V:主 VI 由四五个子 VI 组成;而每个子 VI 也只包含四五个函数或更底层的子 VI;依次层层递推。
LabVIEW 和文本语言不同,除了那些需要被重复使用的代码应该被做成子函数外,对于那些过于庞大或复杂的代码,即便它不会被其它地方调用,也应当被拆分为子 VI,以提高程序可读性。在一个项目中,如果每个子 VI 的程序量都很少,那肯定非常容易被理解。但是,它也会带来一些不利之处。
第一,子 VI 数量过多。程序员最担心的可能就是子 VI 数量过多是否会影响程序运行效率。使用子 VI 确实会占用一些额外的运算资源,但这些额外的开销非常微小,绝大多数情况下都可以忽略不计。如果对程序效率要求极高,即使是子 VI 这样不易察觉的额外开销也需要去掉的话,那么可以在子 VI 的属性对话框中(Execution 页面),勾选 “内联子 VI 到调用方”(Inline SubVI into calling VIs)。
这样,在编译时,LabVIEW 会自动将该子 VI 的代码“融入”到调用者中,消除调用开销,同时在开发环境中依然保持子 VI 的独立性和可读性。
第二,程序的层次比较多。程序的调用关系过多,可能会不利于阅读整个程序的全部代码。但实际上在团队工作中,每个程序员仅仅负责一个项目的一小部分。他主要关心整个项目的结构,以及自己负责那一部分的细节。而对于项目其它部分的细节则关心不多。所以,项目中模块和层次划分得越详细,子 VI 越多,反而越有利于程序员理解程序大致流程,以及有利于维护自己负责的那一小部分。
前面提到的是理想状况。实际编程时,因为创建子 VI 也需要花费一些额外的编程时间,程序员往往更注重当前程序的编写速度,而不是优化程序风格。为了加快编程速度,程序员甚至愿意牺牲程序的可维护性。毕竟,将来维护这段程序的可能不是他自己,而眼前,程序的开发速度比书写风格更容易受到领导关注。
所以,在实际制定编程规范时,可以采用折中方案。每个子 VI 可以更复杂一些,包含更多的节点。但这个程序框图复杂程度的上限就是不超过一个屏幕的显示范围。目前,主流显示器的面积都是比较大的,VI 的程序框图的面积最大也不应当超过显示屏幕的面积,这样,程序框图上的所有代码都可以在不拖动滚动条的前提下,在显示区域内显示出来。
很多初学者,为了尽可能多地在程序框图上添加内容,会把程序框图画得非常大。实际上,最佳的实践原则是:一个程序框图的逻辑应当尽量在一个屏幕范围内显示完 整,无需滚动滚动条。
如果逻辑复杂到必须滚动屏幕才能看完,那就说明该 VI 应该被拆分为多个子 VI 了。至于窗口大小是否全屏,取决于你的显示器配置。在单显示器下,建议不要全屏,以便同时查看前面板;在多显示器环境下,则可以灵活布局。
布局和连线
内容相同的两个程序框图也会有可读性的差别,这主要取决于程序框图上节点的布局和连线的排布。一般来说,排布整齐,连线方向一致的程序,肯定比杂乱无章的程序框图更容易阅读和理解。
排布程序框图时,主要应该注意以下几点:
- 程序框图上所有的连线的方向都应该是从左向右,数据在数据线上的流动总是从左向右。从左到右,是阅读时最自然的习惯。
- 程序框图上有一个主要数据线贯穿程序左右。这个数据应该是比较重要的数据,比如文件的引用、某个仪器设备、某个类等等。有了这个主线,其他人就可以顺着它来阅读代码。
- 数据线应当尽量减少弯曲和交叉。由于数据都是从左向右流动,理论上它们应当是平行的。
下图是一个示例程序框图的布线,这样的程序是非常容易阅读的:
把程序框图上一个个的节点、连线排列整齐,是要花费一些时间的,比较麻烦。好在 LabVIEW 提供了自动排布程序框图的功能,堪称是懒汉福星。点击 VI 程序框图窗口工具栏上的“整理程序框图”按钮,就可以自动整理程序框图。它可以在不改变程序的逻辑关系的前提下,调整程序框图上节点和连线的位置,使它们更整齐易读。比如下面这个杂乱的程序框图:

在点击“整理程序框图”按钮后,变成了下面工整的程序框图:

但是,这个工具毕竟比不上人脑更智慧,经过整理的程序框图虽然比原来整洁很多,但还是无法与人工调整出来的程序框图一样美观。
注释
为了帮助其他人更快地理解程序逻辑或一些比较特殊的实现方法,为代码添加文字说明是必不可少的。在 LabVIEW 中添加文字说明,首先应该做到以下几点:
- 使用有意义的控件或常量名称;
- 显示节点的标签,并使用有意义的文字;
- 为子 VI 创建有意义的图标。
程序框图上的各种节点和结构也和控件一样,是有标签的,只是默认状态下不显示。对于 LabVIEW 的各种结构,推荐使用子程序框图标签(Subdiagram Label)添加注释。在结构的右键菜单中选择 “可见项目 -> 子程序框图标签”:

结构的上方会变成一处添加文字的区域,把当前框图的注释写在这里即可。这一区域的背景颜色,字体颜色都是可以调整的。
使用标签添加注释的好处:一是比较容易统一整个项目的注释风格,二是注释会始终保持与节点和结构的相对位置,不会因为代码布局的调整而错乱。
如果仅使用节点和结构的标签仍然不够,比如作为教学、展示等用途的程序可能需要格外详细的解释说明,那么还可以在程序框图上添加一些文本标签,在文本标签中添加详细的注释文字。
注释应当尽量靠近被注释的节点或连线。如果注释文字过长,为了不影响程序正常的代码,也许只能把注释写在离被注释节点或数据较远的地方。为了使注释更加醒目和直观,可以使用以下两种风格的注释方式。
第一种,使用箭头表明注释针对的节点或数据,如下图所示:

LabVIEW 的文本标签自带箭头,不需要额外再使用箭头装饰。把鼠标放在文本标签上,标签右下角会出现一个小图标,拉动它,就会出现一个箭头(如下图所示)。拖动这个箭头到需要注释的节点或结构,箭头就会锚定在上面。这样,将来无论是移动了节点,还是移动了注释标签,都不用担心它们失去关联。

第二种,使用标号把注释文字和被注释的节点或数据关联起来,如图下图所示:

标号最好也使用文本标签自带的箭头用被注释节点锚定起来。
使用文本标签做注释有一个局限:它的布局可能会被“整理程序框图”工具破坏。因为它可能导致程序中注释的位置出现错误。在 LabVIEW 的程序中,文本标签和程序上的节点,连线等没有逻辑关系。“整理程序框图”工具并不知道它们内容的关联性,在整理程序框图的过程中,节点的位置发生变化,注释摆放的位置也会发生变化。带有箭头锚定的文本标签会摆在相对较合理的位置,没有锚定的文本标签可能会被放到完全不相关的位置上。
使用书签(Bookmarks)管理注释
在添加文本标签注释时,如果以 # 开头(例如 #TODO: 需添加错误处理 或 #FIXME: 算法待优化),LabVIEW 的 书签管理器(Bookmark Manager) 会自动识别这些注释并汇总成列表。
这对于标记未完成的工作、需要修复的 Bug 或代码审查意见非常有用,点击列表项即可直接跳转到对应的程序框图位置。
使用自定义数据类型
“簇”类型的数据是一种很常见的数据。编写比较大型的程序时,可能多个子 VI 的参数中都有同一种簇类型的数据。比如说,一个用于保存某一实验数据的程序,其中多个 VI 用到了同一个“实验信息”数据类型。“实验信息”是一个簇,它由两个元素组成,分别是“实验名称”和“实验时间”。
在程序的编写过程中,可能会发现这个簇数据的类型需要改动。原来的两个元素不够用,程序还需要添加“实验编号”这一信息:
由于程序中多个子 VI 都用到了这一数据类型,一旦它发生变动,就需要把所有用到这个簇的 VI 都进行相应修改。这样的修改不但繁琐,更糟糕的是可能会遗漏某些数据没有改动,造成程序潜在的错误。
解决这一问题的方法是为类似的数据创建一个自定义类型。自定义数据类型同自定义控件一样,保存在 .ctl 文件中。它的创建方法也与自定义控件相同。区别在于需要把它的控件类型设置为“自定义类型”或“严格自定义类型”。在程序中,如果用到了这个.ctl 文件,就把它叫做这个自定义控件或自定义类型的实例。
自定义控件与它的实例之间没有任何关联。如果使用自定义控件制作并保存了一个漂亮的按钮控件。需要用到它时,通过拖拽或打开这个.ctl 文件就可以在 VI 前面板上添加一个此类用户自定义控件的实例。这个实例一旦生成,就和原用户自定义控件无任何关联了。无论是你修改这个实例,还是修改原用户自定义控件,都不会对另一方产生任何影响。
自定义类型与严格自定义类型在 VI 上生成实例的方法与自定义控件相同。不同之处在于:自定义类型以及严格自定义类型与它们的实例之间是相关联的。比如,创建一个数值类型的自定义类型,它的所有实例也都是数值型的。如果在自定义类型文件中把这个控件的数据类型改为字符串,那么它已有的所有实例都将自动变成字符串类型。
对于严格自定义类型,它和它的实例之间,不但数据类型是相关联的,控件的属性也是相关联的。这些属性包括控件的颜色、大小以及枚举或下拉列表类型控件的项目等,也是相关联的。
比如,在使用自定义类型时,自定义类型上的控件是一个文本下拉列表控件,它的数据类型是 U16 数值型。控件中包括两个下拉选项:“条目一”和“条目二”。如果在自定义类型中,为它多添加一项 "条目三"。这时候,因为数据类型还是 U16 不变,它所有的实例不会跟随它更新,实例中的枚举型控件还是只有两项。但如果使用了严格自定义类型,则它所有的实例会自动更新,也都变为三项。
LabVIEW 中有些数据类型以及它们的属性经常会在编程过程中进行调整,比如簇、枚举、下拉列表、组合框、数组、波形图等。为了便于随时修改它们,在编程时,凡是遇到这样的数据类型,就该为它们制定一个严格自定义类型。当需要所有实例控件的外观都保持一致时,也应该使用严格自定义类型。
连线板
为了保持程序框图上连线的整齐,所有的子 VI 都应当尽可能采用相同模式的连线板。
比如,可以规定程序中所有 VI 的连线板都必须采用 4224 模式:(按照每一列接线端的数量命名此模式)。所有 VI 中的输入参数,用连线板左侧的六个接线端表示;输出参数用 右侧的接线端表示。子 VI 在程序框图默认是显示子 VI 的图标,但也可以把它的连线板显示出来:在子 VI 图标上点击鼠标右键,选择“显示项 -> 接线端”即可。
下图所示的程序中,所有子 VI 都统一采用了 4224 模式的连线板。这样就可以保证每个 VI 参数接线端所在的位置是固定的。两个子 VI 中,同一排上的参数接线端,它们的相对高度肯定是相同的。这样,它们之间连接数据线,就可以保证数据线是一条直线,没有弯折。
若程序框图上每个子 VI 采用不同模式的连线板,则他们的接线端很难都保持在同一高度,连线难免出现转折处。如下图所示:
4224 模式的连线板总共有 12 个接线端。如果 VI 的参数(输入输出控件)太多,超过 12 个,就没法使用这一模式了。但实际上,VI 的参数不宜太多,一般应该控制在 8 个以下。如果超过这一数字,不但这个 VI 使用起来比较麻烦,而且调用它的代码的可读性也成问题。一个子 VI 中联出十几根数据线,程序阅读者分辨起来就非常费劲了。
如果 VI 需要输入或输出较多的数据,可以把其中类似或相关的数据合并起来,用一个数组或簇来表示它们,以减少参数的数量。或者采用其它方式在 VI 间传递数据,比如使用全局变量。
参数对应的连线板接线端位置,应与参数控件在 VI 前面板上的位置相对应。这样做是为了方便程序阅读者直观地了解接线端与控件的对应关系。比如下图所示的 VI,它的连线板上接线端的布局与前面板上控件的布局完全一致。所以读者一眼就可以认出,连线板上第一列第二排接线端表示的参数是“AC Filter Bandwidth”。

关键原则:
- 错误簇位置: 无论采用何种模式,“错误输入”必须位于左下角,“错误输出”必须位于右下角。 这是 LabVIEW 的铁律,确保了错误链的连线平直。
- 引用句柄位置: 引用句柄(Refnum)通常位于左上角(输入)和右上角(输出)。
- 对齐: 尽量让输入和输出参数在水平方向上对齐,以减少连线弯折。