《编程匠艺》读书笔记(一)

前言

最近读了《编程匠艺》这本书,它是由美国作者 Pete Goodliffe 编写的,它不仅是一本学习指南,更是一本激发编程激情的读物,展示了一种追求卓越的编程态度。

在我看来,它带来不仅仅是技术上的提升,更好地掌握编程技巧、提高自己的开发效率和质量,更重要的是对编程的思考和理解。

下面是读书笔记+个人理解,书中一共分24个章节,当前是 01章- 06章。

一、防御性编程

1.1 优秀的代码

有三种代码

  • 能用的代码:面对常规输入没问题,但是遇到意外的输入可能会崩溃
  • 正确的代码:这种代码绝不会崩溃,但是输入集可能会很庞大,并且难以测试,有时也会很难理解
  • 优秀的代码:这种代码首先会是正确的,然后也是健壮、高效的

想到在《java解惑》这本书中第一章就是判断整数是否奇数的代码,修改后就从能用变为优秀

// 碰到负整数会有问题
public static boolean isOdd(int i) {
    
    
    return i%2==1;  
}
// 修改后
public static boolean isOdd(int i) {
    
    
    return i%2!=0;  
}

1.2 防御性编程技巧

自己一个人编写优秀代码似乎很简单,但是跟和他人合作或者接手旧代码,这种情况下依然写出优秀代码就很难,这时就需要用到防御性编程。

  • 不要做设想,就像上面判断是否奇数,不能设想程序只会输入正整数,一个人写代码短时可能会记住,但时间一长或者在团队合作中其他人调用你的代码,不会考虑这些
  • 使用好的编码风格和设计
  • 不能仓促编写代码
  • 不要相信任何人
  • 编码目标是清晰,而不是简洁,不能只考虑一行代码解决,更要便于理解,无论自己还是他人
  • 各司其职,在 java开发中,bean的属性要设为为私有,还有我认为类的职责要和名字搭配,不要加入无关的方法
  • 在编译时打开所有警告开关,这针对 C/C++
  • 使用静态分析工具
  • 使用安全的数据结构
  • 检查所有返回值
  • 谨慎处理内存(或其它宝贵资源) 针对 C/C++
  • 声明位置处初始化变量,针对 C/C++,Java声明处有默认值
  • 尽可能推迟一些声明变量
  • 使用标准语言工具,针对 C/C++
  • 使用好的诊断日志工具,Java有log4j
  • 谨慎使用强制转换
  • 提供默认行为,switch default和if else 仔细考虑各种情况
  • 考虑数字溢出
  • 将可以设置为常量的变量都设置为常量

1.3 约束

C、C++通过断言assert来约束代码,Java也可以通过assert语句来约束代码

二、代码样式

2.1 面向人类

我们编写的代码面向的读者有三类:

  • 自己:自己字写的难看,自己都很难阅读,更何况代码
  • 机器:机器不管样式,只需要通过编译,就不会因为样式报错
  • 其他人:这类人是最重要的,个人的能力总是有限的,人需要合作完成任务

所以代码样式是面向人类的,机器不管这些。优秀的程序员样式肯定和新手不一样,新手编写的代码样式总是混乱的,看看自己当年写的代码。

2.2 优秀样式

优秀样式有如下特征:

  • 一致:缩进空格数要一致,大小括号的位置也需要一致等等
  • 传统:使用业内流行的一种规范
  • 简洁:简单,他人易学习,可接受

优秀的样式不是一成不变的,它一定是团队中大多数人所接受的

三、代码命名

3.1 命名 Why What How

  • 为什么好的命名很重要

    有名称的就是存在的,好的名称便于理解和记忆。在编程过程中,命名糟糕的实体不仅不方便管理,而且会产生误导,出差错。一个对象的名称应该清晰地描述这个对象

  • 命名哪些

    变量,函数,类,命名空间,包,宏,源文件等。这几样是编程过程中最常见的

  • 怎样命名

    见下文

3.2 优秀的命名

  • 技术性正确,编译器不会报错

  • 重点在于清晰而非简洁,无歧义

  • 符合语言习惯,前后一致,统一

  • 利用上下文,如类Tree有个方法countApplesInTree, 可以修改为Tree:countApples

3.3 变量命名

  • 名词:通常表示某个物理对象,有些不能与现实对象对应的变量也能用名词,如用过的时间 elapsed_time

  • 动词:如果变量不能用名词表示,那么它一般就是一个名词化的动词,如计数 count

  • 数字变量的名称描述了对其值的解释,如设备长度 widget_length

  • 逻辑变量的名称是个条件语句形式名称,如 is_red

  • 变量和类型要区分开来,所以变量的首字母一般是小写,而类型是大写

3.4 函数命名

  • 函数是一个动作,命名至少包含一个动词
  • 从使用者的角度来为函数命名
  • 可以使用下划线或者驼峰法来命名

3.5 类型命名

  • 类型可以描述一些具有状态的数据对象,这种情况下,名称可能是名词

  • 类型也可能是实现接口回调函数的类,这种,名称可能是动词

四、代码自文档化

4.1 为什么不愿编写文档

  • 大多数程序员对文字工作都会感到头大,对编写大量注释都会感到头疼,并且编写文档十分耗时,阅读也是如此,所以更愿意编写程序
  • 所有独立的文档都会随着代码的更改而更新,在大型项目中,这是种十分可怕的工作
  • 大量的文档是很难管理的,必须和代码一样进行版本控制
  • 外部文档和程序很难链接,很容易错过重要信息

4.2 自文档化的代码

  • 自文档化的代码是优美的,可读性很强的代码,它本身就易于理解,不需要外部文档。
  • 理想情况下的自文档化代码是不需要注释的,那会影响阅读,但实际很难做到,所以需要良好的注释

4.3 技巧

  • 好的样式
    • 在if/else中结构保持一致(例如,if后面总是处理正确情况,else后面处理错误情况),并且保持统一,不能后面就情况相反
    • 避免过多嵌套,谨慎使用递归
    • 谨慎优化代码
  • 分解为原子函数
    • 一个函数,只执行一种操作,选择一个毫无歧义说明这种操作的名称,一个好的名称就不需要编写额外的文档
    • 减少任何出人意料的副作用
    • 保持简短,短小的函数易于理解
  • 选择合适类型
    • 永远不需要修改的值,可以定义为常量
    • 如果变量描述的值不包含负值,那么应该选用无符号类型(如果语言支持)
    • 使用枚举来描述一组相关的值
    • 选择合适的类型。c/c++将值的大小放在size_t变量中, 指针的运算结果放入ptrdiff_t变量
  • 命名常量
    • if (count == 76) 中76的含义到底是什么,将76修改为常量 banaba_per_cake更好理解
    • 上述像 76 这种被称为Magic Number,有些代码检测工具会检测到
  • 强调重要的代码
    • 在类中一定要按顺序进行声明。公共信息要放在首位,私有的实现信息放在最后
    • 隐藏所有不重要的信息
    • 不要隐藏重要的代码
    • 限制嵌套条件语句数量
  • 分组相关信息
    • 所有相关的信息应该放在同一个地方
    • java 中有package提供分组
    • c/c++有命名空间
  • 提供文件头
    • 在文件的顶部提供一个注释块,用于描述文件作用,便于维护者理解
    • 大多数公司或者说很多开源软件都出于法律方面考虑,都会在每个源文件加上版权或者开源声明
  • 恰当处理错误
    • 恰当的位置处理错误。如磁盘IO异常,就应该在访问磁盘的代码中处理该异常。该异常还会引起文件无法访问异常,那么就不能在这里处理IO错误
    • 不要在最终的用户界面代码部分处理磁盘IO错误
  • 编写有意义注释
    • 只有你在无法以任何其它方式来提高代码清晰度的下,再添加注释
  • 提高自身水平
    • 写作技巧可通过 读越多的书进行提高。用批判的眼光来审视其他人的文章,可以学会分辨好坏
    • 编写代码也一样,读各种优秀的代码(各个开源库),也会提高个人水平

五、代码注释

5.1 作用

  • 注释可将优秀的代码和糟糕的代码区分开来,将粗糙复杂艰涩难懂的逻辑与清晰友好的算法区分开,但也不能过分夸大注释的作用。
  • 注释可以锦上添花,但不能雪中送炭,不能够让酸代码变为甜的

5.2 是什么

  • 从语法角度讲是编译器忽略不计的源代码块,从语义讲是对所处代码的解释
  • 注释的目标读者是人,提高注释质量就必须满足人在阅读代码时所真正需要的
  • 作为负责的程序员,有义务写好注释

5.3 该写什么

  • 解释为什么,而不是怎么样

  • 不要在注释中重复代码

  • 当编写密密麻麻的注释来解释代码时,应该优化代码,而不是写注释

  • 记录意想不到的内容,例如一个操作系统问题

  • 写的文字必须是真实,有价值,清晰明了,容易理解的

5.4 不该写什么

  • 过去的事,不要写过去版本的代码,现在都有版本控制系统,能够查找到过去的代码和注释

  • 不想要的代码,同样有代码版本控制系统,

  • ASCII艺术,任何突出代码部分的样式都是不可取的,例子如下

    aBadExample(n, foo(wibble));
    //             ^^^
    //             My favorite function
    
  • 代码块结尾,用注释来标记控制块的结尾是不可取的,现代编辑器都有代码块折叠功能,很容易区分

    // end if (a < 1)
    

5.4 使用注释

  • 在注释中加 TODO 可以标记未完成的工作
  • 使用注释注意要删除已经过时的注释,当修改代码时,维护代码周围的所有注释
  • 当查看旧代码库时,最好不要删除找到的任何空洞无物的注释

六、代码错误处理

6.1 错误原因

产生错误的原因有很多种,不过大体可分为下面3种:

  • 用户错误,愚蠢的用户粗暴地对待了你心爱的程序,他或许提供了错误的输入,或进行了荒谬的操作
  • 程序员错误,用户按钮都正确,但是程序还是出问题了,是由于编写的代码存在bug引起的,
  • 意外情况,用户输入ok,程序员也没犯错,但是碰到意外情况,如网络断开,硬盘磁盘满了

6.2 错误管理报告

错误报告就是向客户端传播错误信息

  • 不报告,直接终止程序,这不是个好方法,但是简单。
  • 返回值判断,通过对函数返回值返回的状态码判断,是否发生错误
  • 异常,java使用异常来管理错误
  • 信号,是硬件中断的软件等价物,操作系统为每个信号提供合理的错误处理程序,c可以使用信号处理错误。

6.3 错误处理

  • 通过日志来记录错误
  • 程序需要在没有什么可做的情况下向用户报告错误,用户不能时时刻刻被错误轰炸,遇到可以恢复的情况,不要报告。当然有些只有用户能处理的错误,需要立即报告给他
  • 有时错误并不能在代码端解决,或者你并不知道怎么解决,那么你需要向上传递错误

6.4 错误检查

  • 检查代码是否进行了资源清理,特别是一些流的关闭
  • 检查代码函数参数,是否是预期
  • 检查函数调用上下文状态

参考

  1. 编程匠艺

猜你喜欢

转载自blog.csdn.net/qq_23091073/article/details/131772473
今日推荐