2.3 思考、编码、测试、调试、重复
在本书中,我们不仅要编写简单的程序,还将编写复杂的程序。不管程序的复杂性如何,通过一种有组织的结构化方法解决每个问题是非常重要的。我建议使用图2.3所示的“思考、编码、测试、调试、重复”范式,直到我们认为自己的代码已经很好地满足了问题所提出的要求为止。
“思考”步骤相当于确保自己理解了需要制作哪种类型的烘焙食品。思考一些由自己回答的问题,并决定是否已经有了适用的“菜谱”还是需要自行构思一个菜谱。在编程中,菜谱相当于算法。
“编码”步骤相当于开始动手试验各种配料、替代材料和重复过程(例如每隔5分钟检查一下面包皮的颜色)的各种组合。在编程中,它相当于用代码实现一种算法。
“测试”步骤相当于确定最终的产品是否与预期的相符。例如,从烤箱里拿出来的烘焙食物是不是一块面包?在编程中,我们用不同的输入运行一个程序,并检查它们的实际输出是否与预期输出相符。
图2.3 用编程解决问题的理想方法。在编写任何代码之前理解问题,然后对自己所编写的代码进行测试,并根据需要对代码进行调试。不断重复这个过程,直到代码通过了所有的测试
“调试”步骤相当于对菜谱进行微调。例如,如果做出来的东西太咸,就减少烘焙过程中所添加的盐。在编程中,我们对程序进行调试,找出哪行代码导致了不正确的行为。如果我们并没有遵循最佳实践,那么这只是一个粗略的过程。本章后面还将介绍一些调试方法,本书的第8部分也会描述一些调试技巧。
这4个步骤根据需要重复多次,直到代码通过所有的测试。
2.3.1 理解任务
当我们面对一个需要用编程解决的问题时,我们不应该立即就开始编写代码。如果我们从编写代码开始,就进入了图2.3所示的“编码”阶段。我们在第一次就能正确地编写代码是件不太可能的事情,因为我们不可能第一次就能够正确地解决问题。我们需要不断地重复整个周期,直到对特定的问题已经深思熟虑。从思考问题入手,我们就最大限度地减少了编程周期的重复次数。
我们处理难度大的问题时,可以尝试把它分解为几个更小的问题。这些更小的问题具有更简单、更少的步骤组合。我们首先可以把注意力集中在解决更小的问题上。例如,我们不应该一开始就烘焙一块充满“异国情调”的面包,而是可以从烘焙一个小面包卷入手,这样就能够熟悉正确的配料比例,从而不至于浪费太多的资源或时间。
当我们面对一个问题时,应该询问自己下面这些问题。
● 程序应该实现什么目标?例如,“计算圆的面积”。
● 程序与用户是否存在任何交互?例如,“用户将输入一个数”和“向用户显示该半径的圆的面积”。
● 用户提供的是哪种类型的输入?例如,“用户将提供一个表示圆的半径的数”。
● 用户期望从程序获得什么?以什么样的形式获得?例如,我们可能向用户显示“12.57”,或者是较复杂的“半径为2的圆的面积是12.57”,或者可以向用户显示一幅画像。
我建议按照以下两种方式描述问题,从而有组织地对问题进行思考。
● 确定问题的黑盒表现形式。
● 写下一些示例输入以及它们的预期输出。
即学即测2.1 找出一份菜谱(从身边或者从网上寻找)。写下关于该菜谱用途的问题。首先编写一个含糊的问题,然后再编写一个更加具体的问题。
2.3.2 任务的黑盒表现形式
当我们需要用编程解决一个特定的任务时,可以把这个任务看成一个黑盒。一开始,并不需要关注它的具体实现。
定义:实现就是编写代码来完成任务的方式。
我们暂时不关心实现的细节,而是专注于具体的问题:有没有与用户进行任何交互?程序是否需要输入?程序是不是会产生一些输出?程序在背后是不是需要执行一些计算?
绘制一张图展示程序与用户之间可能发生的交互是很有帮助的。回到面包烘焙菜谱这个例子。图2.4展示了一种可能的黑盒表现形式。输入在黑盒的左边,输出在黑盒的右边。
图2.4 用一组特定的配料烘焙一块面包的黑盒表现形式
当我们对黑盒的输入和输出有了概念之后,就可以考虑程序可能具有的一些特殊行为了。这个程序在不同的情况下是否具有不同的行为?在上面这个面包例子中,如果没有糖,有没有替代品可以使用?如果只加糖不加盐,会不会产生一种不同类型的面包?我们应该写下在这些情况下程序的行为。
所有这些特定的交互都可以用流程图来展示。我们可以追踪流程图中的多条路径,每条路径可以表示一种不同的实现和结果,如图2.2所示。
即学即测2.2 我们需要在完成烘焙之后进行一些清理工作。我们需要完成两件事情:洗碗和倒垃圾。用图2.2所示的流程图组织下面这些步骤和决策。尽可能多地使用下面这些步骤和决策,但并不需要全部使用。
步骤:用水冲洗碗 决策:还有东西需要放在垃圾袋里吗?
步骤:唱歌 决策:我对自己的烘焙技巧是否满意?
步骤:扎紧垃圾袋 决策:还有没有剩下的脏碗?
步骤:将垃圾拿出门 决策:今晚我是否应该去看电影?
步骤:拿起一个脏碗
步骤:用洗洁精擦洗脏碗
步骤:把洗干净的碗放入晾碗架
步骤:把垃圾袋放在地板上
步骤:把一些垃圾倒入垃圾袋中
2.3.3 编写伪码
现在,我们已经设计了一些测试用例,知道了一些需要关注的特殊行为,并了解了完成特定的任务所需要的一系列步骤的黑盒表现形式。如果能够用流程图画出这些步骤,现在是时候把流程图上的内容转换为用编程概念表示的语言了。为了解决问题,我们必须设计一系列需要遵循的步骤,用它们实现在问题中所规划的任务。
伪码混合使用了日常语言与编程代码,它可以写在纸上,也可以在代码编辑器中输入。它可以帮助我们确定程序的结构在各个阶段都是正确的:当我们从用户获取输入时;当我们显示输出时;当我们需要做出一个决定时以及当我们需要重复一组步骤时。
用语言概括一系列的步骤就像编写和试验自己的菜谱一样,我们必须根据自己所知道的配料味道和用途来决定它们的最佳使用方式。在编程中,我们必须使用自己所掌握的知识,把各种技巧和结构融入代码中,这也是编程中最为困难的部分。
在伪码中,计算圆的面积的代码可能像下面这样。
1.从用户那里获取一个半径。
2.应用一个公式。
3.显示结果。
4.重复步骤1~3,直到用户停止。
在本书中,我们将看到一些例子,它们说明了某些编程概念的重要性。知道使用哪个概念以及在什么时候使用这个概念的唯一方式是直觉,而直觉是通过大量的实践产生的。
当然,我们在编程中可以用很多方法完成同一个任务。在某种方法中陷入僵局无法取得进展并不是坏事,至少我们理解了为什么一种特定的方法对于当前情况并不适用。随着时间的积累和经验的增加,我们能够培养出更好的直觉,能够感觉到某个概念比另一个概念更为适用。
即学即测2.3 毕达哥拉斯定理(勾股定理)是,求c的值。用伪码编写一系列步骤求出c的值。
提示: