解决编程问题的普适性过程
- 本篇不需要任何前置知识,推荐在学习 C 语言和学完 C 语言后各看一遍。
- 我们鼓励你在解决问题的时候进行思考,锻炼解决问题的能力,而不只是成为一个做代码翻译工作的 “码农”。
解决编程问题的常见误区:
从编写代码入手,抓来就写(没有任何计划),对于简单编程问题,也许它是有效的,但往往它不可避免的不起作用。然后花费无数个小时试图修复代码(依旧没有任何计划),由于没有明确的计划来做什么,当 “修复” 代码时,往往导致它变得更复杂、更混乱。最终这个程序有点奏效,你对此心满意足。
相反,你应该以一种严谨的方式设计一种算法。下图显示了如何设计算法。然而,请注意,“编写代码” 只有在你有了一个经过手动测试的算法之后,才能在你建立计划之前给你一些信心,证明你的计划是可靠的。
如果你计划得足够好并且代码编写得正确,你的代码将在第一次工作。即便它第一次不起作用,那么你至少有一个对于代码如何调试的可靠计划。
Work an Example Yourself
尝试设计算法的第一步是自己(手动)处理至少一个问题实例,为每个参数选择特定值。往往需要确定一个正确的示例,以及错误的示例。
如果你在这一步陷入困境,这通常意味着两件事中的一件。第一种情况是问题不明确,不清楚你应该做什么。在这种情况下,你必须在继续之前解决问题。如果你正在解决自己创造的问题,你可能需要更仔细地思考正确的答案应该是什么,并完善你对问题的定义。
第二种情况是,缺乏领域知识,即问题所涉及的特定领域或学科的知识。也许你应该适当补充学习对应的知识。注意,领域知识可能来自数学以外的领域。它可以来自任何领域,因为编程对于处理任何类型的信息都很有用。
Write Down What You Just Did
这一步中,必须思考解决问题所做的工作,并写下 ** 解决该特定实例的步骤。** 思考这一步骤的另一种方式是,写下一组清晰的指示,其他人可以按照这些指示来重现刚刚解决的特定问题实例的答案。如果在步骤 1 中执行了多个实例,那么也将重复步骤 2 多次,对步骤 1 中的每个实例重复一次。如果一条指令有点复杂,那没关系,只要指令稍后有明确的含义,我们将把这些复杂的步骤转化为它们自己的编程问题,这些问题将单独解决。
Generalize Your Steps
** 将步骤 2 得到的具体步骤,抽象为一般性的结论。** 有时可能很难概括步骤。发生这种情况时,返回步骤 1 和 2 可能会有所帮助。做更多的问题实例将提供更多的信息供参考,更能帮助深入算法。这个过程通常被称为编写 “伪代码”,以编程方式设计算法,而不使用特定的目标语言。几乎所有的程序员在编写任何实际代码之前都会使用这种方法来确保他们的算法是正确的。
Test Your Algorithm
在步骤 3 之后,我们有了一个我们认为正确的算法。然而,我们完全有可能在这一路上搞砸了。步骤 4 的主要目的是确保我们的步骤在继续之前是正确的。为了实现这一点,我们使用不同于设计算法时使用的参数值来测试我们的算法。我们手动执行算法,并将其获得的答案与正确的答案进行比较。如果它们不同,那么我们知道我们的算法是错误的。我们使用的测试用例(参数值)越多,我们就越能确信我们的算法是正确的。不幸的是,通过测试无法确保我们的算法是正确的。要完全确定你的算法是正确的,唯一的方法就是正式证明它的正确性(使用数学证明),这超出了这个专门化的范围。
确定好的测试用例是一项重要的技能,可以随着实践而提高。对于步骤 4 中的测试,您需要使用至少产生几个不同答案的情况进行测试(例如,如果您的算法有 “是” 或 “否” 答案,则应使用同时产生 “是” 和 “否” 的参数进行测试)。您还应该测试任何角落情况,其中行为可能与更一般的情况不同。每当您有条件决定(包括计算位置的限制)时,您应该在这些条件的边界附近测试潜在的角点情况。
Translation to Code
既然你对你的算法很有信心,那么是时候把它翻译成代码了。大多数时候,你会想将代码输入编辑器,以便编译和运行程序。
Test Program
在黑盒测试中,测试人员只考虑功能的预期行为,而不考虑设计测试用例的任何实现细节。缺乏对实现细节的访问是这种测试方法的由来 —— 函数的实现被视为测试人员无法查看的 “黑盒子”。
事实上我们无需执行步骤 1-5 就能够为假设问题设想好的测试用例。实际上,在开始解决问题之前,您可以针对问题提出一组黑盒测试。一些程序员提倡测试优先的开发方法。一个优点是,如果您在开始之前编写了一个全面的测试套件,那么在实现代码之后就不太可能在测试上有所疏漏。另一个优点是,通过提前考虑你的情况,你在开发和实现算法时能够降低错误率。
- 选择测试用例的一些建议
- 确保测试涵盖所有错误情况。
- 确保测试 “太多” 和 “太少”。例如,如果程序需要一行正好有 10 个字符的输入,则用 9 个和 11 个进行测试。
- 任何给定的测试用例只能测试一个 “错误” 条件。这意味着,如果要测试两个不同的错误条件,则需要两个不同测试用例。
- 准确地在有效性边界处进行测试。
与黑盒测试不同,白盒测试涉及检查代码以设计测试用例。白盒测试中的一个考虑因素是测试覆盖率 —— 描述测试用例如何覆盖代码的不同行为。
请注意,虽然白盒和黑盒测试不同,但它们不是互斥的,而是互补的。可以从形成一组黑盒测试用例开始,实现算法,然后创建更多测试用例以达到所需的测试覆盖率。
Debug Program
一旦在代码中发现了问题,就需要修复它,这个过程称为调试。许多新手程序员(甚至一些经验丰富的程序员)以临时方式调试,试图更改代码中的某些内容,并希望它能解决他们的问题。这样的方法很少有效,常常会导致很多挫折。