如何像计算机科学家一样思考

 

系列名称:如何像计算机科学家一样思考(How to think like a computer scientist)
包含版本:C++、JAVA、Python
我很喜欢这种教育方式~

附录A:程序开发计划
  如果花费了大量的时间在调试上,很可能是因为没有一个有效的程序开发计划。

  一个典型的不好的程序开发计划就像这样:
    1. 编写一个完整的方法。
    2. 编写更多的方法。
    3. 编译程序。
    4. 花一个小时来找语法错误。
    5. 花一个小时来找运行时错误。
    6. 花三个小时来找语义错误。

  显而易见,问题出在头两步。如果写了一个方法甚至很多方法都不调试,那么得到的代码可能已经多得让你无法调试了。 如果遇到这种情况,唯一的解决办法就是删除代码直到再次获得一个可以工作的程序,然后再慢慢将程序增加回来。编程新手往往不希望这么干,因为他们精心编写的代码实在是太宝贝了。可是为了高效的进行调试,你不得不残忍起来!
  下面是一个较好的程序开发计划:
    1. 从一个能做一些直观事情(比如打印一些东西)的程序开始。
    2. 每次增加少许几行代码,并且每次改动都测试程序是否正确。
    3. 重复前两步直到程序满足预期的要求。
  每次改动后的程序都应该产生一些验证新添代码的可见效果。这种编程方式能节省许多时间。因为一次只增加少许几行代码,所以容易发现语法错误;程序的每个版本产生一些可见的结果,这就使你能不断测试自己头脑中关于程序是如何工作的模型。如果头脑中的模型是错的,在写出一大堆错误代码之前你将面对矛盾(并且也有了改正的机会) 。
  这种方式的问题是常常难于找出下手的地方并得到一个完整正确的程序。我将通过开发一个名为isIn 的方法来演示这种方式。 这个方法取一个字符串和一个字符为参数, 返回一个布尔值: 如果字符出现在字符串中就返回 true否则返回 false。
  1. 第一步,写一个尽量短但可以编译、运行并做一些可见的事情的方法:

public static boolean isIn (char c, String s) { 
    System.out.println ("isIn"); 
    return false; 
}

  当然,要测试这个方法就要调用它,需要在main 方法或在一个正常工作的程序中什么地方创建一个简单的测试用例。 先来看一个字符串中出现了字符的用例(期望得到的结果是true) :

public static void main (String[] args) { 
    boolean test = isIn ('n', "banana"); 
    System.out.println (test); 
}

  如果一切按照计划进行,代码将顺利编译、运行然后打印单词 isIn 和值false。当然,答案是不对的,但目前我们知道方法被调用并且返回了值。 在个人的编程生涯里,我浪费了太多太多的时间调试某个方法,结果却发现它根本没有被调用。如果当时我采用这种开发方式,这种事情是不应该发生的。
  2. 第二步,检查方法取得的参数:

public static boolean isIn (char c, String s) { 
    System.out.println ("isIn looking for " + c); 
    System.out.println ("in the String " + s); 
    return false; 
}

  第一个打印语句允许我们确认 isIn 方法找的是正确的字母, 第二句用来确认找的是正确的位置。
  现在输出是:
    isIn looking for b
    in the String banana
  既然已经知道它们的作用,再打印参数似乎有点傻。关键在于确认它们是否和我们设想的一样。

  3. 为了遍历字符串,可以利用 7.3 节的代码。一般来说,重用代码片断比全部从头开始更好。

public static boolean isIn (char c, String s) { 
    System.out.println ("isIn looking for " + c); 
    System.out.println ("in the String " + s); 
    int index = 0; 
    while (index < s.length()) { 
        char letter = s.charAt (index); 
        System.out.println (letter); 
        index = index + 1; 
    } 
    return false; 
}

  现在运行这个程序,它将一次打印字符串中的一个字符。如果一切进展良好,可以确定这个循环检测了字符串中的每个字母。
  4. 到这里还没有充分思考过这个方法到底要做什么, 此时我们最需要的可能就是找到一种算法。最简单的算法是一个线性查找,即遍历向量并将每个元素和目标键进行比较。
  令人愉快的是前面已经写过了遍历向量的代码。和以往一样,每次增加几行:

public static boolean isIn (char c, String s) { 
    System.out.println ("isIn looking for " + c); 
    System.out.println ("in the String " + s); 
    int index = 0; 
    while (index < s.length()) { 
        char letter = s.charAt (index); 
        System.out.println (letter); 
        if (letter == c) { 
            System.out.println ("found it"); 
        } 
        index = index + 1; 
    } 
    return false; 
}

  遍历字符串的时候,将每一个字母和目标键做比较。如果找到了目标,就打印一些信息, 这样我们就能知道新增代码执行的时候产生了一些可见的效果。
  5. 现在已经很接近正确工作的代码了。 如果找到了要找的内容那么下一个改动是要从方法中返回:

public static boolean isIn (char c, String s) { 
    System.out.println ("isIn looking for " + c); 
    System.out.println ("in the String " + s); 
    int index = 0; 
    while (index < s.length()) { 
        char letter = s.charAt (index); 
        System.out.println (letter); 
        if (letter == c) { 
            System.out.println ("found it"); 
            return true; 
        } 
        index = index + 1; 
    } 
    return false; 
}

  如果找到了目标字符,返回true;如果经历了整个循环也没有找到,正确的返回值就应该是false。
  现在运行程序应该得到:
    isIn looking for n
    in the String banana
    b
    a
    n
    found it
    true
  6. 下一步是要确认别的测试用例能正确的工作。 首先应该确认如果字符没有在字符串中则方法返回false。然后要检查一些典型的容易招致麻烦的情况,例如空字符串”",或者只有一个字符的字符串。
  一般说来这种测试可以帮我们找到存在的bug,但不能判断方法是否正确。
  7. 倒数第二步要移出或注释掉打印语句。

public static boolean isIn (char c, String s) { 
    int index = 0; 
    while (index < s.length()) { 
        char letter = s.charAt (index); 
        if (letter == c) { 
            return true; 
        } 
        index = index + 1; 
    } 
    return false; 
}

  如果稍后还要查看这个方法,那么将打印语句注释掉是一个好主意。但如果这个方法是最终版本并且你能确信它是正确无误的,就可以移出这些打印语句了。
  移出注释可以让代码更加干净,也有助于发现遗留的问题。 如果代码的用意并不是很明显,就应该增加注释来解释清除。要抵制逐行翻译代码的诱惑。例如,这样做是毫无必要的:

// if letter equals c, return true
if (letter == c) {
return true;
}

  注释应该用来解释含义不明显的代码,提示容易导致错误的情况和说明包含在代码中的假设。还有,在每个方法之前给出该方法的用途也是一个很好的做法。
  8. 最后一步是检测代码并确认它是正确的。在这里我们知道方法的语法是正确的,因为编译顺利通过。要检查运行时错误,只有找出每个可能导致错误的语句和条件。
  在这方法中唯一能导致运行时错误的语句是 s.charAt (index)。如果s 是null 或者索引超越了边界那么这条语句就将失败。因为s 是一个参数,就不能确保它不是null,所以只有检查。一般说来方法最好都要确认参数的合法性。while 循环的结构保证了 index 总是在0 到 s.length-1 之间。如果检查全部有问题的条件,或者证明这些条件不可能发生,那么就证明了方法不会导致运行时错误。
  我们还没有证明这个方法的语义是正确的,但在逐步递增的过程中,避免了很多可能的错误。例如已经知道方法能正确取得参、循环遍历了整个字符串。我们还知道这个方法成功的比较了字符,如果目标在字符串中就返回true。最后我们知道,如果循环存在就表明目标不在字符串中。
  在没有正式证明的情况下,这可能是我们能做到的最好情况。

 


  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
内容简介 · · · · · · 本书以培养读者以计算机科学家一样的思维方式来理解Python语言编程。贯穿全书的主体是如何思考、设计、开发的方法,而具体的编程语言,只是提供了一个具体场景方便介绍的媒介。 全书共21章,详细介绍Python语言编程的方方面面。本书从基本的编程概念开始讲起,包括语言的语法和语义,而且每个编程概念都有清晰的定义,引领读者循序渐进地学习变量、表达式、语句、函数和数据结构。书中还探讨了如何处理文件和数据库,如何理解对象、方法和面向对象编程,如何使用调试技巧来修正语法错误、运行时错误和语义错误。每一章都配有术语表和练习题,方便读者巩固所学的知识和技巧。此外,每一章都抽出一节来讲解如何调试程序。作者针对每章所专注的语言特性,或者相关的开发问题,总结了调试的方方面面。 本书的第2版与第1版相比,做了很多更新,将编程语言从Python 2升级成Python 3,并修改了很多示例和练习,增加了新的章节,更全面地介绍Python语言。 这是一本实用的学习指南,适合没有Python编程经验的程序员阅读,也适合高中或大学的学生、Python爱好者及需要了解编程基础的人阅读。对于首次接触程序设计的人来说,是一本不可多得的佳作。 作者简介 · · · · · · [美] 艾伦 B. 唐尼(Allen B. Downey) Allen Downey是欧林工程学院的计算机科学教授,曾任教于韦尔斯利学院、科尔比学院和加州大学伯克利分校。他是加州大学伯克利分校的计算机科学博士,并拥有MIT的硕士和学士学位。 译者介绍 赵普明 毕业清华大学计算机系,从事软件开发行业近10年。从2.3版本开始接触Python,工作中使用Python编写脚本程序,用于快速原型构建以及日志计算等日常作业;业余时,作为一个编程语言爱好者,对D、Kotlin、Lua、Clojure、Scala、Julia、Go等语言均有了解,但至今仍为Python独特的风格、简洁的设计而惊叹。 目录 · · · · · · 第1章 程序之道 1 1.1 什么是程序 1 1.2 运行Python 2 1.3 第一个程序 3 1.4 算术操作符 3 1.5 值和类型 4 1.6 形式语言和自然语言 5 1.7 调试 6 1.8 术语表 7 1.9 练习 8 第2章 变量、表达式和语句 9 2.1 赋值语句 9 2.2 变量名称 9 2.3 表达式和语句 10 2.4 脚本模式 11 2.5 操作顺序 12 2.6 字符串操作 13 2.7 注释 13 2.8 调试 14 2.9 术语表 15 2.10 练习 16 第3章 函数 17 3.1 函数调用 17 3.2 数学函数 18 3.3 组合 19 3.4 添加新函数 19 3.5 定义和使用 21 3.6 执行流程 21 3.7 形参和实参 22 3.8 变量和形参是局部的 23 3.9 栈图 23 3.10 有返回值函数和无返回值函数 24 3.11 为什么要有函数 25 3.12 调试 26 3.13 术语表 26 3.14 练习 27 第4章 案例研究:接口设计 30 4.1 turtle模块 30 4.2 简单重复 31 4.3 练习 32 4.4 封装 33 4.5 泛化 34 4.6 接口设计 34 4.7 重构 35 4.8 一个开发计划 36 4.9 文档字符串 37 4.10 调试 38 4.11 术语表 38 4.12 练习 39 第5章 条件和递归 41 5.1 向下取整除法操作符和求模操作符 41 5.2 布尔表达式 42 5.3 逻辑操作符 42 5.4 条件执行 43 5.5 选择执行 43 5.6 条件链 44 5.7 嵌套条件 44 5.8 递归 45 5.9 递归函数的栈图 46 5.10 无限递归 47 5.11 键盘输入 47 5.12 调试 48 5.13 术语表 49 5.14 练习 50 第6章 有返回值的函数 53 6.1 返回值 53 6.2 增量开发 54 6.3 组合 56 6.4 布尔函数 57 6.5 再谈递归 58 6.6 坚持信念 59 6.7 另一个示例 60 6.8 检查类型 60 6.9 调试 61 6.10 术语表 63 6.11 练习 63 第7章 迭代 65 7.1 重新赋值 65 7.2 更新变量 66 7.3 while语句 66 7.4 break语句 68 7.5 平方根 68 7.6 算法 70 7.7 调试 70 7.8 术语表 71 7.9 练习 71 第8章 字符串 73 8.1 字符串是一个序列 73 8.2 len 74 8.3 使用for循环进行遍历 74 8.4 字符串切片 75 8.5 字符串是不可变的 76 8.6 搜索 77 8.7 循环和计数 77 8.8 字符串方法 78 8.9 操作符in 79 8.10 字符串比较 79 8.11 调试 80 8.12 术语表 82 8.13 练习 82 第9章 案例分析:文字游戏 85 9.1 读取单词列表 85 9.2 练习 86 9.3 搜索 87 9.4 使用下标循环 88 9.5 调试 90 9.6 术语表 90 9.7 练习 91 第10章 列表 93 10.1 列表是一个序列 93 10.2 列表是可变的 94 10.3 遍历一个列表 95 10.4 列表操作 95 10.5 列表切片 96 10.6 列表方法 96 10.7 映射、过滤和化简 97 10.8 删除元素 98 10.9 列表和字符串 99 10.10 对象和值 100 10.11 别名 101 10.12 列表参数 102 10.13 调试 103 10.14 术语表 104 10.15 练习 105 第11章 字典 108 11.1 字典是一种映射 108 11.2 使用字典作为计数器集合 110 11.3 循环和字典 111 11.4 反向查找 111 11.5 字典和列表 112 11.6 备忘 114 11.7 全局变量 115 11.8 调试 117 11.9 术语表 118 11.10 练习 119 第12章 元组 121 12.1 元组是不可变的 121 12.2 元组赋值 122 12.3 作为返回值的元组 123 12.4 可变长参数元组 124 12.5 列表和元组 124 12.6 字典和元组 126 12.7 序列的序列 127 12.8 调试 128 12.9 术语表 129 12.10 练习 129 第13章 案例研究:选择数据结构 132 13.1 单词频率分析 132 13.2 随机数 133 13.3 单词直方图 134 13.4 最常用的单词 135 13.5 可选形参 136 13.6 字典减法 137 13.7 随机单词 138 13.8 马尔可夫分析 138 13.9 数据结构 140 13.10 调试 141 13.11 术语表 142 13.12 练习 143 第14章 文件 144 14.1 持久化 144 14.2 读和写 144 14.3 格式操作符 145 14.4 文件名和路径 146 14.5 捕获异常 147 14.6 数据库 148 14.7 封存 149 14.8 管道 150 14.9 编写模块 151 14.10 调试 152 14.11 术语表 152 14.12 练习 153 第15章 类和对象 155 15.1 用户定义类型 155 15.2 属性 156 15.3 矩形 157 15.4 作为返回值的实例 158 15.5 对象是可变的 159 15.6 复制 159 15.7 调试 161 15.8 术语表 161 15.9 练习 162 第16章 类和函数 163 16.1 时间 163 16.2 纯函数 164 16.3 修改器 165 16.4 原型和计划 166 16.5 调试 167 16.6 术语表 168 16.7 练习 168 第17章 类和方法 170 17.1 面向对象特性 170 17.2 打印对象 171 17.3 另一个示例 172 17.4 一个更复杂的示例 173 17.5 init方法 173 17.6 _ _str_ _方法 174 17.7 操作符重载 175 17.8 基于类型的分发 175 17.9 多态 177 17.10 接口和实现 177 17.11 调试 178 17.12 术语表 179 17.13 练习 179 第18章 继承 181 18.1 卡片对象 181 18.2 类属性 182 18.3 对比卡牌 183 18.4 牌组 184 18.5 打印牌组 185 18.6 添加、删除、洗牌和排序 185 18.7 继承 186 18.8 类图 188 18.9 数据封装 189 18.10 调试 190 18.11 术语表 191 18.12 练习 191 第19章 Python拾珍 194 19.1 条件表达式 194 19.2 列表理解 195 19.3 生成器表达式 196 19.4 any和all 197 19.5 集合 197 19.6 计数器 199 19.7 defaultdict 200 19.8 命名元组 201 19.9 收集关键词参数 202 19.10 术语表 203 19.11 练习 203 第20章 调试 205 20.1 语法错误 205 20.2 运行时错误 207 20.2.1 我的程序什么都不做 207 20.2.2 我的程序卡死了 207 20.2.3 无限循环 208 20.2.4 无限递归 208 20.2.5 执行流程 208 20.2.6 当我运行程序,会得到一个异常 209 20.2.7 我添加了太多print语句,被输出淹没了 210 20.3 语义错误 210 20.3.1 我的程序运行不正确 211 20.3.2 我有一个巨大而复杂的表达式,而它和我预料的不同 211 20.3.3 我有一个函数,返回值和预期不同 212 20.3.4 我真的真的卡住了,我需要帮助 212 20.3.5 不行,我真的需要帮助 212 第21章 算法分析 214 21.1 增长量级 215 21.2 Python基本操作的分析 217 21.3 搜索算法的分析 218 21.4 散列表 219 21.5 术语表 223 译后记 224
### 回答1: 像计算机科学家一样思考Python意味着以科学家的思维方式来使用Python编程语言。这种思维方式强调逻辑性,注重问题解决的方法论和思考过程。 首先,像计算机科学家一样思考Python要注重问题的分析和解决。在编写程序之前,需要仔细分析问题并确定解决方案。这包括定义问题的输入、输出和所需的计算过程。通过详细的问题分析,我们可以更好地理解问题的本质,并找到更好的解决方案。 其次,像计算机科学家一样思考Python需要注重抽象和模块化。大问题可以通过将其分解为更小的、可管理的部分来解决。使用函数和类等概念对代码进行模块化,使其易于维护和扩展。同时,采用合理的命名和注释,使代码易于理解和阅读。 此外,像计算机科学家一样思考Python需要遵循良好的编程实践。这包括编写干净、可读性强的代码,避免重复和冗余。同时,应采用合适的数据结构和算法,以提高程序的性能和效率。 最后,像计算机科学家一样思考Python还强调测试和调试。在编程过程中,我们应该为代码编写测试用例,并进行测试以确保其正确性。对于出现的错误,我们需要进行系统的调试,定位问题并修复它们。 总之,像计算机科学家一样思考Python是一种系统和科学的方法来解决问题。通过注重问题分析、抽象和模块化、编程实践以及测试和调试,我们可以更高效地使用Python编程语言。这种思维方式不仅可以帮助我们更好地解决问题,也可以提高我们的代码质量和编程能力。 ### 回答2: 要像计算机科学家一样思考Python,我们需要遵循一些基本原则。首先,我们应该培养抽象思维和问题解决能力。计算机科学家不仅能够编写代码,还能够以高层次的方式思考问题,并将其分解成更小、更易处理的部分。这种能力有助于我们设计和实现更有效、可维护的代码。 其次,我们需要学会分析问题并提出多种解决方案。计算机科学家一直在寻求更好的方法来解决问题,这要求我们思考不同的方法,并评估每种方法的优缺点。这种批判性思维有助于我们编写高效、可扩展和可重用的代码。 此外,我们还需要学会使用适当的数据结构和算法。计算机科学家了解各种数据结构和算法的特点,并能够选择最适合解决特定问题的结构和算法。这些知识可以帮助我们提高代码的性能和效率。 最后,我们应该注重代码的可读性和可维护性。计算机科学家了解良好的编码实践,并努力编写易于理解和修改的代码。这样,不仅自己能够更轻松地维护代码,也可以使其他人能够更好地理解和使用我们的代码。 综上所述,要像计算机科学家一样思考Python,我们需要培养抽象思维、解决问题的能力,学会分析和评估多种解决方案,使用适当的数据结构和算法,以及关注代码的可读性和可维护性。这些原则将帮助我们提高编程技能,并更好地应用Python语言。 ### 回答3: 像计算机科学家一样思考Python,意味着运用系统化的思维方式来解决问题,并利用Python编程语言的特性和功能。 首先,计算机科学家注重分析和解决问题的能力。在编写Python代码之前,需要深入了解问题的本质和要求,并分析问题的各个方面。这意味着需要明确问题的输入和输出,以及中间的计算步骤。通过分析,可以找出更加高效和可行的方法来解决问题。 其次,计算机科学家注重代码的可读性和可维护性。在Python编程中,注重命名规范和代码结构的清晰度。变量、函数和类的命名应该简洁明了,能够准确地反映其功能和用途。代码应该按照模块化和可重用的原则进行组织,使其易于理解和修改。此外,注释和文档也是重要的方面,可以帮助他人理解代码和进行维护。 进一步,计算机科学家注重算法和数据结构的选择。不同的算法和数据结构适用于不同类型的问题。在使用Python编程时,要了解各种算法和数据结构的优劣之处,并选择最适合的方法来解决问题。这将有助于提高代码的执行效率和性能。 另外,计算机科学家注重测试和调试。通过编写有效的测试用例来验证代码的正确性,通过调试技巧来解决代码中的错误和问题。在Python编程中,可以利用断言、单元测试和调试器等工具来进行测试和调试,从而确保代码的正确性和稳定性。 总结来说,像计算机科学家一样思考Python,需要注重问题分析、代码可读性和可维护性、算法和数据结构的选择,以及测试和调试等方面。这种思维方式能够帮助我们更加高效和优雅地使用Python来解决各种问题。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值