这两周我们小组进入了冲刺阶段的实训,这周我读了《程序员修炼之道》第三四章的内容。
靠巧合编程 怎样靠巧合编程
一开始就不知道它为什么能工作。实现的偶然: 因为代码现在的编写方式才得以发生的事情。最后会依靠没有记入文档的错误或是边界条件。它也许不是真的能工作--它也许只是看起来能工作。你依靠的边界条件也许只是一个偶然。 没有记入文档的行为可能会随着库的下一次发布而变化。多余的和不必要的调用会使你的代码变慢。多余的调用还会增加引入它们自己的新bug的风险。 结论? 对于你编写给别人调用的代码,良好的模块化以及把实现隐藏再撰写了良好文档的小接口之后,这样一些基本原则都能对你有帮助。对于你调用的例程,要只依靠记入了文档的行为。如果出于任何原因你无法做到这一点,那就充分地把你的各种假定记入文档。 语境的偶然: 只是你现在为GUI环境编写代码,该模块就必须依靠给你的GUI吗?你是否依靠说英语的用户?有文化的用户?你还依靠别的什么没有保证的东西? 隐含的假定:巧合可以在所有层面让人误入歧途--从生产需求知道测试。特别是测试,充满了虚假的因果关系和巧合的输出。不要假定,要证明。 Don't Program by Coincidence 怎么深思熟虑地编程 总是意识到你在做什么。不要盲目地编程。 按照计划行事 ,依靠可靠的事物,不要依靠巧合或假定。 为你的假定建立文档。不要只是测试你的代码,还要测试你的假定。断言为你的工作划分优先级。把时间花在重要的方面,很可能也是最难的地方。如果基础设施不正确,再好的口哨都没用。不要做历史的奴隶。准备好进行重构。 所以下次有什么东西看起来能工作,而你却不知道为什么,要确认它不是巧合。
算法速率 我们说估算算法是什么意思?
只要我们编写的是含有循环或递归调用的程序,我们就会下意识地检查运行时间和内存需求。O()表示法:1:常量型,访问数组元素,简单语句lg(n): 对数型,二分查找 n:线性型,顺序查找 nlg(n):比线性差,但不会差很多(快速排序,堆排序) n的平方,平方律型(选择和插入排序) n的立方,立方型,2n×n 矩阵相乘。 Cn,指数型,旅行商问题,集合划分。 常识估算: 简单循环,从1循环到n,很可能是O(n) 嵌套循环,O(m*n) -> O(n的平方) 二分法,O(ln(n)) 分而治之,O(nln(n)) 组合,常常用启发式方法来优化。 实践中的算法速率:时间和空间的权衡。估算你的算法的阶,只需要几个点就可以确定。测试你的估算。 最好的并非总是最好的:最快的算法未必是最合适的。有时候追求简单,快速。
重构 重写,
重做和重新架构代码合并起来,成为重构,refactor 你应在何时进行重构不要对改动犹豫不决 重复,非正交的设计,过时的知识,性能。 现实世界的复杂情况:重构就像是切除肿瘤,需要停止花时间修复。否则早重构,常重构。 如果不能立刻重构,将其加入计划。 怎样进行重构: 重构就是重新设计,新的事实、新的理解、新的需求等。重构要慎重、深思熟虑、小心进行的活动。 1, 不要试图在重构的同时增加新的功能。 2, 在开始重构之前,确保你拥有良好的测试。尽可能经常运行这些测试。这样,如果你改坏了,你也会知道。 3, 采取短小、深思熟虑的步骤。在每个步骤后测试。 修改了代码,也要修改依赖代码的地方。最好一劳永逸地修正它,不要容忍破窗户。
易于测试的代码 单元测试
软件的单元测试是对模块进行演练的代码,建立某种人工环境,然后调用被测试的模块中的例程。然后不同的测试值,对返回值进行检查。 针对合约进行测试: 代码是否符合合约,以及合约的含义是否与我们所认为的一样。边界,前后条件等。 这种风格的测试要求你首先测试模块的自组件。这样容易得知哪里出了错误。 为测试而设计。 早发现问题比解决问题更好。 编写单元测试: 将它们放在方便的地方。 这个给开发者带来无价的资源: 1, 一些例子,说明怎样使用你的模块的所有功能。 2, 用于构建回归测试、以验证未来对代码的任何改动是否正确的一种手段。 可以用#ifdef在编译的时候跳过单元测试的代码。 也可以借助shell脚本。使用测试设备: 可以是GUI驱动,也可以是makefile与perl脚本组合 测试装备都应该有以下功能:用以指定设置与清理的标准途径。用以选择个别或所有可用测试的方法。分析输出是否是预期结果的手段。标准化的故障报告形式。 可以通过子组件来构成一个系统,达到任意深度。 即兴测试,临时测试,print,或者交互。可能需要将它正式化。 构建测试窗口:含有跟踪消息的日志文件就是一种机制,格式要正规一致。使用不为普通用户所知的热键,来打开诊断调试窗口。更大的程序,可以使用web服务器。 测试文化:测试是技术,更是文化。测试你的软件,否则你的用户就得测试。