15、死程序不说谎
我们很容易掉进“它不可能发生”这样一种心理状态。
【很多时候我们在项目中遇到问题或者bug时,第一反应就是这不可能。但事实就是错误已经发生了,这说明非常、非常糟糕的事情已经发生,我们不能纠结“事情不可能”这个问题上,而是要弄清楚事情为什么会发生,思考解决方案,并编码调试,最终把这种“不可能”消除】
要崩溃,不要破坏
尽早检测问题的好处就是你可以更早的崩溃,Java和C++采用异常机制来捕获意料之外的事情。如果没有异常机制,或者库不抛出异常,那就要确保自己对错误进行了处理,在C语言中,可以利用宏来完成这一功能。
# define CHECK(LINE, EXPECTED) \ { \
int rc = LINE; \ if (rc != EXPECTED) \
ut_abort(__FILE__,__LINE, $LINE, rc, EXPECTED); \ }
当你的代码发现某件不可能的事情已经发生时,你的程序已经没有存活能力。它做任何事情都会变得可疑,所以要尽快的终止它。死程序带来的危害通常比病程序小得多。
16、断言式编程
如果它不可能发生,用断言确保它不会发生。断言可能在编译的时候被关闭,决不要把必须执行的代码放在assert中。
不要用断言代替真正的错误处理。
printf("Enter 'Y' or ‘N': “);
ch = getchar();
assert((ch == 'Y') || (ch == ’N‘));
这种做法不是好idea,assert会调用exit退出整个程序,这可能不是你真正处理错误的意图。
关于断言的常见误解:
【断言给代码增加了一些开销。因为它们检查决不应该发生的事情,所以只会由代码中的bug触发。一旦代码经过了测试并发布出去,它们就不再需要存在,应该被关闭,以使代码运行得更快。断言是一种调试设施。】
这里有两个明显错误的假设:首先,假定测试能过找到所有的bug;其次,你的程序运行在一个非常”安全“的世界。第一条防线是检查任何可能发生的错误,第二条防线是使用断言设法检查你疏漏的错误。
不应该使用断言来控制程序的逻辑,例如:
void func(int a)
{
if(a != 1)
{
.........
}
else
{
assert(false);
}
}
这种情况类似于利用断言来处理错误。
你们认为下面”不可能的事情”中,哪些可能发生
1) 一个月少于28天
2) 内角和不等于180度的三角形
3) 没有60秒的一分钟
17、何时使用异常
检查每一个可能的错误(特别是意料之外的错误)是一种良好的习惯,但是对错误处理过头了,可能会把你引向相当丑陋的代码,你程序的正常逻辑可能会被错误处理完全遮蔽,随处可见的只有错误处理。
18、怎么配平资源
对于资源分配和解除分配的处理要有始有终,这意味着分配某项资源的例程或对象应该负责解除该资源的分配。
【嵌套的分配】
对于不止一个资源的例程或者对象:以与分配资源相反的次序解除资源的分配;在代码的不同地方分配同一组资源,总是以相同的次序分配它们。这将降低发生死锁的可能性。
purify(www.rational.com)和Insure++(www.parasoft.com)是两种检查运行中的程序内存泄露的工具
19、解耦与得墨忒耳法则
把你的代码组织成最小组织单元(模块),并限制它们之间的交互!
20、元程序设计
我们想要让我们的系统变得高度可配置。
元数据是任何对应用进行描述的数据【应用该怎么样运行、它应该使用什么资源,等等】,元数据在运行时、而不是在编译时被访问和使用。
为一般情况编程程序,把具体情况放在别处。【将抽象放进代码,细节放进元数据】
建议以纯文本方式表示配置元数据,如果在应用启动的时候加载配置,那如果需要改变配置的话,这会迫使你重新启动应用程序。更为灵活的方法是编写能在运行时重新加载其配置的程序。
21、时间耦合
时间有两个方面对我们很重要:并发和次序!
编写线性代码,我们很容易做出一些假定,把我们引向不整洁的编程!
对并发和时序依赖进行思考还能够引导你设计更加整洁的接口
22、它只是视图
不要把程序写成一个大块,而应该“分而治之”,把程序划分为模块。每个模块都有其自身的责任!
23、靠巧合编程
作为开发者,我们时刻工作在雷区,我们应该避免靠巧合编程,而要深思熟虑地编程。
怎样深思熟虑地编程
(1)总是意识到你在做什么。
(2)不要盲目地编程。试图构建你不熟悉的应用,或是使用你不熟悉的技术,就是希望自己被巧合误导。
(3)按照计划行事。
(4)依靠可靠的事物。不要依靠巧合或假定。
(5)为你的假定建立文档。
(6)不要只是测试你的代码,还要测试你的假定。
(7)为你的工作划分优先级。
(8)不要做立时的奴隶。不要让已有的代码支配将来的代码。
作为一个新手,我现在编写代码基本上都是靠已有的代码在支撑,这是一个学习的过程,应该尽量避免这样做,如果新手不这样做的话,可能就是在使用自己不熟悉的技术了。
24、算法速率
简单循环、嵌套循环、二分法、分而治之和组合的时间复杂度。
25、重构
随着程序的演化,我们有必要重新思考早先的决策,并重写部分代码。代码需要演化,它不是静态的事物。
重写、重做和重新架构代码合起来称为重构(refactoring)。以前设计的东西都可以根据新的事实、更深的理解、变化的需求等等,重新进行设计。
【应该在何时进行重构】:重复、非正交的设计、过时的知识、改善性能。
时间压力常常被用作不进行重构的借口。追踪需要重构的事物,如果你不能立刻重构某样东西,一定要把它列入计划,确保收到影响的代码最少。
怎么进行良好的重构:
(1)不要试图在重构的时候增加新功能;
(2)在开始重构之前,确保你拥有良好的测试;
(3)采取短小、深思熟虑的步骤。如果你使你的步骤保持短小,并在每一个步骤之后进行代码测试,你将能够避免长时间的测试。
26、邪恶的向导
你在使用向导,却不理解它制作出的所有代码,你就无法控制你自己的应用。不要使用你不理解的向导代码。
向导与库调用或标准的操作系统服务不一样,向导生成的代码最终会变成应用的完整组成部分,并没有被分解出来。
27、需求之坑
开发必须解决商业问题,而不只是满足陈述的需求。用文档记载需求背后的原因将在每天进行实现决策时给你的团队带来无价的信息。
28、解开不可能解开的谜题
在面对棘手的问题时,列出所有在你面前的可能途径。不要排除任何东西,不管它听起来有多无用或者愚蠢。逐一检查列表中的每一项,并解释为何不能采用某个特定的途径。对你的约束进行分类,并划分优先级。
有时候你会发现,当你在处理一个问题的时候似乎比你想象的要难很多。感觉自己好像是走错了路,认为一定有比这更容易的方法!这时正是你退回一步的时候,问问自己以下问题:
(1)是否真的有更加容易的方法?
(2)你是在设法解决真正的问题,还是被外围的技术问题转移了注意力?
(3)这件事为什么是一个问题?
(4)是什么使它如此难以解决?
(5)它必须以这种方式完成吗?
(6)它真的必须完成吗?
很多时候,对需求的重新诠释能让整个问题全部消失。
29、等你准备好
当你面对一件任务时,如果你反复感觉到疑虑,或是体验到某种勉强,你就要注意它。你可能无法马上指出问题的所在,但你需要好好静下来理清思路,找出问题的来源。
30、规范陷阱
对有些事情“做”胜于“描述”,要知道,随着规范越来越详细,你得到的回报会递减,甚至会是负回报。
31、注重实效的团队
(1)不要留破窗户:团队作为一个整体,不应该容忍破窗户——-那些小小的、无人修正的不完美。
(2)时刻注意周围的变化:确保每个人都主动地监视环境的变化,对新需求进行持续的度量。
(3)交流:团队中的开发者必须相互交谈。创立品牌,启动项目时,给项目区一个响亮的名字,最好是不寻常的某种东西,这样很容易让人记住。
(4)不要重复你自己:重复会造成工作的浪费,并且可能会带来维护的噩梦。
(5)正交性:认为项目的各种活动——-分析、设计、编码、测试———会孤立地发生,这是一个错误。它们只是看待同一问题的不同方式。可以按功能划分团队,这种分组方式能够极大地减少各个开发者之间的相互影响。
(6)自动化:使团队所做的每一件事情自动化。
32、无情的测试
早测试、常测试、自动测试。bug被发现得越早,进行修补的成本就越低。好的项目拥有测试代码可能比产品代码还要多。要通过全部测试,编码才算完成。
单元测试——-集成测试———验证和校验——–资源耗尽、错误及恢复——–性能测试—–可用性测试
33、全部都是写
代码中的注释:把项目的那些难以描述、容易忘记,却又不能记载在别的任何地方的东西记载下来。
大家都喜欢看到简单的模块级头注释、关于重要数据与类型说明的注释、以及给每一个类和每一个方法所加的简单头注释、用以描述函数的用法和任何不明了的事情。
34、极大的欲望
项目的成功是有它在多大程度上满足了用户的期望来衡量的。不符合用户预期的项目注定是失败的,不管交付的产品在绝对的意义上有多好。