《代码大全2》读书笔记

第7章 高质量的子程序

7.1 创建子程序的正当理由

  • 降低/隔离复杂度,隐藏实现细节,引入中间的、易懂的抽象
  • 避免代码重复,支持子类化
  • 提高可移植性,限制变化所带来的影响
  • 简化复杂的逻辑判断,改善性能

7.2 在子程序层上设计

  • 功能的内聚性:只做了一件事并把它做得很好,操作与名称相符
  • 顺序上的内聚性:包含需按特定顺序执行的操作,它们共享数据且只有全部执行完功能才完整
  • 通信上的内聚性:不同操作使用了同样的数据,但不存在其他任何联系
  • 临时的内聚性:包含一些需要同时执行才放在一起的操作
  • 其他类型的内聚性:基本是不可取的,它们会导致代码组织混乱、难于调试、不便修改
  • 子程序间耦合松散,连接小、明确、可见并灵活

7.3 好的子程序名称

  • 能准确描述子程序所做的全部事情:避免使用表述不清的动词;避免用数字区分不同子程序
  • 函数命名时应针对返回值有所描述
  • 过程命名时用语气强烈清晰的"动词+宾语"形式:类的过程不用加入对象名(宾语)
  • 准确使用对仗词:add/remove,source/target,next/previous等
  • 为常用操作确立命名规则:如统一命名方式 x.Id() y.GetID() 为其中一种

7.4 子程序可以写多长

  • 允许有序增长到100~200行(不含注释),该长度与小而美的子程序一样不易出错,超过200行易遇到可读性等问题
  • 限制长度不如关注 – 内聚性、嵌套层次、变量数量、决策点数量、注释数量来决定长度

7.5 如何使用子程序参数

子程序间的接口是最易出错的部分之一:39%错误源于互相通信时

  • 参数保持顺序统一:如输入-修改-输出-状态参数最后;多个子程序使用类似参数顺序保持一致
  • 参数使用语义提示:如const标记输入、&标记修改,或加i/m/o/s等前缀
  • 使用所有的参数且避免用输入参数做工作变量:传递了就要用到,否则删掉;避免混淆
  • 对特征参数的假定加以说明:如输入、修改或输出;参数单位;非枚举状态值含义;数值范围;不该出现的特定值
  • 参数个数限制在7个以内:心理学研究表明超过7个单位的信息很难记住,多个参数考虑合成数据类
  • 传递对象还是成员做参数 – 取决于子程序接口的抽象:
    1. 参数传递:期望的几项特定数据碰巧来自一个对象;
    2. 对象传递:想持有某个特定对象并要进行某些操作(经常修改参数列表且都来自同一个对象)
  • 确保实参和形参相匹配:检查参数类型并留意编译器警告

7.6 使用函数时要特别考虑的问题

  • 若用途为返回其名称所指明的返回值就应用函数,否则用过程
  • 设置函数返回值:
    1. 检查所有可能的返回路径并在开头用默认值初始化;2. 不要返回指向局部数据的引用或指针
  • 宏/内联子程序尽量避免使用宏
    1. ()包含整个宏表达式;2. {}括起含有多条语句的宏;3. 展开后形同子程序的宏命名同子程序以便替换
  • 节制使用inline子程序:除非剖测(profile)时得到不错的性能收益

第11章 变量名的力量

11.1 选择好变量名的注意事项

  • 准则:变量名要完全准确地描述其所代表的的事物(可读、易记、无歧义、长度适中)
  • 问题为导向:好的命名表达的是“什么”(what),而不是“如何”(how)
  • 恰当的名字长度:
    1. 平均长度在8~20个字符最易于调试 – 若发现很多短名时应检查其含义是否足够清晰
    2. 较长的名字适用于很少用到的、全局变量,较短的适用于局部、循环变量
  • 对全局变量名加以限定词:如命名空间(namespace)、包名(package)或带有子系统特征的前缀
  • 限定词前置突出含义:为变量赋予主要含义的部分应位于最前面

11.2 为特定类型的数据命名

  • 循环变量:短循环用i、j等;长循环或循环外变量应使用可读性高的命名
  • 状态变量:使用枚举、具名常量;当某段代码不易读懂时就应考虑重命名
  • 临时变量:即时“临时”最好也取个可读性高的名称
  • 布尔变量:使用肯定且隐含"真/假"含义的名字,如found等而非notDone等难于阅读的否定词
  • 枚举类型:使用组前缀;若使用须冠以枚举名则无需前缀如Color.Red
  • 具名常量:应根据常量表示的含义而非具有的值为其命名

11.3 命名规则的力量

  • 何时采用命名规则:
    • 多人合作、维护或需他人评估的项目时
    • 大规模程序,脑海里无法同时了解事情全貌必须分而治之时
    • 生命周期长的项目,长到搁置几星期/月后又需重启工作时
    • 当项目中存在一些不常见术语,希望在编码阶段使用标准术语或缩写的时候

11.4 非正式命名规则

  • 前缀标识全局变量g_、具名常量c_、参数变量a、局部变量l、成员变量m_、类型声明T
  • 语法标明并限制只读参数const、可修改参数&、*
  • 格式化命名保持一致风格:如一直单用驼峰命名或匈牙利命名法

11.5 标准前缀

  1. 用户自定义类型(UDT):缩写标识出数据类型,如字符ch、文档doc
  2. 语义前缀(不随项目变化):如指针p、全局变量g

11.6 创建可读的缩写

  • 使用标准缩写(列在字典的常见缩写)
  • 去掉虚词and/or/the等,去除无用后缀ing/ed
  • 统一在某处截断或约定成俗的缩写如src/succ
  • 使用名字中每一个重要单词且最多不超过三个
  • 缩写要一致;可读出来;避免易看/读错的字符组合;
  • 项目级缩写辞典:创建新缩写时加以说明并归档,只有不惜花费精力写文档的缩写才的的确确因当被创建

11.7 应该避免的名称

  • 避免容易令人误解或混淆(相似含义/易拼错/数字)的名称或缩写,如I/1/l
  • 避免含义不同却名字相似,避免仅靠大小写区分
  • 避免使用发音相近的名称,不易于讨论
  • 避免混合多种自然语言(中英混杂)

第8章 防御式编程

8.1 保护程序免遭非法输入数据的破坏

  • 核心思想:子程序不会因传入错误实参而被破坏,哪怕是由其他子程序产生的错误数据
  • 检查所有外部来源数据的值;检查所有输入参数的值;决定如何处理错误的输入数据;

8.2 断言(Assertion)

  • 断言主要用于开发和维护阶段:
    • 检查输入或输出值在预期范围内(如指针非空;数组或容器容量足够;表已初始化存储着有效数据)
    • 检查子程序开始(结束)执行时文件或流处于打开(关闭)状态,且读写位置位于开头(结尾),检查读写模式
    • 检查仅作为输入的参数值是否被子程序所修改
  • 使用断言的指导建议:
    • 用错误代码处理预期内(或系统内部)状况,用断言处理绝不应该发生的状况(触发则修改源码)
    • 避免将需要执行的代码放到断言中
    • 用断言注解并验证前条件(调用方确保参数正确)和后条件(被调用方确保返回正确)
    • 对于高健壮性的代码(大规模长周期复杂项目),应先使用断言再使用错误处理代码

8.3 错误处理技术

  • 处理预期内可能发生的错误方式:
    • 返回中立值/最接近的合法值:如指针操作返回NULL,超出范围返回最大值
    • 换用下一个正确的数据,返回与前次相同的数据
    • 将警告信息显示/记录到日志文件中(注意数据隐私)
    • 返回一个错误码:只处理部分错误其余报告调用方有错误
    • 关闭程序:适用于安全攸关倾向于正确性的程序
  • 在架构层次确定错误参数处理方式,并始终如一地采用该种处理方式

8.4 异常

  • 异常 – 将不甚了解的出错转交给调用链其他子程序更好地解释,使用建议:
    • 发生了不可忽略的错误需要通知程序其他部分
    • 只在发生真正罕见或无法解决的问题下才抛出异常,可局部处理的直接处理掉
    • 避免在构造/析构函数中抛出异常
    • 抛出的异常应该与接口的抽象层次一致
    • 在异常消息中加入关于导致异常发生的全部信息
    • 了解所用函数库可能抛出的异常,未能捕获将导致程序崩溃
    • 创建一个集中的(统一存储格式化)标准化的(规定使用场合/异常对象类型等)异常报告机制

8.5 隔离程序,使之包容由错误造成的损害

  • 隔栏(手术室)技术 – 数据进入前"消毒",之后都认为是安全的
    • 只在类的公开方法检查并清理数据,而类的私有方法不再承担校验职责
    • 在输入数据后立即将其转换为恰当的类型
  • 隔栏外部的程序使用错误处理技术,而隔栏内部应使用断言(数据已清理,出错为程序问题)

8.6 辅助调试的代码

  • 不要把产品版的限制强加于开发版:在开发期牺牲某些,用以换取让开发更顺畅的内置工具
  • 尽早引入辅助调试的代码
  • 采用进攻式编程:
    • 确保断言语句使程序终止运行 – 问题引起的麻烦越大越易被修复
    • 完全填充分配到的内存/文件/流 – 易于排查内存分配/文件格式错误
    • 确保case语句的default/else分支都产生不可忽视的提示
    • 在删除一个对象前把它填满垃圾数据
    • 可以的话将错误日志文件发送到email
  • 计划移除调试辅助的代码
    • 使用类似ant和make的编译工具
    • 使用预处理器(内置/自定义的编译条件)
    • 使用调试存根:stub存根子程序开发阶段用于各种校验日志输出,发布时立即将控制权交还调用方

8.7 确定在代码中该保留多少防御式代码

  • 保留那些检查重要错误的代码,去掉检查影响细微错误的代码
  • 保留能让程序稳妥地崩溃的代码,去掉会导致硬性崩溃/数据丢失的调试代码
  • 为技术支持记录并保存错误日志
  • 确认错误显示消息对用户而言是友好的

8.8 对防御式编程采取防御的姿态

  • 避免过度使用,因地制宜的调整防御式编程的优先级

第18章 表驱动法

  • 从表里查找信息而不使用逻辑语句if和case; 将复杂的逻辑链/继承结构用查表法替代

18.1 表驱动法使用总则

  1. 如何从表中查询条目:直接访问,索引访问,阶梯访问
  2. 应该在表里存些什么:数据还是函数

18.2 直接访问表

  • 无须绕很多复杂的圈子就能在表里找到需要的信息
  • 构造查询键值:
    • 复制重复信息直接使用键值;
    • 转换键值使其能直接使用,将键值转换独立成子程序

18.3 索引访问表

  • 将基本数据映射为索引表的一个键值,再用键值关联主数据表
  • 优点一:避免主查询表单条记录过大和重复造成的空间浪费:如商品码099→商品类型AE→A~E商品信息
  • 优点二:操作索引中的记录比操作主表中的记录更方便更廉价:如员工姓名索引,薪水索引
  • 优点三:良好的可维护性:将索引查询提取为单独的子程序,方便更换查询技术等

18.4 阶梯访问表

  • 表中的记录对不同数据范围有效,而非不同的数据点 – 适合处理无规则数据
  • 留心端点:确认已考虑到每个阶梯区间的上界
  • 超多阶梯考虑用二分查找取代顺序查找
  • 将查询操作提取为单独的子程序; 考虑用索引替代

第4章 关键的"构建"决策

4.1 选择编程语言

  • 更熟悉或更高级的编程语言将达到更好的生产率和质量
  • 了解诸如面向对象、面向过程、脚本等语言的明确优点和弱点

4.2 编程约定

  • 高质量软件其"架构的概念完整性"与"底层实现"保持着内在的固有的一致性
  • 架构的指导方针使得程序的结构平衡:如绘画设计,其中一部分古典主义而一部分印象主义是不可能具有"概念完整性"的
  • 针对"构建活动"的指导方针(格式约定等)提供了底层的协调,将每个类都衔接到一种完整的设计中,成为可靠的部件

4.3 你在技术浪潮中的位置

  • 编程工具不应该决定你的编程思想:首先决定要表达的思想,再决定如何去表达出来
  • 编程原则并不依赖特定的语言,如果语言缺乏就应该试着去弥补它,发明自定的编码约定、标准、类库及其他改进措施

4.4 选择主要的构建实践方法

  • 编码
    • 是否确定哪些设计工作要预先进行,哪些设计在编码时进行?
    • 是否规定了诸如名称、注释、代码格式等"编码约定"?
    • 有无规定特定的由软件架构确定的编码实践:如何处理错误条件/安全性事项、类接口有哪些约定、考虑多少性能因素等
    • 是否确定你在技术浪潮中的位置并有相应的调整计划和预期目标?是否知道如何不受限于某种编程语言?
  • 质量保证
    • 编码前是否要先编写测试用例?需要为自己的代码编写单元测试么?
    • check in代码前,会用调试器单步跟踪整个代码流程吗?是否进行集成测试?
    • 会复审(review)或检查别人的代码吗?
  • 工具
    • 是否选用SVN/GIT版本控制及相关工具?
    • 是否选定了一种语言(版本)或编译器版本?是否允许使用非标准的语言特性?
    • 是否选定了某个编程框架(J2EE 或 .NET),或明确决定不使用框架?
    • 是否选定并拥有了将要用到的工具——IDE、测试框架、重构工具、CI等?

第33章 个人性格

33.1 个人性格是否和本书话题无关

  • 下定决心成为出色的程序员:聪明无法提升,性格却可改进,而个人性格对造就高手有决定性意义

33.2 聪明和谦虚

  • 如何专注你的才智比你有多聪明更重要:越了解自己的局限性越能保持谦虚,进步也就越快
    • 将系统"分解",使之易于理解
    • 进行审查、评审和测试,减少人为失误; 应和他人沟通(三人行必有吾师),以提高软件质量
    • 通过各种各样的规范,将思路从相对繁琐的编程事务中解放出来

33.3 求知欲

  • 在成长为高手的过程中,对技术的求知欲具有压倒一切的重要性 – 技术不断更新,跟不上就会落伍
  • 在开发过程中建立自我意识:阅读学习并实践,若工作中学不到新东西就应考虑换新工作
  • 对编程和开发过程做试验:学会迅速编写小实例去试错,并能从中有所收获
  • 阅读问题的相关解决方法:人们并不总能自行找出解决问题的巧妙方法,所以要学习他人的解法
  • 在行动之前做分析和计划:不要担心分析太久,等钟摆走到"行动"快中央的位置再说
  • 学习成功项目的开发经验,研究高手的程序:如《编程珠玑》; 找些一流程序员评论你的代码
  • 阅读文档:文档中有许多有用的东西值得花时间去看 – 例如每两月翻翻函数库文档
  • 阅读其他书本期刊:每两月看本计算机好书(35页/周),过不了多久就能掌握本行业脉搏并脱颖而出
  • 同专业人士交往:与希望提高技术的人为伍,参加交流会/群
  • 向专业开发看齐
    • 1入门级:会利用某语言基本功能或特性编写类、流程语句
    • 2中级:能利用多种语言的基本功能,并会得心应手地使用至少一种语言
    • 3熟练级:对语言或环境(或两者兼具)有着专业技能,或精通J2EE的盘根错节,或对C++引用如数家珍
    • 4技术带头人级:具有第3级的专业才学,并明白工作中85%的时间都是与人打交道 – “为人写代码,而非机器,所写代码晶莹剔透还配有文档”

33.4 诚实

  • 承认自己"不知道",不假装是高手 – 听听别人说法,学到新内容,并了解他们是否清楚讨论的东西
  • 犯了错误应立即主动承认 – 复杂的智力活动有潮起潮落,错误情有可原,这也是强调测试的原因之一
  • 力图理解编译器的警告而非弃之不理 – 忽略警告,时间就很可能会浪费在调试上
  • 透彻理解自己的程序,而不只是编译看看能否运行 – 测试只能找出错误,不能确保"不存在错误"
  • 提供实际的状况报告 – 深思熟虑后冷静地在私下汇报项目真实状态,管理者需要准确的信息以便协调
  • 提供现实的进度方案,在上司前坚持自己的意见 – 如"无法商量项目该花多少时间,就像不能商量确定一里路有几米一样,自然规律是不能商量的。但我们可协商影响项目进度的其他方面,比如减少些特性,降低性能,分阶段开发,少些人时间
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值