ImageVerifierCode 换一换
格式:DOCX , 页数:24 ,大小:31.43KB ,
资源ID:30489794      下载积分:3 金币
快捷下载
登录下载
邮箱/手机:
温馨提示:
快捷下载时,用户名和密码都是您填写的邮箱或者手机号,方便查询和重复下载(系统自动生成)。 如填写123,账号就是123,密码也是123。
特别说明:
请自助下载,系统不会自动发送文件的哦; 如果您已付费,想二次下载,请登录后访问:我的下载记录
支付方式: 支付宝    微信支付   
验证码:   换一换

加入VIP,免费下载
 

温馨提示:由于个人手机设置不同,如果发现不能下载,请复制以下地址【https://www.bdocx.com/down/30489794.html】到电脑端继续下载(重复下载不扣费)。

已注册用户请登录:
账号:
密码:
验证码:   换一换
  忘记密码?
三方登录: 微信登录   QQ登录  

下载须知

1: 本站所有资源如无特殊说明,都需要本地电脑安装OFFICE2007和PDF阅读器。
2: 试题试卷类文档,如果标题没有明确说明有答案则都视为没有答案,请知晓。
3: 文件的所有权益归上传用户所有。
4. 未经权益所有人同意不得将文件中的内容挪作商业或盈利用途。
5. 本站仅提供交流平台,并不能对任何下载内容负责。
6. 下载文件中如有侵权或不适当内容,请与我们联系,我们立即纠正。
7. 本站不保证下载资源的准确性、安全性和完整性, 同时也不承担用户因使用这些下载资源对自己和他人造成任何形式的伤害或损失。

版权提示 | 免责声明

本文(Writing Clean Code读书笔记.docx)为本站会员(b****8)主动上传,冰豆网仅提供信息存储空间,仅对用户上传内容的表现方式做保护处理,对上载内容本身不做任何修改或编辑。 若此文所含内容侵犯了您的版权或隐私,请立即通知冰豆网(发送邮件至service@bdocx.com或直接QQ联系客服),我们立即给予删除!

Writing Clean Code读书笔记.docx

1、Writing Clean Code读书笔记【转载】Writing clean code读书笔记 转载自:写在前面的话:这两天看了Writing Clean Code,很受启发,感觉值得再读,于是整理了一点笔记,作为checklist,以备速查。原书共8章,每章都举一些例子,指出不足,再用通用的规则改写,每章结束时会总结一下要点,其中覆盖了比较重要的规则。附录A是作者整理的编码检查表。本笔记前8章和原书前8章对应,列出了所有的规则,对比较特别或者比较难理解的规则还附上了书中的例子,偶尔加一两句个人的想法。第9章是原书各章末尾要点的汇总。第10章是原书的编码检查表。本笔记只作为原书的一个速查手册

2、,详细的内容请看原书。中译本:编程精粹 Microsoft 编写优质无错C 程序秘诀Steve Maguire 著,姜静波 佟金荣 译,麦中凡 校,电子工业出版社英文版:Writing Clean Code Microsoft Techniques for Developing Bug-free C ProgramsSteve maguire, Microsoft Press英文版原名:Writing Solid Code Microsoft Techniques for Developing Bug-free C ProgramsSteve maguire, Microsoft Press1

3、 假想的编译程序1.1 使用编译程序所有的可选警告设施1.2 使用lint 来查出编译程序漏掉的错误1.3 如果有单元测试,就进行单元测试1.4 TipsC 的预处理程序也可能引起某些意想不到的结果。例如,宏UINT_MAX 定义在limit.h中,但假如在程序中忘了include 这个头文件,下面的伪指令就会无声无息地失败,因为预处理程序会把预定义的UINT_MAX 替换成0:#if UINT_MAX 65535u#endif怎样使预处理程序报告出这一错误?2 构造自己的断言2.1 既要维护程序的交付版本,又要维护程序的调试版本少用预处理程序,那样会喧宾夺主,尝试用断言2.2 断言是进行调试

4、检查的简单方法。要使用断言捕捉不应该发生的非法情况。不要混淆非法情况与错误情况之间的区别,后者是在最终产品中必须处理的。这是断言和错误处理的区别2.3 要使用断言对函数参数进行确认2.4 要从程序中删去无定义的特性或者在程序中使用断言来检查出无定义特性的非法使用这个对C/C+很适用2.5 不要浪费别人的时间 详细说明不清楚的断言森林中只标有“危险”,而没指出具体是什么危险的指示牌将会被忽略。2.6 断言不是用来检查错误的当程序员刚开始使用断言时,有时会错误地利用断言去检查真正地错误,而不去检查非法的况。看看在下面的函数strdup 中的两个断言:char* strdup(char* str)c

5、har* strNew;ASSERT(str != NULL);strNew = (char*)malloc(strlen(str)+1);ASSERT(strNew != NULL);strcpy(strNew, str);return(strNew);第一个断言的用法是正确的,因为它被用来检查在该程序正常工作时绝不应该发生的非法情况。第二个断言的用法相当不同,它所测试的是错误情况,是在其最终产品中肯定会出现并且必须对其进行处理的错误情况。2.7 用断言消除所做的隐式假定,或者利用断言检查其正确性Eg. 对于和机器相关的内存填充程序,不必也无法将其写成可移植的。可以用条件编译。但其中应该对某

6、种机器的隐含假设做检查。2.8 利用断言来检查不可能发生的情况压缩程序的例子:正常情况和特殊情况,重复次数=4或者就等于12.9 在进行防错性程序设计时,不要隐瞒错误2.10 要利用不同的算法对程序的结果进行确认2.11 不要等待错误发生,要使用初始检查程序2.12 Tips不要把真正需要执行的语句放在断言里3 为子系统设防3.1 要消除随机特性 使错误可再现3.2 冲掉无用的信息,以免被错误地使用分配内存时填充上非法值:eg. 68000 用0xA3,Intel X86系列用0xCC释放内存时立刻填上非法值引申:这个和代码大全中讲的进攻式编程观点类似3.3 如果某件事甚少发生的话,设法使其经

7、常发生eg. 让realloc函数中移动内存块这种比较少发生的事情经常发生自己包装一个relloc。3.4 保存调试信息到日志,以便进行更强的错误检查这里的日志信息相当于一个簿记功能的信息,写到内存链表中。p168代码有错:if( pbiPrev = NULL )pbiHead = pbi-pbiHead;3.5 建立详尽的子系统检查并且经常地进行这些检查调试检查eg。利用簿记和是否被引用的标志,检查是否有内存泄漏和悬挂指针3.6 仔细设计程序的测试代码,任何选择都应该经过考虑eg. 先后顺序是有讲究的:先看500元的套装,再看80元的毛衣3.7 努力做到透明的一致性检查不要影响代码的使用者的

8、使用方式3.8 不要把对交付版本的约束应用到相应的调试版本上要用大小和速度来换取错误检查能力3.9 每一个设计都要考虑如何确认正确性如果可能的话,把测试代码放到所编写的子系统中,而不要把它放到所编写子系统的外层。不要等到进行了系统编码时,才考虑其确认方法。在子系统设计的每一步,都要考虑“如何对这一实现进行详尽的确认”这一问题。引申:回忆高中时检查结果:如果是解方程,则代入数值验算就可;如果是计算题,换一个方法再算一遍。总之,要有方法确认其正确性。3.10 “调试代码时附加了额外信息的代码,而不是不同的代码”加调试代码时要保证产品代码一定也要运行,这样才能测试到真正的产品代码。3.11 在自己包

9、装的内存函数中加上允许注入错误的机制。eg. 定义一个failure结构,在NewMemory中测试这个结构,如果为真,则返回false,表示内存分配失败。这样,开发者和测试者都能利用这个机制,人为的注入错误。4 对程序进行逐条跟踪4.1 代码中不会自己生出错误来,错误是程序员编写新代码或者修改现有代码的产物。如果你想发现代码中的错误,没有哪个办法比在对代码进行编译时对其进行逐条跟踪更好。这个如果用个“完美的”编译器就更好。4.2 不要等到出了错误再对程序进行逐条的跟踪而是把对程序逐条跟踪看成是一个必要的过程。这可以保证程序按你预想的方式工作。引申:可以和代码走查结合在一起。或者先进行代码走查

10、,再逐条跟踪,共两遍检查代码。4.3 对每一条代码路径进行逐条的跟踪注意覆盖率问题:语句覆盖or分支覆盖4.4 当对代码进行逐条跟踪时,要密切注视数据流这样有助于发现以下错误:上溢和下溢错误;数据转换错误;差1 错误;NULL 指针错误;使用废料内存单元错误(0xA3 类错误);用 = 代替 = 的赋值错误;运算优先级错误;逻辑错误。4.5 源级调试程序可能会隐瞒执行的细节,对关键部分的代码要进行汇编指令级的逐条跟综对条件语句的各个子条件,不要一次越过,而要看每个子条件的值。5 糖果机界面作者以糖果机的糟糕的界面设计导致人犯错讲起,阐述界面设计应该指导程序员少犯错误。5.1 要使用户不容易忽视

11、错误情况,不要在正常地返回值中隐藏错误代码作者以getchar函数为例:这个函数返回一个char或者是-1,由此要求使用getchar的程序员必须用int来接收getchar的返回值,但肯定会有很多程序员忘记这一点,由此可能会引发难以捕捉的错误。作者设计了另一个函数界面来处理这种情况:int fGetChar(char*),返回值存入char*所指位置,而int返回flag,为true表示正确。这样,由于划分了正常的返回值和错误代码,避免了getchar的返回值要用int接收的问题。5.2 要不遗余力地寻找并消除函数界面中的缺陷Eg. 下述代码隐含着一个错误pbBuf = (byte*)rea

12、lloc( pbBuf, sizeNew );if( pbBuf != NULL )使用初始化这个更大的缓冲区如果realloc分配内存时失败,返回NULL,则pbBuf为NULL,它原来指向的内存将会丢失。如果界面是flag fResizeMemory( void* ppv, size _t sizeNew )则好得多5.3 不要编写多种功能集于一身的函数,为了对参数进行更强的确认,要编写功能单一的函数以realloc为例,它接受的指针为NULL但size大于0时相当于malloc,指针不为NULL但size为0时相当于free。这样realloc就混杂了malloc和free的功能,极其容

13、易出错。5.4 不要模棱两可,要明确地定义函数的参数像realloc那样灵活的参数不一定很好,要考虑程序员给出这样的输入参数可能是出于什么原因,如果没有充分的理由,用断言来禁止太灵活的输入能减少错误。5.5 返回值与错误处理:编写函数使其在给定有效的输入情况下不会失败返回错误码不是唯一的处理错误的方式。Eg. Tolower函数在遇到输入是小写字母时,应该怎么办?如果返回-1,那么将遇到和getchar相同的问题:程序员要用int来存储tolower的返回值。此时,tolower返回原字符也许是一个更好的方式。5.6 使程序在调用点明了易懂:要避免布尔参数通过检查调用代码,检验界面设计的合理性

14、。Eg. 以下两个函数声明会导致调用方式的不同:void UnsignedToStr(unsigned u, char *strResult, flag fDecimal);void UnsignedToStr(unsigned u, char* str, unsigned base);前者的调用方式是:UnsignedToStr(u, str, TRUE);UnsignedToStr(u, str, FALSE);这显然不好。而后者是UnsignedToStr(u, str, BASE10)则好的多。5.7 编写注解突出可能的异常情况用注释写出常见的错误用法和正确用法的例子。5.8 小结本章

15、先给出一个界面不好的例子,再给出一般原则:要不遗余力的检查界面的合理性。然后讲功能要单一,输入要有限制,输出的正常返回值要与错误码分开,用调用方式检查界面,用注释来指出异常情况。6 风险事业6.1 使用有严格定义的数据类型可移植类型最值得注意之处是:它们只考虑了三种最通用的数制:壹的补码、贰的补码和有符号的数值。Char只有0127吗是可移植的Unsigned char 是0255,但signed char是-127127 (没有-128吗)是可移植的6.2 经常反问:“这个变量表达式会上溢或下溢吗?”Eg. 以下代码会导致无穷循环,因为ch会上溢为0,导致不可能大于UCHAR_MAX。uns

16、igned char ch;/* 首先将每个字符置为它自己 */for (ch=0; ch = 0)NULL;6.3 尽可能精确地实现设计,近似地实现设计就可能出错6.4 一个“任务”应只实现一次(Implement the task just once).一个原则:Strive to make everyfunction perform its task exactlyone timestatic window * pwndRootChildren = NULL;void AddChild( window * pwndParent, window * pwndNewBorn )/* 新窗口可

17、能只有子窗口 */ASSERT( pwndNewBorn-pwndSibling = NULL );if( pwndParent = NULL )/* 将窗口加入到顶层根列表 */pwndNewBorn-pwndSibling = pwndRootChildren;pwndRootChildren = pwndNewBorn;else/* 如果是父母的第一个孩子,那么开始一个链,* 否则加到现存兄弟链的末尾处*/if( pwndParent - pwndChild = NULL )pwndParent - pwndChild = pwndNewBorn;elsewindow *pwnd = p

18、wndParent - pwndChild;while( pwnd - pwndSibling != NULL)pwnd = pwnd - pwndSibling;pwnd - pwndSibling = pwndNewBorn;.假如AddChild 是一个任务,要在现有窗口中增加子窗口,而上面的代码具有三个单独的插入过程。常识告诉我们如果有三段代码而不是一段代码来完成一个任务,则很可能有错。这往往意味着这个实现中有例外情况。其最终的改进见下一节。6.5 避免无关紧要地if 语句以指针为中心的树的构建,可以不必为特殊情况编写代码:void AddChild(window* pwndParen

19、t, window* pwndNewBorn )window *ppwindNext;/* 新窗口可能没有兄弟窗口 ? */ASSERT( pwndNewBorn - pwndSibling = NULL );/* 使用以指针为中心的算法* 设置ppwndNext 指向pwndParent - pwndChild* 因为pwndParent - pwndChild 是链中第一个“下一个兄弟指针”一个“任务”应只实现一次*/ppwndNext = &pwndParent-pwndChild;while( *ppwndNext != NULL )ppwndNext = &( *ppwndNext

20、)-pwndSibling;*ppwndNext = pwndNewBorn;由于没有无关的if语句,使所有的程序都会经过同样的路径,因此这段代码就会被测试的很充分。6.6 避免使用嵌套的“?:“运算符重新整理思路,甚至用查表法,都能简化过程。6.7 每种特殊情况只能处理一次不要让处理同一个特殊情况的代码散布在多个地方6.8 避免使用有风险的语言惯用语这里举了好几个例子。Eg. pchEnd = pch + size;while( pch = 0) 和while(size- 0),前者有风险,后者却没有。6.9 不能毫无必要地将不同类型地操作符混合使用,如果必须将不同类型地操作符混合使用,就用

21、括号把它们隔离开来6.10 避免调用返回错误的函数(Avoid calling functions that return errors)这样,就不会错误地处理或漏掉由其它人设计的函数所返回的错误条件。如果自始至终程序反复处理同样的错误条件,就将错误处理部分独立出来。Eg. 单独的错误处理子程序。有时更好的方法是使错误根本不会发生。Eg. 窗口的rename函数可能要realloc,从而导致失败,但通过分配超额的内存空间(都取名字长度的最大值),则这个使错误不会出现,从而避免了错误处理的代码。7 编码中的假象7.1 只引用属于你自己的存储空间7.2 不能引用已释放的存储区7.3 只有系统才能拥

22、有空闲的存储区,程序员不能拥有决不要使用free以后的内存7.4 不要把输出内存用作工作区缓存Dont use output memory as workspace buffers.7.5 不要利用静态(或全局)量存储区传递数据7.6 不要写寄生函数依赖于别的函数内部处理的函数叫寄生函数,被依赖的叫宿主函数。宿主函数的实现一旦改变,寄生函数就不能正常工作。Eg. ,FIG(FORTH Interest Group)公布的FORTH-77中有CMOVE, FILL等函数。如果用CMOVE实现FILL,则FILL就是寄生函数。如果CMOVE实现为一次拷贝4个字节,则FILL就失败。/* CMOVE

23、 用头到头的移动来转移存储 */void CMOVE (byte *pbFrom,byte *pbTo,size_t size)while(size- 0 )*pbTo+ = *pbFrom+;/* FILL 填充某一存储域 */void FILL (byte *pb,size_t size,byte b)if(size0)*pb = b;CMOVE(pb,pb+1,size-1);7.7 不要滥用程序设计语言用一把螺丝刀来播开油漆罐的盖子,然后又用这把螺丝刀来搅拌油漆这并不是正确的做法,之所以这样做是因为当时这样很方便,而且能够解决问题。程序设计语言也是如此。Eg. 不要将比较的结果作为计算

24、表达式的一部分另外标准也会变。Eg. Forth-77和Forth-83中的布尔值定义7.8 紧凑的C 代码并不能保证得到高效的机器代码我的观点是:如果你总是使用稀奇古怪的表达式,以便把C 代码尽量写在源代码的一行上,从而达到最好的瑜伽状态的话,你很可能患有可怕的“一行清”(one-line-itis)疾病(也称为程序设计语言综合症)7.9 为一般水平的程序员编写代码8 剩下来的就是态度问题8.1 错误几乎不会“消失”错误消失有三个原因:一是错误报告不对;二是错误已被别的程序员改正了;三是这个错误依然存在但没有表现出来。8.2 马上修改错误,不要推迟到最后 不要通过把改正错误移置产品开发周期的

25、最后阶段来节省时间。修改一年前写的代码比修改几天前写的代码更难,实际上这是浪费时间。 “一次性”地修改错误会带来许多问题:早期发现的错误难以重现。 错误是一种负反馈,程序开发倒是快了,却使程序员疏于检查。如果规定只有把错误全部改正之后才能增加新特征的话,那么在整个产品开发期间都可以避免程序员的疏漏,他们将忙于修改错误。反之,如果允许程序员略过错误,那就使管理失控。 若把错误数保持在近乎于0 的数量上,就可以很容易地预言产品的完成时间。只需要估算一下完成 32 个特征所需的时间,而不需要估算完成32 个特征加上改正1742个错误所需的时间。更好的是,你总能处于可随时交出已开发特征的有利地位。8.

26、3 修改错误要治本,不要治标8.4 除非关系产品的成败,否则不要整理代码整理代码的问题在于程序员总不把改进的代码作为新代码处理,导致测试不够8.5 不要实现没有战略意义的特征8.6 不设自由特征对于程序员来说,增加自由特征可能不费事,但是对于特征来讲,它不仅仅增多了代码,还必须有人为该特征写又档,还必须有人来测试它。不要忘记还必须有人来修改该特征可能出现的错误。8.7 不允许没有必要的灵活性Eg. realloc的参数8.8 在找到正确的解法之前,不要一味地“试”,要花时间寻求正确的解8.9 尽量编写和测试小块代码。即使测试代码会影响进度,也要坚持测试代码8.10 测试代码的责任不在测试员身上

27、,而是程序员自己的责任开发人员和测试人员分别从内外开始测试,所以不是重复劳动。8.11 不要责怪测试员发现了你的错误8.12 建立自己优先级列表并坚持之约克的优先级列表吉尔的优先级列表正确性正确性全局效率可测试性大小全局效率局部效率可维护性/明晰性个人方便性一致性可维护性/明晰性大小个人表达方式局部效率可测试性个人表达方式一致性个人方便性8.13 你必须养成经常询问怎样编写代码的习惯。本书就是长期坚持询问一些简单问题所得的结果。 我怎样才能自动检测出错误? 我怎样才能防止错误? 这种想法和习惯是帮助我编写无错代码呢还是妨碍了我编写无错代码?9 本书各章要点汇总书中每章结束时都小结了本章要点,这

28、里汇总如下:9.1 假想的编译程序 消除程序错误的最好方法是尽可能早、尽可能容易地发现错误,要寻求费力最小的自动查错方法。 努力减少程序员查错所需的技巧。可以选择的编译程序或lint 警告设施并不要求程序员要有什么查错的技巧。在另一个极端,高级的编码方法虽然可以查出或减少错误,但它们也要求程序员要有较多的技巧,因为程序员必须学习这些高级的编码方法。9.2 自己设计并使用断言 要同时维护交付和调试两个版本。封装交付的版本,应尽可能地使用调试版本进行自动查错。 断言是进行调试检查的简单方法。要使用断言捕捉不应该发生的非法情况。不要混淆非法情况与错误情况之间的区别,后者是在最终产品中必须处理的。 使

29、用断言对函数的参数进行确认,并且在程序员使用了无定义的特性时向程序员报警。函数定义得越严格,确认其参数就越容易。 在编写函数时,要进行反复的考查,并且自问:“我打算做哪些假定?”一旦确定了相应的假定,就要使用断言对所做的假定进行检验,或者重新编写代码去掉相应的假定。另外,还要问:“这个程序中最可能出错的是什么,怎样才能自动地查出相应的错误?”努力编写出能够尽早查出错误的测试程序。 一般教科书都鼓励程序员进行防错性程序设计,但要记住这种编码风格会隐瞒错误。当进行防错性编码时如果“不可能发生”的情况确实发生了,要使用断言进行报警。9.3 为子系统设防 考查所编写的子系统,问自己:“在什么样的情况下,程序员在使用这些子系统时会犯错误。”在子系统中加上相应的断言和确认检查代码,以捕捉难于发现的错误和常见的错误。 如果不能使错误不断重现,就无法排除它们。找出程序中可能引起随机行为的因素,并将它们从程序的调试版本中清除。把目前尚“无定义”的内存单元置成了某个常量值,就可能产生这种错误。在这种情况下,如果程序在该单元被正确地定义为某个值之前引用了它的内容,那么每次执行这部分错误的代码,都会得到同样的错误结果。 如果所编写的子系统释放内存(或者其它的资源),并因此产生了“ 无用信息”,那么要把它搅乱,使它真的像无用信息。否则,这些被释放了的数据就有

copyright@ 2008-2022 冰豆网网站版权所有

经营许可证编号:鄂ICP备2022015515号-1