SoRoMan

人若无名,便可专心练剑。

  博客园 :: 首页 :: 博问 :: 闪存 :: 新随笔 :: 联系 :: 订阅 订阅 :: 管理 ::
读书笔记:Writing Solid Code (1):
http://www.cnblogs.com/soroman/archive/2007/08/06/845465.html
读书笔记:Writing Solid Code (2):
http://www.cnblogs.com/soroman/archive/2007/12/22/1010870.html

Go on writting solid code...

-----------------------------------
Chapter 4 Step through your code
检查你的代码
-----------------------------------


Summary:
The best way to find bugs is to step through all new code in a debugger. By stepping through each instruction with your focus on the data flow, you can quickly detect problems in your expressions and algorithms. Keeping the focus on the data, not the instructions, gives you a second, very different, view of the code. Stepping through code takes time, but not nearly as much as most programmers would expect it to.

概述:
发现bugs最好的方法是在调试器里逐行检查所有新的代码。通过检查每个指令,集中精力注意数据流,你可以很快侦测到语句和算法中的问题。
把注意力放在数据上,而非指令上,能给你另外一种对代码的了解。逐行检查代码是花费时间,但没有绝大部分的程序员认为的需要花费那么多的时间。

4.1.Don't wait until you have a bug to step through your code.
不要等到bug出现时才去检查你的代码。

【注】编写没有bug的代码的最好的方式是什么?就是主动地检查新增加的或者修改后的代码,观察它们的执行情况,验证每个指令是不是按照你的意图做了该做的事情。
黑盒测试的问题是你不知道到盒子里到底发生了什么。检查代码可以说是一种白盒测试,这是程序员自己的责任。下些断点,观察下数据,你就会发现bugs了。

4.2.Step through every code path.
检查每条代码路径。

【注】除了if-else, switch path外,&&, ||, ?: 也包含多个path。还有通常error handling分支的bug很难被发现,因为进入该分支的机会少,需要注意。

4.3.As you step through code, focus on dataflow.
检查代码的时候,注意数据流。

【注】检查代码的真正威力在于跟踪数据流向,由此,能发现很多bugs:
Overflow and underflow bugs
Data conversion bugs
Off-by-one bugs
NULL pointer bugs
Bugs using garbage memory (OxA3 bugs)
Assignment bugs in which you've used =instead of ==
Precedence bugs
Logic bugs

4.4.Source level debuggers can hide execution details. Step through critical code at the instruction level.
源代码级的调试器可能隐藏运行细节。在指令层级上检查重要的代码。

【注】比如你在walk through复杂条件语句(如包含&& or ||),你可以在指令级(或汇编级,汇编语句与机器指令是相互对应的关系)
观察以得出所有分支部分的结果,而这些你在源代码级可能不方便得到。

-----------------------------------
Chapter 5 Candy Machine Interfaces
糖果机接口
-----------------------------------


Summary:
It's not enough that your functions be bug-free; functions must be easy to use without introducing unexpected bugs. If bug rates are to be reduced,
each function needs to have one well-defined purpose, to have explicit single-purpose inputs and outputs, to be readable at the point where it is
called, and ideally to never return an error condition. Functions with these attributes are easy to validate using assertions and debug code, and they minimize the amount of error handling code that must be written.

概述:
只做到你的函数本身没有bug是不够的,函数必须是容易使用,同时不会引入意外的bugs(你自己没有bug还不够,还需要别人使用你也不会出现bug)。如果要减少bug出现率,那么每个函数需要一个定义

好的目的,需要显式的用于单个用途的输入和输出,需要在它被调用的地方有好的可读性,以及理想情况下不会返回出错情况。拥有这些属性的函数是很容易通过Assertions和调试代码验证的,这种函数减少了那些必须的error handling的代码。

Guidelines:

5.1.Make it hard to ignore error conditions.Don 't bury error codes in return values.
不要轻易忽略出错情形。不要在返回值中携带出错代码。

【注】象getchar, malloc这种函数接口就不好:
getchar返回整型,如果出错的话返回-1(EOF),象这种将所有信息放在返回值的接口是不好的。如果一个系统char型的都是无符号数,即不会返回-1,那么getchar失败时也无法知道。
malloc返回指针,如果失败返回0。
改进getchar接口:
如果成功读取一个新的char,则返回TRUE,否则False,char可以从参数返回。

5.2.Always look for, and eliminate flaws in your intefaces.
从你的接口中不停地寻找并减少瑕疵。

【注】下面代码的隐患在于如果realloc失败,那么pbBuf=NULL,pbBuf所指向的原先的内存块就会丢失掉。
1pbBuf = (byte * ) realloc(pbBuf , sizeNew);
2if (pbBuf != NULL)
3 /*use/initialize the larger buffer*/

改进接口:
1flag fResizeMemory(void **ppv, size_t sizeNew)
2{
3    byte **ppb = (byte **)ppv;
4    byte *pbNew;
5    pbNew = (byte *) realloc (*ppb , sizeNew);
6    i f (pbNew != NULL)
7        *ppb = pbNew;
8    return (pbNew != NULL);
9}

这样即使失败,原先的指针不会被破坏。

5.3.Don't write multipurpose functions. Write separate functions to allow stronger argument validation.
不要写含多个目的的函数。分开(目的)写函数以允许更强的参数检查。

5.4.Don't be wishy-washy. Define explicit function arguments.
不要不正式。定义显式的函数参数。

【注】参考下面的函数:
1char *CopySubStr(char *strTo, char *strFrom, size_t size)
2{
3    char* str Start = strTo:
4    while (size-- > 0)
5     *strTo++ = *strFrom++;   
6    *strTo = '\0' ;
7    return (strstart) ;
8}

函数中没有验证参数size的大小,对于size=0,函数可以跳出,但是对于size大于strFrom指向的长度的时候,bug就发生了,而且调用者需要花费力气找出bug。
有时候允许一个无意义的参数,比如说size=0,是值得的,因为可以减少调用者在外部的测试。但是,如果调用者传入0 size的机会是很少的或者从不,那么就不要handle 0 size,因为这可能隐藏bug,所以使用assert来保证size != 0从而发现bug。这还是防御式编程与隐藏bug的矛盾。参考在fFreeMemory中对NULL指针的处理(Assert)。

5.5.Write functions that, given valid inputs, cannot fail.
编写只要传入合法的输入,不会失败的函数。

【注】象tolower函数,很多人喜欢将它设计成
1char to1ower(char ch)
2{
3    if(ch >= 'A' && ch <= 'Z' )
4        return (ch + 'a' - 'A' ) ;
5    else
6        return (-1);
7}

这同样是一个返回值包含过多信息的例子。其弊病参见前面所述。你当然可以象前面那样改进。但是,用户始终需要作runtime check。如果这样改进:
1char tolower(char ch)
2{
3    ASSERT(ch >= 'A' && ch <= 'Z' ) ;
4    return (ch + 'a' - 'A' ) ;
5}

这样,用户永远不需要作runtime error check,减少了调用者的代码量以及出错的可能(在debug版已经被assert了),这其实就是debug版的作用。

5.6.Make the code intelligible at the point of call. Avoid boolean arguments.
使得(调用函数的)代码是容易理解的。避免布尔参数。

【注】布尔参数不容易扩展。

5.7.Write comments that emphasize potential hazards.
写些注释强调潜在的风险。


posted on 2007-12-25 14:57  SoRoMan  阅读(1593)  评论(0编辑  收藏  举报
free web counters
Vistaprint Discount Codes