设计第一个程序
本节阅读量:我们已经学习了一些程序的基础知识,现在更仔细看看如何设计程序。
通常当有某种想法,并想通过程序实现。新手程序员很难弄清楚如何将想法转换为实际的代码。但事实上,你已具备了许多需要的技能。
最重要的事情(也是最难做的事情)是在开始编码之前设计程序。很多时候,编程就像盖房子。如果试图在没有遵循建筑计划的情况下建造房子,会发生什么?除非你很有天赋,否则最终会得到一个有许多问题的房子:墙不直,屋顶漏水等等。类似地,如果在有一个好的计划之前尝试编程,很可能会发现你的代码有许多问题,你将不得不花费大量时间来解决那些本可以避免的问题。
长远来看,提前计划将节省时间,减少挫败感。
在本课中,将展示一种将想法转换为简单程序的通用方法。
设计步骤1:确定目标
为了成功的编写程序,首先需要确定目标。理想情况下,应该能用一两句话来说明。例如:
- 用户所在的组织名称和电话号码的列表。
- 随机生成地下城和有趣的外观洞穴。
- 生成具有高股息的股票推荐列表。
- 模拟球从塔上掉落到地面所需的时间。
尽管这一步似乎很明显,但也非常重要。因为最糟糕的,是编写一个与实际上想要不同的程序!
设计步骤2:确定需求
虽然步骤1可以帮助确定想要的结果,但仍然是模糊的。下一步是考虑需求。
需求是一个花哨的词,用于表示解决方案需要遵守的约束(例如预算、时间、空间、内存等),以及程序为满足用户需求而必须的功能。注意,需求同样是关注“什么”,而不是“如何”。
例如:
- 应保存电话号码,以便以后调用。
- 随机副本应始终包含从入口到出口的路线。
- 股票推荐应依据历史定价数据。
- 用户应能够输入塔的高度。
- 需要在7天内出测试版本。
- 程序应在用户提交请求后10秒内生成结果。
单个问题可能会产生许多需求,解决方案直到满足所有需求时才“完成”。
设计步骤3:确定工具、目标和代码备份
对于经验丰富的程序员,此时通常会有许多其他步骤,包括:
- 定义程序将在其上运行的目标体系结构或操作系统。
- 确定要使用的工具集。
- 决定是单独编写程序还是作为团队共同编写程序。
- 定义测试/反馈/发布策略。
- 确定如何备份代码。
然而,对于新手程序员,这些问题的答案通常很简单:用正在使用购买或下载的IDE,独自在自己的系统上编写供自己使用的程序,而且除了自己之外,代码可能不会被任何人使用。
如果要处理复杂逻辑,应有一个备份代码的计划。仅仅将目录压缩或复制到机器上的另一个位置是不够的(尽管这总比没有要好)。如果系统崩溃,您将失去一切。一个好的备份策略涉及从系统中取出全量代码的副本。有许多简单的方法可以做到:将其压缩并通过电子邮件发送给自己,将其复制到百度云或其他云服务,用FTP传到另一台计算机,将其拷贝到本地网络上的另一台机器,或使用安装在另一台机器或云上的版本控制系统(例如github)。版本控制系统还有一个优势,不仅能恢复文件,还能回滚到以前的版本。
设计步骤4:将困难问题分解为简单问题
现实生活中,经常需要执行非常复杂的任务。试图弄清楚如何完成这些任务是非常有挑战性的。在这种情况下,经常使用自顶向下的问题解决方法。即,我们不是解决单个复杂的任务,而是将该任务拆解为多个子任务,每个子任务都更容易单独解决。如果这些子任务仍难解决,则可以进一步细分。通过不断地将复杂的任务拆分为简单的任务,最终可以达到每个单独的任务都是可管理的,即使不是琐碎的。
例如,假设想打扫房子。任务层次结构如下所示:
- 打扫房子
一次清洁整个房子是一项相当大的任务,因此让我们将其分解为子任务:
- 打扫房子:
- 清理客厅
- 清理厨房
- 清理卫生间
这更易管理,因为有了可以单独关注的子任务。然而,可以进一步细分:
- 打扫房子:
- 清理客厅
- 打扫地面
- 清理厨房
- 清理垃圾
- 洗餐具
- 清理卫生间
- 清洁马桶
- 整理洗漱用具
- 清理客厅
现在有了任务层次结构,没有一个任务特别困难。通过完成这些相对容易管理的子项,就完成更困难的打扫房子的总体任务。
创建任务层次结构的另一种方法是从下至上。这种方法中,将从简单任务的列表开始,并通过对它们进行分组来构造层次结构。
例如,许多人在工作日要上班或上学,所以假设想要解决“上班”的问题。如果有人问早上起床上班需要做什么任务,可能会得出以下列表:
- 挑选衣服
- 穿上衣服
- 吃早餐
- 出发上班
- 刷牙
- 起床
- 准备早餐
- 取自行车
- 洗脸
使用自下而上的方法,将具有相似性的项目分组在一起,将其组织到项目的层次结构中:
- 起床
- 关闭闹钟
- 下床
- 穿衣服
- 清洁自己
- 刷牙
- 洗脸
- 早餐
- 备餐
- 吃早饭
- 路上
- 取自行车
- 骑自行车
这样任务层次结构在编程中非常有用,因为有了任务层次结构后,就基本上定义了整个程序的结构。顶级任务(在本例中,“打扫房子”或“上班”)相当于main()函数(因为它是试图解决的主要问题)。子项成为程序中的函数。
如果其中一项(功能)太难实现,只需将该项拆分为多个子项/子功能。最终,应该达到这样的效果,即程序中的每个函数都很容易实现。
设计步骤5:确定事件的顺序
现在,程序有了结构,是时候确定如何将所有任务链接在一起了。第一步是确定将要执行的事件的顺序。例如,当早晨起床时,做上述任务的顺序是什么?可能如下所示:
- 起床
- 清洁
- 早餐
- 上路
如果是编写计算器,可以按以下顺序执行操作:
- 用户输入第一个数字
- 用户输入数学运算符
- 用户输入第二个数字
- 计算结果
- 打印结果
到这一步,设计完后,我们已经为实际实施做好了准备。
实施步骤1:概述主要功能
现在,准备开始编写代码。上面的序列可以用于概述您的主程序。暂时不要担心输入和输出。
|
|
或者对于计算器:
|
|
请注意,如果要使用这种“大纲”方法来构造程序,则函数将无法编译,因为定义尚不存在。在准备实现函数定义之前注释掉函数调用是解决此问题的一种方法。或者,可以先定义对应的空函数,以便程序可以编译。
实施步骤2:实现每个函数
在这一步中,对于每个函数,将执行三项操作:
如果函数足够细粒度,则每个函数都应该相当简单和直接。如果给定的函数看起来仍然过于复杂,无法实现,则需要分解为更容易实现的子函数(或者,如果以错误的顺序定义了某些操作,需要重新检查事件的顺序)。
下面执行计算器示例中的第一个函数:
|
|
首先,确定getUserInput函数不接受参数,并将向调用方返回一个int值。这在返回值为int且没有参数的函数原型中得到反映。接下来,我们编写了函数体,包含4条语句。最后,在函数main中实现了一些临时代码,以测试函数getUserInput(包括其返回值)是否正常工作。
使用不同的输入值多次运行该程序,并确保该程序的行为符合预期。如果发现一些不工作的情况,就可以知道问题出在刚刚编写的代码中。
一旦确定程序这部分按预期工作,就可以删除临时测试代码,并继续实现下一个函数(函数getMathematicalOperation)。
记住:不要一次性实现整个程序。分步骤进行,在继续之前测试每个步骤。
实施步骤3:最终测试
一旦程序“完成”,最后一步是测试整个程序,并确保按预期工作。如果不工作,请修复它。
编写程序时的建议
让程序易于启动。新手程序员通常对他们的程序做的事情有个宏伟愿景。“我想编写一个具有图形和声音以及随机怪物和地牢的角色扮演游戏,有一个你可以访问的城镇来出售在地牢中找到的物品”。如果你试图写一些太复杂而无法开始的东西,你会因缺乏进展而不知所措和气馁。相反,让你的第一个目标尽可能简单,是你力所能及的。例如,“希望能够在屏幕上显示某个字段”。
随着时间的推移添加功能。一旦简单程序工作正常,然后您可以向其添加功能。例如,一旦可以显示字段,就可以添加一个可以四处走动的角色。一旦可以四处走动,就添加可能阻碍你前进的墙。一旦有了城墙,就用它们建造一个简单的城镇。一旦有了一个城镇,增加商人。通过增量地添加每个功能,程序将逐渐变得更复杂,而不会在过程难倒你。
一次专注于一个领域。不要试图一次编写所有代码,也不要将注意力分散在多个任务上。一次专注于一项任务。一个工作任务和五个尚未开始的任务比六个部分工作任务要好得多。分散注意力,很可能会犯错误,忘记重要的细节。
测试每一段代码时。新手程序员通常会一次性编写整个程序。当第一次编译时,编译器报告数百个错误。这不仅是令人畏惧的,如果代码不能工作,也很难找出原因。相反,编写一段代码,然后立即编译和测试它。如果不工作,将确切地知道问题在哪,并且很容易修复。一旦确定代码可以工作,就继续下一部分。这样完成代码编写也许需要更长时间,但当完成时,整个程序可以正常工作,且不必花费多倍的时间来尝试找出问题。
不要沉迷于完善早期代码。函数(或程序)的初稿很少是完美的。此外,随着添加功能并找到更好的方法来构建事物,程序往往会随着时间的推移而改变。如果过早地沉迷于改进代码(添加大量文档、完全遵守最佳实践、进行优化),则在需要更改代码时,可能会失去所有这些投资。相反,让功能最少地工作,然后继续下一步。当对解决方案充满信心时,可以进行代码重构。不要追求完美——有价值的程序永远都不是完美的,总可以做一些事情来改进。达到“足够好”并继续前进。
优化可维护性,而不是性能。Donald Knuth 有一句名言说,“过早的优化是一切罪恶的根源”。新手程序员通常花费太多的时间来考虑如何微观优化代码(例如,试图找出2个语句中哪一个更快)。这很少有实际用途。大多数性能优势都来自良好的程序结构,使用正确的工具和功能来解决手头的问题,并遵循最佳实践。应该使用额外的时间来提高代码的可维护性。找到冗余并删除。将长函数拆分为短函数。用更好的代码替换笨拙或难以使用的代码。最终的结果是代码更容易在以后进行改进和优化(在确定实际需要优化的位置后),并且错误更少。在问题变成问题之前发现它们。
总结
许多新手程序员简化了设计过程(因为它看起来需要花费额外的精力/或它不像编写代码那么有趣)。然而,对于任何有价值的项目,从长远来看,遵循这些步骤将节省大量时间。预先设计可以节省最后的大量调试。
