友情提示:380元/半年,儿童学编程,就上码丁实验室。

通常编程语言都会提供一些内置的功能模块,同时允许开发者编写自定义的功能模块,来实现某些特殊的功能,并提高编写程序的效率。App Inventor也不例外。
在App Inventor中,那些隶属于组件的紫色代码块,就是编程语言中内置的功能模块,也称为内置过程。同样,App Inventor也允许开发者定义自己的功能模块,即编写自定义过程{![在计算机科学中,过程(procedure)有许多别名,如子程序、函数、方法等,是一个大型程序中的某一部分代码,由一个或多个语句组成,负责完成某项特定任务,或实现特定功能。
与其他代码相比,过程是一段独立的代码,有自定义的名称,可以被其他代码多次调用。直接引用过程的名称,就可以实现对过程的调用。——译者注]}。在App Inventor中,通过命名一段代码块来定义过程,从而实现功能的扩展。应用中可以像调用App Inventor中的内置过程一样,调用自定义过程。
本章中你将看到,这样将某个具体功能抽象为过程的能力,对于解决复杂问题来说是至关重要的,同时也是创建高质量应用的基石。

在编写程序时,也有相似的对行为的抽象。程序员可以创建一些有名称的指令序列。在一些编程语言中,这些指令序列称为函数(function)或子程序(subprogram);在App Inventor中,称之为过程(procedure)。过程就是一系列的代码块,被赋予了一个有意义的名字,在应用中可以随时随地调用它。
图21-1就是一个过程的例子,它的功能是以英里为单位,计算两个GPS坐标之间的距离。

同样的道理,在设计或编写一个大型应用时,求两点间距离这个过程一旦定义完成,你就不必再关注它的具体实现方式,只要在需要的时候,直呼其名即可。这种抽象能力对于解决大型问题来说是至关重要的,可以将大型的软件项目分解成若干个便于管理的功能模块。
过程还有助于减少程序中的错误,因为可以消除很多冗余的代码。定义过程只是一次性地将代码规整到一起,之后就可以随时随地调用它。现在假如应用中要计算你所在的位置与其他10个点之间的距离,并求出最短距离。如果没有过程,你可能要复制粘贴10次图21-1中的代码块,而一旦定义了求两点间距离的过程,直接调用它就可以了。
此外,像复制粘贴代码块这样的行为,很容易在程序中留下隐患,因为一旦你想修改程序,就必须找到所有复制粘贴过的代码,并逐个以相同的方式修改它们,这种代码的可维护性极差(回顾一下第20章的内容)。想象一下,你试图在一个有1000个块的代码中,找到5~10处曾经粘贴过的代码块!与其被迫地复制粘贴这些块,不如用过程将它们封装起来。
最后,过程将有助于建立代码库,让这些代码在其他应用中也可以被复用。即便是创建一个非常具体的应用,有经验的程序员也会在必要时设法复用其他应用中的部分代码。有些程序员甚至从未创建过自己的应用,他们只是专注于创建可复用的代码库,以便其他程序员可以借此来创建他们自己的应用。
消除冗余
图21-2中的这些代码块来自于“随手记”应用,观察一下这些代码,看能否发现其中冗余的部分。

有经验的程序员在看到这样的代码时,脑子里会立即敲响警钟,甚至不必等到开始复制粘贴第一段代码,就能预料到这段代码的复用价值。因此最好将它们封装在一个过程里,这样既保证程序有很好的可读性,也可以让以后的修改变得容易。
因此,经验丰富的程序员会创建一个过程,将冗余的代码搬到过程中,并在原来使用冗余代码的地方调用这一过程。经过这样的调整,程序的运行结果完全一样,但代码更易于维护,也使得其他程序员有机会重复利用这些代码。这种代码(块)的重新整理过程称为代码的重构。
定义过程
我们来创建一个过程,实现图21-2中那些冗余代码的功能。在App Inventor中,定义过程几乎与定义变量一样简单:从内置块的过程抽屉中拖出一个“定义过程…执行”块或“定义过程…返回”块。如果过程需要通过计算返回一个结果,则使用后者(我们将在本章稍后的部分讨论)。
在拖出“定义过程”块后,可以修改过程名称:点击默认名称“我的过程”并输入新名称。由于冗余代码块的作用是显示笔记列表,因此重构时将过程名设为“显示列表”,如图21-3所示。


21.3 调用过程
诸如“显示列表”和“刷牙”这样的过程,暗含着要去完成一项任务。不过,在被调用之前,它们起不到任何作用,反过来说,只有在被调用时,才能体现出它们的功能。到目前为止,我们只是创建了过程,却没有调用它。调用它意味着让它运行,或者说让它实现预设的功能。
在App Inventor中,每当定义一个新的过程,就会在内置代码块的过程抽屉里生成一个新的块,如图21-5所示。

在“随手记”的例子中,从过程抽屉中三次拖出“调用显示列表”块来取代三个事件处理程序中的冗余代码。例如,列表选择框的完成选择事件处理程序(删除一条笔记),修改的结果如图21-6所示。

程序计数器
为了搞清楚调用过程块的运行机制,需要想象应用中有一个指针,它指向正在执行的指令,并随着程序的运行而移动。在计算机科学中,这个指针被称作程序计数器。
当程序计数器在事件处理程序中随着执行的指令移动时,一旦遇到一个调用过程块,它就会跳到该过程中,并开始追随过程中块的运行;当过程执行完成,程序计数器将跳回到此前的位置(调用过程块处),并继续向下移动。
以“随手记”为例,删除列表项块执行完成后,程序计数器跳到显示列表过程中,并随过程中的块(设置笔记标签的显示文本属性为空,并执行遍历列表循环)移动;最后程序计数器再回到事件处理程序中,继续执行“让本地数据库保存数据”块。
为过程添加参数
显示列表过程将冗余代码封装成一个“动宾词组”,这提高了代码的可读性,使你可以站在更高的层次上理解这些事件处理程序,而忽略掉如何显示列表的细节。这样做的另一个好处是,一旦你想修改列表的显示方式,只需修改一处代码(而不是三处)即可。
就过程的通用性而言,显示列表过程仍然存在局限性。该过程所显示的列表(笔记本)是特定的,用于显示列表的标签(笔记标签)也是特定的,它无法用来显示其他列表,比如应用的用户列表。究其原因,是过程中使用的要素太过具体了。
在App Inventor以及其他编程语言中,与过程相关联的一个重要的概念就是参数,使用参数可以构造出更为通用的过程。为了实现预设功能,过程需要对某些数据进行加工处理,而这些数据就是由参数来提供的。以睡前刷牙为例,可以将牙膏类型和刷牙时间设置为刷牙过程的参数。
在定义过程块的左上角有一个蓝色标记,点击它可以为过程添加参数。对于显示列表过程,我们定义了一个名为“列表”的参数,如图21-7所示。



对于“随手记”应用来说,“笔记本”列表将作为实际参数,被添加到调用过程块的列表插槽中。经过修改,列表选择框的完成选择事件处理程序如图21-10所示。

由于有了参数,显示列表过程可以用于处理任何列表,而不仅仅是笔记本。例如在“随手记”应用中,假设笔记的内容可以在一组用户中共享,而你想查看一下用户列表,就可以调用显示列表过程,并传入用户列表参数,如图21-11所示。

过程的返回值
关于显示列表过程的通用性,还有一个问题需要讨论。你能猜到是什么吗?如前所述,它可以显示任何数据列表,但是只能在笔记标签中显示。假如你想用其他界面元素(如另一个标签)来显示列表(如用户列表),该如何是好呢?
一个方法就是重构过程——重新定义它的功能。现有功能是“用指定的标签显示列表”,改为“只返回一段文本“,而这段文本可以被显示在任何地方。为此,需要使用“定义过程…返回”块来取代“定义过程…执行”块,如图21-12所示。

图21-13中是显示列表过程的改进版本,这次使用的是“定义过程…返回”块。注意,由于过程的功能改变了,因此名称也由原来的“显示列表”改为“列表转文本”。

在之前的显示列表过程里,使用了过于具体的笔记标签组件,来保存并直接显示列表内容。而在改进版本中,用变量文本替代了具体组件,来保存并输出列表的文本。在遍历列表循环执行完毕后,变量文本中包含了列表中的所有项,而且项之间以换行符“n”分隔(即“项1n项2n项3”)。最后,将变量文本插入返回插槽,返回给调用者。
在定义了有返回值的过程后,过程抽屉中同样会自动生成一个该过程的调用块。与无返回值过程的调用块相比,它们在外观上略有差别,如图21-14所示。你可以比较一下两者的不同。

在这种情况下,调用块的返回值可以填充到任何一个标签的显示文本属性中。以笔记本列表为例,需要显示列表的三个事件处理程序都可以调用这一过程,如图21-15所示。


跨应用的代码复用
通过创建过程,不仅可以在应用的内部实现代码的复用,有些过程,如列表转文本,还可以成为一种通用的资源,被更多的应用所采用。事实上,有许多组织和编程社区致力于创建公共资源,他们在自己感兴趣的领域积累了功能丰富的过程库。
通常编程语言会提供一个导入(import)功能,使得开发者可以在自己的应用中引入其他的代码库。App Inventor目前没有这项功能,因此要想实现过程在不同项目间的复用,只能专门创建一个代码库的应用,其中包含了可复用的过程。当你需要开发一个新的项目时,将代码库应用另存为新项目,并在此基础上进行新项目的开发。
求两点间距离
在显示列表(列表转文本)例子中,我们通过定义过程来消除程序中的冗余代码:你开始写代码,随后发现代码存在冗余,于是整理代码消除冗余,这一过程显得有些被动。作为一个软件的开发人员或开发团队,应该在正式开始编写代码之前,首先对软件进行设计,包括软件中可能需要的过程,以及对这些过程的复用。这样的设计可以在项目开发过程中节省大量时间。
考虑这样一个应用:确定离某人当前位置最近的本地医院。有些应用看似无用,却会在关键时刻派上用场!以下是对这个应用功能的设计。
应用启动时,以英里为单位求两点之间的距离,起点是设备所在的当前位置,终点是最先搜索到的医院。然后再搜索第二家医院,以此类推。在求得若干个距离后,判断距离最短的医院,并显示医院的的地址(以及/或者地图)。
从以上描述中,你能断定这个应用中需要什么样的过程吗?
通常,在一段应用的功能描述中,动词(动宾词组。——译者注)提示了程序中需要创建的过程以及过程的功能,而描述中重复的部分则是另一类线索(“诸如此类”之前的描述)。这种情况下,“求两点之间的距离”以及“判断出最短距离”成为两个必需的过程。
现在考虑设计一个过程“求两点间距离”(名称贴切就好,要做到独出心裁可不是我的强项)。在设计过程时,首先要确定过程的输入及输出。所谓输入,就是向调用过程块传递参数,来实现过程的功能;所谓输出,就是过程要向调用者返回执行结果。在这里,调用者需要向过程传递两个点的经度及纬度值,如图21-17所示;而过程的任务是以英里为单位返回两点之间的距离。



接下来程序比较求得的两个距离,来判断哪个医院最近。不过这种处理方式适用于仅有两家医院的情况,假如还有更多的医院,就需要创建一个距离列表,将所有求得的距离添加到列表中,通过遍历列表循环来比较所有距离,并找到最小值。依你所学,你能写出这个过程吗?试试看,将其命名为“求最近距离”,以距离列表为参数,并返回最短距离在列表中的索引值。
小结
像App Inventor这样的编程语言提供了一系列最基本的内置功能模块。为了扩展App Inventor语言的功能,可以编写自定义过程,将新的功能抽象成一个恰当的名称。虽然App Inventor本身并没有提供“显示列表”功能,但是你实现了这一功能。计算两个GPS坐标之间的距离,这个功能也要经常用到,怎么办呢?答案是靠你自己来创造。
我们这里创建的过程只是一些练习。在实际工作中,想要创建大型的、可维护的软件,还要定义更高级别的过程(在一个过程中调用另一个过程,甚至多重调用。——译者注),这将有助于解决更为复杂的逻辑问题,使你的思路免于陷入细节之中。对于一个程序员来说,这是一种至关重要的能力。过程是将代码块封装起来,并赋予它一个贴切的名字。在编写过程时,你会关注这些代码块的细节,但对程序的其他部分而言,这个过程只是一个抽象的名字,你可以站在更高的层次上来理解并使用它。