代码大全阅读笔记(待续)


Author: yifei / Created: April 4, 2018, 1:29 p.m. / Modified: April 4, 2018, 9:30 p.m. / Edit

第一章 欢迎进入软件构建的世界

没啥可记录的

第二章 用隐喻更充分地理解软件开发

软件开发最大的挑战还是将问题概念化,编程中的很多错误都是概念性的错误。

应该先做出一个尽可能简单、但能运行的版本。一点点在其上附上肌肉和皮肤,一次增加一部分代码,直到得到一个可以完全工作的系统。

第三章 三思而后行:前期准备

第五章

实际开发中的问题和学校作业的区别是,实际开发中的问题可能经常在你提交代码之后需求已经变了,需要再次开发

第十章

隐式变量声明

隐式变量声明是一项非常危险的行动。

BAD

account_no = xxx
account_number = 123456 // 记错变量名了
target_account = account_no 

GOOD

Python 中无法强制声明变量,可以使用 pylint 等工具

可以通过以下几个方面改善

  1. 关闭隐式变量声明
  2. 遵循某种命名规则
  3. 使用lint等工具检查变量名

初始化

在需要第一次使用变量的地方声明变量,在变量声明的时候初始化,并且尽量声明为const或者final。

尤其要注意 i,j 这些变量在再次使用的时候有没有再次初始化。

对于 C++ 等语言,要在构造函数中初始化所有变量,并且在析构函数中释放内存。

作用域

介于统一变量多个引用点之间的代码称为“攻击窗口”。跨度指的是两次访问同一个变量的间隔,生存时间指的是变量的第一次使用和最后一次使用的间隔。应该尽量减少变量的存活时间和跨度,比如说全局变量的跨度和生存时间都很长,所以他们不好。

持续性

在程序中加入断言来验证关键变量的合理取值 在需要删除变量的时候赋值为不合法数值,比如 null

绑定时间

越晚越好

  1. 编码时 titleBar.color = 0xfff
  2. 编译时 titleBar.color = WHITE
  3. 加载时,也就是程序初始化的时候 titleBar.color =

第十四章

语句一般也分为有副作用的语句和没有副作用的语句,如果前后两个语句都在更新同一个变量,那这样是不好的。至少我们要减少有副作用的语句。这样可以使代码的依赖变得更加明显。代码是按照顺序执行的,对于后一句依赖前一句的代码

比如:

BAD:

ComputeMarketingExpense()
ComputeSaleExpense()
ComouteTravelExpense()
ComputePersonalExpense()
DispalyExpenseSummary()

Good:

market = ComputeMarkingExpense()
sale = ComputeSaleExpense()
travle = ComputeTravelExpense()
personal = ComputePersonalExpense()
Dispaly(ExpenseSummery(market, sale, travle, personal)

对于没有依赖关系的代码,应该让他们尽可能分组,方便自上而下的阅读。最好做到相关代码构成的语句块之间不要有重叠

BAD:

MarketingData marketingData; SalesData salesData; TravelData travelData;

travelData.ComputeDuarerly() salesData.ComputeQuarterly() maketingData.ComputeQuarterly()

salesData.Print() travelData.Print() marketingData.Print()


Good:

MarketingData marketingData; marketingData.ComputeQuarterly() marketingData.Print()

TravelData travelData; travelData.ComputeQuarterly() travelData.Print()

SalesData salesData salesData.ComputeQuerterly() salesData.Print()


# 第十五章 使用条件语句

书中写到应该优先处理正常情况,但是这样的话可能造成箭头形的代码,个人认为还是有限处理错误并返回比较好。不过如果是正常情况的分支的话,应该优先处理常见情况。对于 switch 语句,应该在 default 字句中处理错误

对于大长串的 and 判断条件建议写一个 is_XXX函数来封装一下。

# 第十六章 控制循环

循环的种类:

1. 计数循环,按照规定的次数执行多次
2. 连续求值的循环,预先并不知道需要运行多少次,每次迭代都检查是否需要结束(比如用户选择退出,或者遇到了错误)
3. 无限循环,比如内核,事件循环
4. 迭代器循环,对容器类中的每一个元素执行一次操作

研究显示下面这种代码比传统的 while 或者 do-while 代码更容易理解,以为如果把退出条件强行放到开始或者结尾,那么很难避免重复代码。

while (true) { // ... if (xxx) break // ... }


像是 for 和 foreach 循环最好用在简单的控制条件,不要在循环体内做改动下标等操作。

## 安全计数器

对于可能无法终止的循环,可以考虑添加一个安全计数器来保证不会出现死循环。

BAD:

do { node = node->Next; } while (node-Next != NULL);


Good:

safeCounter = 0; do { node = node->Next; ... safeCounter++; if (safeCounter >= SATETY_LIMITE) { Assert(false, "Internal Error: safe counter violation") } ... } while (node->Next != NULL);


## continue 和 break

continue 语句就相当于一个 else 语句,和之前所述一样,尽量在循环开始的地方使用 continue 语句。对于return 语句也是一样的。

需要特别注意的是 switch 中也允许 break 语句,所以很可能导致潜在的bug

BAD:

network code()     {         switch(line)        {             case  THING1:                doit1();                break;             case  THING2:                if(x==STUFF)                {                     do_first_stuff();                     if(y==OTHER_STUFF)                         break;                     do_later_stuff();                 }                 /代码的意图是跳转到这里… …/                 initialize_modes_pointer();                break;             default :                 processing();         }        /… …但事实上跳到了这里。/         use_modes_pointer(); /致使modes_pointer指针未初始化/     } ``` 

把循环控制在3级以内,否则人类就不能理解。把长循环的内容移到子程序中。

可以由内而外的构建循环,也就是说先写出循环体要处理的内容,再去构建循环条件。

第十七章 其他控制结构

递归,首先要写终止语句,并且要尽量避免迭代次数过多的递归,因为每次递归都会产生一个调用栈,可以使用安全计数器来避免无限递归

第十八章 表驱动法

表驱动法把复杂的条件分支语句转化成通过查表语句。表驱动法还有一个好处就是可以把解析的逻辑放到外部文件中。表驱动的两个问题:

1. 使用什么key,如果对应的条件不能直接做key,可以使用一个函数转化成enum
2. 使用什么value

BAD:

if month == 1: days = 31 elif month == 2: days = 28 elif month == 3: days = 31 ... else month == 12: days = 31

GOOD:

days_per_month = [31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31] days = days_per_month[month -1]

阶梯访问表

比如根据分数来确定学生的评级

range_limit = [50.0, 65.0, 75.0, 90.0, 100.0]
grades = ['F', 'D', 'C', 'B', 'A']
max_grade_level = len(grade) - 1

grade_level = 0
student_grade = 'A'
while student_grade == 'A' and grade_level < max_grade_level:
    if student_score < range_limit[grade_level]:
        student_grade = grades[grade_level]
    grade_level += 1

第十九章 一般控制问题

if 语句

对于复杂的 if 语句来说,也可以考虑使用决策表来简化操作。 对于if语句中的判断,按照书中的顺序来编写数值表达式:if 1 < a and a < 3

度量复杂度的 Tom McCabe 方法,根据决策点来测试:

1. 从 1 开始
2. 遇到 if、while、repeat、for、and、or 复杂度加1
3. 遇到 case 语句,每种情况 +1

0-5, 函数还不错 6-10,需要改进 10+,必须改进

第二十章

没啥可记得

第二十一章

没啥可记得

第二十二章 开发者测试

单元测试 一个程序员或者一个团队的代码的测试 组件测试 被测代码涉及到多个程序或团队 集成测试 对两个或多个包进行测试,通常应该尽早开始 回归测试 重复执行以前的测试,以便查找是否有bug复发 系统测试 在最终配置下运行整个软件

推荐的测试方法

1. 对每项需求进行测试,最好在需求阶段就准备好测试用例
2. 对每一个设计的关注点进行测试
3. 基础测试
4. 测试已经犯过的错误

基础测试

目标是覆盖每一个路径,采用的方法和之前计算代码复杂度的方法一样。

第三十章

如果你发现自己每天多次键入某个长度超过5个字母的命令,那么应该用一个脚本

第三十一章

没啥值得记录的

第三十二章 自说明代码

IBM 的研究显示:平均每十行一个注释的代码可读性最高。

代码注释应该着眼于 why 而不是 how

BAD

// if acccount flag is zero if (accountFlag == 0) ...

GOOD // if establishing a new acccount if (accountFlag == 0) ...

BETTER

if (account.type == AccountType.NewAccount) ...

对于非常规的行为要给与注释

第三十三章 个人性格

你越是谦虚,进步就越快。

  1. 将系统“分解”,是为了使之更易于理解
  2. 进行 review 和 test 是为了减少人为失误(egoless programming)
  3. 将子程序编写的短小,以减轻大脑的负担
  4. 基于问题而不是底层实现细节来编程,从而减少工作量
  5. 通过各种各样的规范,将思路从相对繁琐的编程事务中解放出来。

求知欲

如果分配给你的工作净是些不能提高自身技能的短期任务,你理应表示不满。如果正处于竞争激烈的软件市场,则目前工作用到的一半的知识将在三年后过期。假如不持续学习,你就会落伍。

如果在工作中学不到什么,就找一份新的工作吧。

如果不了解所用语言的某一特性是怎么回事,可编写一个小程序来检验,看看它是如何工作的。请在调试器中观察程序的执行情况。用一个小程序来检验某一概念,总比编写大程序时运用不太了解的特性要好。

如果小程序表现的特性与你设想的不一样,怎么办呢?那正是你要研究的问题。最好通过小程序找出答案,而不要用大程序。有效编程的关键之一就是要学会迅速制造错误,并且每次都能从中有所收获。犯错不是罪过,从中学不到什么才是罪过。

阅读解决问题的有关方法。解决问题是软件创作过程中的核心行为。就算你想再发明个车轮,也不会注定成功,你发明的也许是方车轮。

在行动之前作出分析和计划。在分析和行动直接有着矛盾关系,然而多数程序员的问题不在于分析过度。

学习成功项目的开发经验。Jon Bentley 认为你应该坐下来,准备一杯白兰地,点一根上好的雪茄,想看优秀小说一样来阅读程序。至少应该研究高层设计,并有选择地去研究某些地方的细节源代码。

Thomas Kuhn 指出,凡是成熟的学科都是从解决问题发展起来的。

不仅要阅读别人的代码,还应渴望了解专家对你的代码的看法。

诚实

偷懒的三个境界

  1. 拖延不喜欢的任务
  2. 迅速做完不喜欢的任务,以摆脱
  3. 编写某个工具来完成不喜欢的任务,以便再也不会做这些事

理论上当然是这样的,然而经常会遇到的讨厌的事情并不是重复性的,而是每次都是不同的恶心事情,或者 lead 催很紧,没有时间做类似的工具。

坚持

不要坚持,如果花了15分钟调试仍然没有进展,就该放弃排错过程,让潜意识仔细品品。和计算机错误斗气是不明智的,更好的方法是避开他们。

经验

人们还荒唐地强调程序员有多少经验。“我们需要有五年以上 C 语言编程经验的程序员”就是愚蠢的说法。如果程序员过了前一两年还没有学好 C 语言,那么再过个三年也没有什么意义。

最后一个问题,如果你工作十年,你会得到十年经验还是一年经验的十次重复?必须检讨自己的行为,才能获得真正的经验。只有坚持不懈地学习,才能获取经验;如果不这样做,无论你工作多少年,都无法获得经验。

Gonzo Programming

彻夜编程让你感觉像是世界上最好的程序员,却要花上几个星期去纠正你在短暂辉煌时埋下的错误。可以热爱编程,但是热情不能代替熟练的能力,请想明白什么更重要。

习惯

不能用“没有习惯”来代替“坏习惯”,只能用“新习惯”代替“坏习惯”

第三十四章

基于问题域编程,最顶层代码不要关心实现细节。


评论区