程序员修炼之道阅读笔记之二
程序员修炼之道阅读笔记之二
第四章:注重实效的偏执
第二十一节:按合约设计
在这个行业中,与计算机交流是困难的,但是同人交流却更显得困难一些。为了解决这些困难,我们制定了一些合约。合约规定了双方的责任和义务,以及双方的权益,每个人从中获利。
关注文档记载的DBC:软件模块的责任和义务
前条件:为了调用例程,必须为真的条件。
后条件:例程必须要做的事情,不能有无限循环
类不变项:保证从调用者的角度来看,总是为真。
Java 中的 iContract 框架是专为 DBC 设计的,它通过注释里的 @pre、@post、@invariant 声明这三个概念。它会读取注释并生成包含断言逻辑的源文件。Eiffel 则是通过 require、ensure、is 三个值表示对应概念。但是支持 DBC 的语言真的很少。
第二十二节:死程序不说谎
对待程序我们通常会有“它不会发生”的心理状态,这会导致我们忽视一些问题。对于注重实效的程序员来说,如果我们忽略了一个错误,将是非常糟糕的事情。
对于程序员来说,崩溃往往比破坏更容易让人接受。当代码发生认为不可能的事情时,那么系统已经崩溃了。
正确认识死程序,死程序的危害比有问题的程序危害要小。
第二十三节:断言式编程
如果它不可能发生,用断言确保它不会发生。
使用断言进行代码的检查,排除错误,但传入断言的代码不应该有附条件;但不能使用断言代替错误处理。
让断言开着:检查你任何疏漏的错误
第二十四节:何时使用异常
异常很少应作为程序的正常流程的一部分使用,异常应该保留给意外情况。如果移除了所有的异常处理器,代码就无法运行,那说明异常正在被用于非异常情况中。
是否应该使用异常取决于实际情况。比如打开文件,文件不存在,是否应该发生异常?如果文件应该在那里,那么异常就有正当理由。如果不确定文件是否在那里,返回错误就可以了。
第二十五节:怎样配平资源
对于资源的分配:要有始有终。使用资源,分配资源,解除占用。
对于资源的占用有始有终,但在一些语言中可以扩展这个概念。对资源进行嵌套分配:①以与资源分配相反的次序解除资源占用;②在不同地方分配同一资源时,以相同的次序进行分配,降低死锁的可能性。
异常的配平需要避免违反 DRY 原则。例如文件打开的异常情况,会导致 try..catch 有两条路径,那如何避免在正常流程和 catch 流程都处理 error 情况呢?C++ 可以依赖对象自动析构的特性,Java 可以依赖 finally子句。
无法配平资源时,为内存设立语义不变项,接触顶层结构,可在动态分配的对象上实现可计数方案。
第五章:弯曲、或折断
第二十六节:解耦与得墨忒耳法则
编程中可以引入划分的思想,将模块尽可能小的划分,减少模块之间的交互,其中一个模块出问题时,并不会影响到其他模块。
使耦合减至最小:只需对一个大的对象进行交互,不在需要面对第三甚至第四对象。
第二十七节:元程序设计
再多的天才也无法胜过对细节的关注。
动态配置:使系统变得高度可配置,不仅做出文本提示的表面产物,还有诸如算法和数据库产品等深层次的选择。(要配置、不要集成)
元数据:数据的数据,通过元数据配置和驱动程序。(把抽象放入代码,把细节放入元数据中)
协作式配置:应用相互配置,让其自身适应环境的改变。没有适应能力的代码,只会成为自然界中的渡渡鸟。
第二十八节:时间耦合
时间耦合:在线性时间安排下,时间的次序问题。从这个方面看,我们容许设计中的并发。分析工作流,改善并发。
架构:在设计架构时,用服务进行设计而不是组件。饥饿的消费者模型是在多个消费者进程间进行快速而粗糙的负载平衡的一种有效途径。
一旦具有并发的设计要素,在部署的时候,对于架构的考虑就会仔细很多。
第二十九节:他只是试图
模块编写中,常采用分而治之的思想,但我们只想让模块接受他想听到的数据,可使用一些发布/订阅协议。
Model-View-Controller 是一种将模型与表示模型的 GUI 分离的架构模型,它能有效降低数据和视图之间的相互影响。
第三十节:黑板
黑板方法的关键特性:①从中了解信息并加上自己的发现。②“侦探”可能接受的训练不同,背景和能力也不同,但他们的共同点是都想破案。③侦探班次不同,采取的方法也不同。④黑板没有限制。
黑板作为一个大的平台,存放了主动的对象,消除了太多接口的需要,带来了优雅、一致的系统。
不同于原始的工作流需要考虑各种状况,不同组合,先后顺序等,黑板系统只管写入,读取,查询,通知等基础功能,任意符合条件的事件都可以进入这个系统。
第六章:当你编码时
第三十一节:靠巧合编程
避免巧合编程,趋于深思熟虑的编程,否则结果是灾难性的。
实现的偶然:巧合式编程的实现背后有着多样的因素。
深思熟虑的编程:①总是意识到你在做什么;②不要盲目的编程,不要试图构建你不熟悉的框架或者应用你并不熟练的技术。③按照计划行事,计划是列出来的,并不在你的心中或者餐巾纸上。④依靠可靠的事物。⑤为你的假定建立文档。⑥不仅测试你的代码,还要测试你的假定。⑦为你的工作划分优先级。⑧不要做历史的奴隶,不要让现有的代码支配将来的代码。
第三十二节:算法速率
估算算法即是我们熟知的时间复杂度,用O()表示,它有以下几种常见类型。
- O(1),常量时间,不随数据的多少变化
- O(n),线性时间,简单的循环
- O(m*n),嵌套循环
- O(log(n)),二分法,平衡二叉树的查询
- O(nlog(n)),分而治之,快排
- O(2^n),指数级,斐波那契数列
不同的时间复杂度在达到一定数量级的时候将相差很多,所以某些情况我们要想方设法优化算法的效率。我们主要需要关注的是是复杂度的阶。在确认了算法之后,还需要对其进行测试。
最好的并非总是最好的,是否使用最优算法,还需要根据我们遇到的实际情况。有时数据量很小的情况,算法的效率是可以忽略不计的。
第三十三节:重构
在以下这些情况之下,代码需要重构:
①重复的代码
②非正交的设计
③过时的知识
④性能
重构的观点:早重构,常重构
重构即重新设计,重构是一项谨慎、深思熟虑的活动。在重构的同时,不要视图添加新的功能,更要注意重构之前进行良好的测试。
第三十四节:易于测试的代码
软件的测试,我们可以在构建软件的过程中加入可测试性。
单元测试:对软件的各个模块进行测试。
针对合约进行测试:测试模块是否实现了它许诺的功能。
进行单元测试时,自己必须方便找到它,为开发者提供无价的资源:
①说明怎样使用你的软件的所有功能
②用以构建回归测试、以验证未来对代码的任何改动是否正确的一种手段。
使用测试装备。构建一套完善的测试体系,它能够记录测试状态,分析输出结果是否符合预期,以及选择和运行测试。
第三十五节:邪恶的向导
这里的向导指的是那些用于帮助我们构建程序自动生成的代码,通常他们还被称为脚手架。为什么称向导(wizard)是邪恶的呢,这是因为通过工具生成的代码,很容易被我们忽略,在这种情况下你编写的过程更倾向于靠巧合编程。
这里不是抵制向导代码,而是在强调,不要使用你不理解的向导代码。如果使用,一定要清楚它的机制。
开发每天都在使用不完全理解的事物,比如集成电路的工作原理,处理器的中断结构、用于调度的算法、各种系统库的工作机制等。需要注意的是,这些属于底层依赖,他们也是向导,但不是应用本身的一部分,我们可以对这部分有所了解,但他们不属于邪恶的向导。