## [读书笔记]阅读代码的方法与实践（一）

-------------------------------------------------------------

-------------------------------------------------------------

-------------------------------------------------------------

-------------------------------------------------------------

-------------------------------------------------------------
1 导论
-------------------------------------------------------------
1.1 为什么以及如何阅读代码

1.1.1 将代码作为文献

• 编码分割不一致；
• 结构不必要的复杂或难以理解；
• 明显的逻辑错误或疏忽；
• 过度使用不可移植的构造；
• 缺乏维护。

• 从小型的程序开始阅读；
• 编译研究的程序，并运行它们；
• 主动修改代码来检验对代码的理解是否准确。
• 改进它。

1.1.2 以代码为范例
1.1.3 维护
1.1.4 演进

• 定位到感兴趣的代码；
• 单独了解各个特定的部分；
• 推断节选出的代码和其余代码的关系。

1.1.5 重用
1.1.6 审查

Maxims

1. Make it a habit to spend time reading high-quality code that others have written.
2. Read code selectively and with a goal in your mind. Are you trying to learn new patterns, a coding style, a way to satisfy some requirements?
3. Notice and appreciate the code's particular nonfunctional requirements that might give rise to a specific implementation style.
4. When working on existing code, coordinate your efforts with the authors or maintainers to avoid duplication of work or bad feelings.
5. Consider the benefits you receive from open-source software to be a loan; look for ways to repay it by contributing back to the open-source community.
6. In many cases if you want to know "how did they do that?" there's no better way than reading the code.
7. when looking for a bug, examine the code from the problem manifestation to the problem source. Avoid following unrelated paths.
8. Use the debugger, the compiler's warning or symbolic code output, a system call tracer, your database's SQL logging facility, packet dump tools, and windows message spy programs to locate a bug's location.
9. You can successfully modify large well-structured system with only a minimal understanding of their complete functionality.
10. When adding new functionality to a system, your first task is to find the implementation of a similar feature to use as a template for the one you will be implementing.
11. To go from a feature's functional specification to the code implementation, follow the string messages or search the code using keywords.
12. When porting code or modifying interfaces, you can save code-reading effort by directing your attention to the problem areas identified by the compiler.
13. When refactoring, you start with a working system and want to ensure that you will end up with a working one. A suite of pertinent test cases will help you satisfy this obligation.
14. When reading code to search for refactoring opportunities, you can maximize your return on investment by starting from the system's architecture and moving downward, looking at increasing levels of detail.
15. Code reusability is a tempting but elusive concept; limit your expectations and you will not be disappointed.
16. If the code you want to reuse is intractable and difficult to understand and isolate, look at larger granularity packages or different code.
17. while reviewing a software system, keep in mind that it consists of more elements than executable statements. Examine the file and directory structure the build and configuration process, the user interface, and the system's documentation.
18. Use software reviews as a chance to learn, teach, lend a hand, and receive assistance.

-------------------------------------------------------------
2 基本编程元素
-------------------------------------------------------------

What we observe is not nature itself, but nature exposed to our method of questioning. - Werner Heisenberg

2.1 一个完整的程序

if( *++argv && !strcmp(*argv, "-n")) { ... }

#define STREQ(a, b) (*(a) == *(b) && strcmp((a), (b)) == 0)

printf 的返回值被转换成 void 类型。 printf 返回实际打印出的字符数。如果未能写出字符，putchar 将返回 EOF。

• 存储输出的设备可能没有剩余空间；
• 设备商分配给用户的空间可能耗尽；
• 进程对文件的写入可能超出进程或系统文件大小的最大限制；
• 输出设备商可能发生硬件错误；
• 文件描述符或与标准输出关联的流或许不能写入；

if(ferror(stdout))
err(1, "stdout")

2.2 函数和全局变量

• 猜，基于函数名；
• 阅读位于函数开始部分的注释；
• 分析如何使用该函数；
• 阅读函数体的代码；
• 查阅外部的程序文档。

2.3 while 循环、条件和块

2.4 switch 语句

switch 语句用于处理若干离散的整形或字符类型的值。

2.5 for 循环

2.6 break 和 continue 语句

break 语句将程序转移到最内层的循环或 switch 语句之后执行；break 用于提前退出循环。

continue 语句则跳过该语句到循环末尾之间的语句，继续最内层循环的迭代。continue 语句会再次计算 while 条件表达的值，并执行循环。在 for 循环中，该语句将首先计算第三表达式的值，之后是条件表达式。continue 用于在循环体分开处理不同情况的地方；每种情况一般都以 continue 结束，以便进行下一次循环迭代。

for( ; *string && isdigit(*string); string++)
continue;

continue 忽略 switch 语句，break 和 continue 都不会影响到 if 语句的操作。

2.7 字符和布尔型表达式

// 将字符串转换为数字
while (*cp >= '0' && *cp <= '9' )
i= i*10 + *cp++ - '0';

while( '0' <= *cp && *cp <= '9' )

if ( 'a' <= *s && *s <= 'z' )
*s -= ('a' - 'A');

De Morgan 法则

!(a||b) <=> !a && !b

!(a&&b) <=> !a || !b

• 在用 && 运算符（逻辑与）连接起来的表达式序列中，第一个表达式的求值结果如果为 false，则会结束整个表达式的求值，并生成 false 结果；
• 在用 || 运算符（逻辑或）连接起来的表达式序列中，如果第一个表达式求值为 true，则会终止对整个表达式的求值，产生一个 true 的结果。

2.8 goto 语句

goto 容易被误用，创造出“意大利面条（spaghetti）”式的代码：代码的控制流程难以跟踪和断定。

• 用 goto 语句退出程序或函数；
• 用 goto 语句重新执行某一部分代码...使用 goto 语句有时能够更好的传达编码者的意图；
• 在嵌套循环和 switch 语句中，goto 语句可以用来替代 break 和 continue，改变程序的控制流程。

2.9 小范围重构

“代码的可读性和效率在某种程度上不能兼顾”是一个谬论。

op = &( !x ? ( !y ? upleft : ( y == bottom ? lowleft : left )) :
( x == last ? ( !y ? upright : ( y == bottom ? lowright : right )) :
( !y ? upper : ( y == bottom ? lower : normal ))))[w->orientation];

类 if 语句编排（左） 和 类层叠 if-else 语句编排（右）

op =
& (          !y ? ( !x ? upleft  : x != last ? upper   :  upright ) :
y!= bottom ? ( !x ? left    : x != last ? normal  :  right ) :
( !x ? lowleft : x != last ? lower   :  lowright )
)[w->orientation];

• 添加空格
• 利用临时变量将表达式分解成较小的部分
• 用圆括号提高特定运算符的优先次序
• 用好的缩进
• 对变量名称的明智选择

diff 程序 -w 选项，忽略空白差异。

International Obfuscated C Code Contest

2.10 do 循环和整型表达式

if ( ( p = q ) )
q[-1] = '\n' ;

if ( (p = strchr(name, '=')) != NULL ) { p++; }

if ( 0 == serconsole )
serconsinit = 0;

column & 7
// 屏蔽掉 column 变量的一些最高有效位，返回 column 除以 8 后的余数

• 将 a << n 理解为 a * k, k = 2 exp n。
• 将 a >> n 理解为 a / k, k = 2 exp n。

2.11 再论控制结构

• 每次只分析一个控制结构
• 将每个控制结构的表达式看作是它所包含代码的断言

Maxims

1. When examining a program for the first time, main can be a good starting point.
2. Read a cascading if-else if...else sequence as a selection of mutually exclusive choices.
3. Sometimes executing a program can be a more expedient way to understand an aspect of its functionality than reading its source code.
4. When examining a nontrivial program, it is useful to first identify its major constituent parts.
5. Learn local naming conventions and use them to guess what variables and functions do.
6. When modifying code based on guesswork, plan the process that will verify your initial hypotheses. This process can involve checks by the compiler, the introduction of assertions, or the execution of appropriate test cases.
7. Understanding one part of the code can help you understand the rest.
8. Disentangle difficult code by starting with the easy parts.
9. Make it a habit to read the documentation of library elements you encounter; it will enhance both your code-reading and code-writing skills.
10. Code reading involves many alternative strategies: bottom-up and top-down examination, the use of heuristics, and review of comments and external documentation should all be tried as the problem dictates.
11. Loops of the form for(i=0;i<n;i++) execute n times; treat all other forms with caution.
12. Read comparison expressions involving the conjunction of two inequalities with one identical term as a range membership test.
13. You can often understand the meaning of an expression by applying it on sample data.
14. Simplify complicated logical expressions by using De Morgan's rules.
15. When reading a conjunction, you can always assume that the expressions on the left of the expression you are examining are true; when reading a disjunction, you can similarly assume that the expressions on the left of the expression you are examining are false.
16. Reorganize code you control to make it readable.
17. Read expressions using the conditional operator ?: like if code.
18. There is no need to sacrifice code readability for efficiency.
19. while it is true that efficient algorithms and certain optimizations can make the code more complicated and therefore more difficult to follow, this does not mean that making the code compact and unreadable will make it more efficient.
20. Creative code layout can be used to improve code readability.
21. You can improve the readability of expressions using whitespace, temporary variables, and parentheses.
23. You can improve the readability of poorly written code with better indentation and appropriate variable names.
24. When you are examining a program revision history that spans a global reindentation exercise using the diff program, you can often avoid the noise introduced by the changed indentation levels by specifying the -w option to have diff ignore whitespace differences.
25. The body of a do loop is executed at least once.
26. When performing arithmetic, read a&b as a%(b+1) when b+1 = 2 exp n.
27. Read a<<n as a*k, where k = 2 exp n.
28. Read a>>n as a/k, where k = 2 exp n.
29. Examine one control structure at a time, treating its contents as a black box.
30. Treat the controlling expression of each control structure as an assertion for the code it encloses.

posted on 2012-12-05 22:10 zhaorui 阅读(...) 评论(...) 编辑 收藏

• 随笔 - 321
• 文章 - 7
• 评论 - 156
• 引用 - 2