程序员修炼之道3
今天总结的是程序员修炼之道第三部分的内容:
Chap3 基本工具
纯文本的威力:
优点:可读性远大于二进制,且不依赖特定的应用解码,因此不会过时。为了增加纯文本可读性,应该使用能够理解的词语。另外纯文本可由任何应用读取,因此适合作为应用之间的接口,将应用拼接成功能更强大、更丰富的应用,如linux shell的管道。
纯文本的缺点在于占用空间更大和解析更难。
shell的游戏:比起大型workbench,cli更加简洁清晰,可以用短小有力的命令完成强大的功能,并可以将功能拼接。
不过,与gui相比,shell不便之处在于功能非常零散,如果要做一件事情可能需要使用很多工具。这样的拼接确实使得自由度最大化,但当需要专心工作于一个工程、希望有一个集成的工具能清晰地显示自己的工作并可以完成大部分常用功能时便有些不方便。可说各有所长,不过此前的我没怎么做过大型项目,所以cli的不方便之处不太明显。
不过书中提到windows下的linux shell工具cygwin很有趣,打算试一试。
强力编辑:文本是编程的基本原料,所以编辑器很重要。vim、emacs等编辑器都功能强大,适用于各个平台,有gui和cli版本。应该使用一个特定的强大编辑器并精通,这样生产率会大大提高。
在shell下这很有道理,不过在visual studio这样的大型平台上似乎没有必要?
源码控制:由源码控制工具控制版本和分支可以让工作更加方便有序,减少出错的可能。没有源码控制工具非常痛苦,只能将代码一份份备份,而且很容易丢或者忘记顺序。
我所知道的源码控制工具只有git。
调试:作者似乎异常强调debug过程中的心理因素。debug确实容易让人崩溃。
在debug过程中要获得尽可能具体的信息。随意地、泛泛地调试往往是徒劳。获得具体的错误信息并进行bug的复现才能尽可能高效地debug。
有这样一些策略:
多用ide的可视化功能,如watch窗口。这是最直观的。
在程序中加入打印语句看起来非常愚蠢的,不过在某些情况确实管用,甚至比ide的debug功能还要管用。
在某个数据出问题时,可以检查附近内存。
可以采用橡皮鸭策略,即用语言将代码逻辑解释一遍。这个过程中可能会发现隐藏的问题。
在无法找到问题时,可以用二分查找。(但是在条件不是一维的时候如何二分查找?)
文本操纵:用文本操纵语言实现,简单的工具如emacs、vi的内置语言、脚本语言如perl、python(我只会python好凉啊)。
文本操纵可以用来维护数据库schema,如自动生成代码、填充空位;自动生成重复代码;生成测试数据;生成不同语言之间接口;生成特殊格式的文档。
代码生成器:分两种。
被动代码生成器,为了减少编码时手工编码。仅使用一次,然后将生成的代码嵌入代码中。被动代码生成器不需要完全精确,只需要简便,事后由人工校对一遍。
主动代码生成器,是代码的有机部分,每次都被调用,如用来根据数据生成不同语言的接口。主动代码生成器符合DRY的原则,可以减少错误。
代码生成器不一定要很复杂,也不一定要生成代码,可以生成任何文本。
Chap4 注重实效的偏执
在生活中检查每一个可能的问题似乎是一种病态,然而编码时对输入数据、接口的误用以及自己的可能问题保持警惕十分必要。
这一章介绍了许多工具,用来对代码进行约束、检查,以免出现问题而带来更大的问题。
按合约设计:Designed by Contract,俗称DBC。
合约:合约约定了进入一个函数/方法/模块的前条件,即进入模块必须满足的条件,常常指满足模块运行的情景或变量取值范围等等;后条件,即模块需要达成的结果,模块运行之后所能达到的状态;不变量,是对于模块的约束,在调用模块前后始终为真的一些描述。
合约可以是动态的,由“合约代理”在不同模块之间协商出一套合约,但我不知道相应技术。
这是一个很好的想法,其优点在于让代码早崩溃,此时问题更小更单纯。但是在常用的c、cpp中这个想法并没有那么可行。可以通过断言实现一部分功能。
不过通过良好的文档,可以对前条件、后条件、不变量进行描述,从而达到合约的想法,一定程度上获得其优点。
死程序不说谎:一个出现问题的程序可能会因为异常操作造成很大的破坏,所以运行出现问题时,崩溃好于破坏。要利用异常机制。
断言式编程:对于“不可能发生”的情况进行断言,以保证系统的健壮与安全,以免因错误数据或恶意攻击出问题。
然而断言时要避免加入执行代码及其他有副作用的代码,避免“海森堡bug”:不当调试改变了被调试系统的行为。
异常:检查每一个可能的错误,尤其是意料之中的错误是有必要的。
要将异常用于真正异常情况的处理而非模块逻辑的一部分。检查这一点的方式是去掉异常,观察模块能否正常运作。
怎样配平资源:谁申请的资源谁解除该资源,降低代码耦合度。
对于嵌套分配,以与资源分配相反的顺序解除资源分配以免资源的遗弃,在代码不同地方申请资源时以相同顺序申请以免死锁。
在异常处理机制中,一个模块可以通过捕捉异常再抛出从而退出和正常退出两条退出途径。为了遵守对资源利用有始有终并减少重复的原则,有以下思路:对于c++,使用类封装而非指针以保证析构函数会解除相应的资源;对于java,可以使用finally语句解除资源。在更多复杂的情况下,比如树状结构中一个资源指向更多资源,可以用类封装等方式减少出错率。
练习中指出了两个小技巧:c/c++中为了避免释放后的指针被错误地引用,修改不该修改的内存,可以将指针值改为null;java为惰性内存管理,所以使用完某个变量后可以将变量值改为null,从而解除引用,使变量更快被回收。