$ ls ~yifei/notes/

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

Posted on:

Last modified:

TODO: 总结

作为技术员工,你的一部分工作就是培训周围的非技术人员,讲解开发过程。

在软件开发过程中,如果需求被污染了,那么他就会污染架构,而架构又会污染构建。这样会导致 程序员脾气暴躁、营养失调;开发出的程序具有放射性污染,而且周身都是缺陷。

发现错误的时间要尽可能接近引入错误的时间。

问题定义应该用客户的语言来书写,而且应该从客户的角度来描述问题。最好的解决方案未必是一个计算机程序。

明确的需求免得你去猜测用户想要的是什么。开发过程能够帮助用户更好地理解自己的需求,这是需求变更的主要来源。平均下来,开发过程中有 25% 的需求变化会导致返工量的 75% 以上。

如何怒怼需求

  1. 确保每个人都知道需求变更的代价。“进度”和“成本”这两个字眼比咖啡和冷水澡都要提神,许多“必须要有(must have)”的功能很快就会变成 “有了最好(nice to have)”
  2. 建立一套变更控制系统
  3. 使用能够适应变更的开发方法
  4. 放弃这个项目。如果需求特别糟糕,或者极不稳定,而上 main 的意见没有一条能够奏效,那就取消这个项目。即使你真的无法取消这个项目,也设想一下取消它之后回事怎样的情况。
  5. 注意项目的商业案例、有些需求作为功能特色来看是不错的注意,但是当你评估“增加的商业价值”时就会觉得它是个糟糕透了的主意。

架构的典型组成部分

  1. 服务划分
  2. 业务规则
  3. 用户界面设计
  4. 资源管理
  5. 安全性
  6. 性能
  7. 可伸缩性
  8. 互用性
  9. 国际化、本地化
  10. 错误处理
  11. 关于买还是造的决策
  12. 如何复用
  13. 变更策略

优秀的架构往往适合机器和语言无关的。

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

没啥可记录的

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

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

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

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

第五章

软件的首要技术使命——管理复杂度

低效的设计往往来自:

  1. 用复杂的方法解决简单的问题
  2. 用简单但错误的方法解决复杂的问题
  3. 用不恰当的复杂方法解决复杂的问题

理想的设计特征

  1. 高扇入:大量的类使用某一个固定的类,也就是有很好的复用。
  2. 低扇出:让一个类尽量少地使用其他的类,否则会过于复杂。
  3. 使用标准技术:要尽量使用标准化、常用的方法,让整个系统给人一种熟悉的感觉。

对子系统之间的通信应该加以限制,越少越好。尤其不要有环向的依赖。

类应该像冰山:八分之七都是位于水面之下,而你能看到的只是水面之上的八分之一。

常用的设计模式:

  1. 抽象工厂模式。通过制定对象组的种类而非单个对象的类型来支持创建一组相关的对象
  2. 适配器。把一个类的接口转换成另一个接口
  3. 桥接。把接口和实现分开,使他们可以独立地变化
  4. 组合。创建一个包含了其他同类对象的对象,是的客户端可以与最上层对象交互而无需考虑过多的细节对象。
  5. 装饰器。给一个对象动态的添加职责,而不去创建新的类
  6. 外观。为没有一致接口的代码提供一个一致的接口
  7. 工厂方法。
  8. 迭代器。提供一个服务来顺序访问一族元素中的每一个
  9. 观察者。当一个对象变化时,把这个变化通知其他元素。
  10. 单例。有且只有一个实例
  11. 策略。定义一组行为或算法,使得他们可以动态地相互替换
  12. 模板方法。定义一个操作的算法结构,但是把部分实现的细节留给子类(派生类)

蛮力也是一种强大的方法。画图是另一种强大的启发式方法。图能在另一个更高的抽象层次上表达问题。不要卡在单一问题上,可以去散散步。

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

第六章

不懂 ADT 的程序员开发出来的类只是名义上的“类”而已——实际上这种“类”只不过是把一些稍微有点关系的数据和子程序堆在一起。

YN: 使用 ADT 隐藏实现细节,但是也可能是过早优化,比如 JAVA 中讨厌的 getter、setter

使用类的好处是,你可以像在现实世界中

第十章

隐式变量声明

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

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

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

习惯

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

第三十四章

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

WeChat Qr Code

© 2016-2022 Yifei Kong. Powered by ynotes

All contents are under the CC-BY-NC-SA license, if not otherwise specified.

Opinions expressed here are solely my own and do not express the views or opinions of my employer.

友情链接: MySQL 教程站