第十六章 程序总览

在实现了一个作品的全部功能之后,就像完成了一次旷日持久的探险一样,疲惫而安详,此时,回头看看自己的工作成果,不仅可以获得一种成就感,更重要的是,这样的回顾过程可以让自己对程序的认识从感性上升为理性,这种提升不仅仅是知识与技能层面的,而是思维逻辑与哲学层面的。经过这样一次探险的洗礼,我们的视野变得开阔,性情变得平和,多了几分耐心与勇敢,对人生充满了感慨,也充满了希望。这是一个美好的时刻!

第一节 要素分类

如图16-1所示,这是程序中的全部内容,按照大的类别来划分,包括全局变量、过程及事件处理程序;在前两个大类中,还进行了小的分类,如将全局变量分为三类:数值变量、列表变量及列表常量。为什么要分类呢?为了便于我们理解它们在功能上的差别。

图16-1 程序中所有的要素

人类对世界认识的提升是从给事物分类开始的,比如生物分类学将生物按照“界门纲目科属种”七个层次进行分类。在日常生活中,如果有人问你,你是谁?你可能回答:我是一名老师,或者,我是一名程序员,再或者,我是一名家长,所有这些回答,答案其实都不是我,而是我归属的人群——类。我们会不自觉地用事物所属的类来思考,而不是用事物本身来思考。

在App Inventor开发的程序中,有8种颜色的代码块,来区分不同功能的代码以及不同类别的数据,但我们在图16-1中所见到的的只有3种颜色,这是为什么呢?这是因为只有这3类代码可以独立存在,而其余类型的代码块只能成为这些独立代码的一部分,不能单独停留在编程视图的工作区内。如此看来,独立代码与非独立代码,这也是一种分类。

一、常量

在图16-1中,我们将全局变量分为三类:数值变量、列表变量及列表常量,我们先来说说常量。在正式的编程语言中,都有“常量”这种数据类型,它们的值在程序运行过程中保持不变,但因为这些值要被多次使用,因此,将它们声明为常量,以便于使用和手工修改。在程序运行过程中,常量是不允许被改写的,因此,这一类数据也是安全的。在App Inventor中没有这种数据类型,但我们可以用变量来代替。本程序中的常量全部为列表类型的数据,在第14章中,我们将整理出来的数据表编写成数据列表,以便在程序中访问,进行各种判断和操作,但这些数据在程序运行过程中不曾被改写。

二、变量

我们将变量分为数值变量和列表变量。其中的数值变量只保存一个值,可能是一个数字、一个字符串、一个逻辑值或一种颜色;而列表变量中保存了多个列表项,这些列表项可能是一个值,也可能是另一个列表,这些列表项按照顺序排列,用索引值就可以访问到它们。在我们的程序中,有9个数值变量,而列表变量只有一个,即色块列表。变量用来记录程序运行中某些事物的状态,比如组块的编号、所在的行、列值或当前的游戏分数等等,这些值在整个程序运行过程中,不断地被改写,直到游戏结束。

三、过程——无返回值的绘图过程

程序中有9个这样的过程,这一类过程依据常量、变量的值来绘制图形,它们只改变屏幕上,或者说画布上的显示内容,而不改变程序中的变量。例如绘制组块过程,根据组块坐标列表、组块编号、基准行及基准列的值,在画布上特定位置绘制相应的组块。过程的命名中使用了如擦除、绘制、画等动词,也可以让人联想到它们的功能。另外,这些过程是有层次的,如组块下落调用擦除组块和绘制组块,而擦除和绘制组块过程又调用画块过程,这样构成了一套有结构的代码。

四、过程——无返回值、更新数值变量

程序中有6个这类的过程:创建新组块、组块变量更新、左移、右移、快落、旋转,这些过程的作用是改变与组块相关的变量。如左移、右移过程,通过判断,在满足移动条件的前提下,修改变量基准列的值。不过对这几个过程的命名,除了组块变量更新外,其余几个过程的名称,都没有明确地表示出它们具有变量更新的功能,这是需要我们改进的地方。

五、过程——无返回值、更新列表变量

程序中有三个这类的过程:初始化色块列表、更新色块列表及消除行。它们都是针对全局变量色块列表进行操作。色块列表包含192个列表项,初始化就是将每个列表项的值设为0;更新色块列表是将触底或触块组块的颜色码写入色块列表的指定位置,替换原来的0;消除行则是整行地删除颜色码均不为0的列表项,并在列表首位插入同等数量的、值为0的列表项。

六、过程——返回数值

在图16-1中有5个这样的过程,分别是求分数、求颜色码、求索引值、求重绘起始行及求直落行,从过程的命名上就可以理解这些过程的功能:利用程序中的常量、变量,通过运算,求得一个结果,这样的结果通常会成为某个代码块的输入项,如图16-2所示,求索引值和颜色码的结果用于比较大小。

图16-2 过程返回的数值作为其他代码块的输入项

七、过程——返回逻辑值

程序中有四个这样的过程:已经触顶、已经触块、快落触块、触犯禁区。逻辑值只有两个值:真或假。这些过程通过对常量、变量的计算,得出一个结果,这样的结果通常作为条件语句的输入项。如图16-3所示,在计时程序中,在确定组块没有触底(基准行>16)的情况下,判断组块是否触块,如果触块,再判断是否触顶。如果过程的名字取得好,会大大提高代码的可读性。

图16-3 返回逻辑值的过程:条件语句的输入项

八、过程——返回列表

程序中有两个这样的过程:求触底组块覆盖的行、求填满的行,其中前者是后者的输入项,即,求填满的行过程的参数是求触底组块覆盖的行的返回值。而求填满的行的返回值又是消除行过程的参数。如图16-4,是计时程序中消除填满行的一段代码。

图16-4 返回值为列表的过程

九、无返回值的综合过程

这里把“游戏结束”和“重新开始游戏”两个过程列为综合类的过程,原因是它们在功能上无法被归为前几个类。游戏结束过程用于处理组块触顶后的相关操作:弹出的对话框、提取及保存游戏得分、清除历史记录以及退出游戏;重新开始游戏过程的主要功能是清空画布、分数清零、初始化色块列表、创建下落组块等,兼具了绘制画布和更新数据的两项功能。

十、事件处理程序

事件处理程序是整个游戏的发动机,它们推动了游戏进程的发展变化,直到游戏结束。其中最核心的是计时程序,游戏至始至终在计时程序的控制之下,因此,这也是整个游戏中最复杂、也是最重要的一部分。如果你有兴趣,可以绘制一副这个程序的流程图,并把它挂在你时常能看得见的地方,你会发现,这其中还有一些可以改进的部分。

第二节 要素之间的关系

我们来看图16-5,这里包含了程序中的所有变量(不包含列表常量)、过程及事件处理程序,其中黑线代表程序之间的调用关系,箭头指向被调用的一方;红线表示程序对变量的改写,箭头指向被改写的变量。另外,还有一个过程被放置在图的右下角,这是经过整理被筛出来的无用的过程。

图16-5 要素关系图

从图中黑线的走向及密度上,可以很直观地得出结论:计时程序是整个程序的核心,从它这里一共发射出14条黑线;而求颜色码过程是程序中被调用最多的过程(有7条黑线与之相连),其次是求索引值(5条黑线)。红线反映出过程对变量的操作,从线的密度上看,对变量的修改集中在创建新组块以及组块变量更新这两个过程中。

在第11章中,我们也曾绘制过两张类似的程序全貌图,其中图11-8(程序结构图)与这里的图16-5相似,在11章,我们强调的是程序的结构,有结构的程序是稳固的、可扩展的,而在这里我们要补充一点:对全局变量的操作,要尽量集中在少数过程中。

全局变量是程序中最不稳定的要素,每一段程序都可以读取并修改它们,这无形中增加了程序出错的风险。有时我们发现程序的运行结果不是我们想要的,很多情况下,就要从全局变量的改写上寻找问题的所在。比如,在第七章中,在组块旋转后,由于没有关注到前一刻组块编号的问题,擦除程序是针对新的组块编号来操作的,因此在屏幕上留下了旋转前的红色方块。

这张图的另一个好处是,可以帮助我们厘清程序的脉络,一旦程序运行出现错误,我们很容易顺藤摸瓜,找到问题的所在。因此,不必等到软件开发将要结束时,才来画这张图,应该随着程序的进展,阶段性地绘制这类关系图,心中随时保持对程序的整体把握。

在最后审核这一章时,笔者发现了一个问题:从图16-5中不难发现,几乎所有全局变量都至少有两个箭头指向它,但重绘起始行及预报组块编号两个全局变量仅有一个箭头指向它们,这会不会是程序中的漏洞呢?这个问题留给读者自己去思考和解决。

第三节 开发中的测试

测试环节在软件开发过程中是必不可少的,是时时相伴的。无论我们的大脑多么的发达,我们的编程技术如何的高超,我们的思维多么的缜密,我们永远都不能和“机器”对抗。

初学者容易犯的错误是,先凭自己的想象“拼装”起一堆代码,未经测试就相信程序可以运行了,结果运行时发现漏洞百出,再将摞起来的代码一个个拆掉。几乎每个教编程语言的教材,第一个例子都一个“Hello world”程序,这是一个最简单的程序,用来测试开发环境是否已经就绪。我们的学习也应该从最简单的操作开始,在交替的开发与测试中完善程序,也提升自己的编程技术。

App Inventor中提供了两项很好的程序调试手段:禁用与单独执行某代码块,如图16-6所示。

图16-6 App Inventor中的两个调试工具

图中左边是“绘制背景”过程,用鼠标右键点击该过程,将弹出快捷菜单,其中第4项为“禁用代码块”,点击该项后,整个过程变成灰色,这时我们来测试程序,看看如果没有这个过程,程序是否可以正常运行。这是排查程序中错误的通常做法:屏蔽某一段程序,来诊断错误代码是否就隐藏在这段程序中。

图中右边是重新开始游戏过程,我们选中了“让画布1清除画布”代码块,点击右键弹出快捷菜单,选中最后一项“执行该代码块”,就可以让画布清空,你可以试着在程序运行过程中,单独执行这行代码,看看会发生什么事情。这项功能通常与“禁用”一起使用:首先让部分代码禁用,然后逐个解禁,并单独执行该代码,来排查出错误代码。

使用这二项功能调试程序时,要求开发环境必须连接了测试设备,或运行了模拟测试环境,否则,“禁用”不会有任何效果,而且第二项功能无法执行,系统还将将给提示,如图16-6中右下角的提示。

第四节 开发中的遗留问题

到目前为止,我们的程序实现了俄罗斯方块中应有的大多数功能,但程序的运行并非完美无缺,有些问题还需要进一步探索,来给出解决方案。

  1. 程序在运行到重绘画布时,有明显的停顿。这个问题在我的第一个版本的只有三种组块的游戏中也存在,在这个版本中,停顿的时间有所减少,但依然可以明显感觉到停顿;
  2. 在极限测试时,快速点击旋转按钮,会造成组块无法正确地触块,会悬在空中,但后续的块会部分地覆盖它。这说明色块列表中的数据已经实现了更新,只是画布的绘制没有完成;
  3. 长按快落键将实现“直落”功能,但在测试过程中发现,当已经触底的组块下方有空行(灰色方块)时,偶尔会有直落组块穿过触底组块的现象。

这就是程序中的bug,也叫臭虫,也叫错误,几乎所有程序都会遗留一些类似的bug,但我们不能以此为借口让这些bug永远寄居在我们的程序中,迟早会找到解决问题的办法。也希望学习者开动脑筋,找出这些bug的问题所在,将它们清除出去。

最后,关于事物分类的举例,我从网上找到了一幅很不错的生物分类图,放在这里与大家共享。