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

文章目录

  • 前言
  • 七、代码工具
    • 7.1 在意工具
    • 7.2 了解工具
    • 7.3 选择工具
  • 八、代码测试
    • 8.1 Why/Who/What/When/How
    • 8.2 测试类型
    • 8.3 测试原则
  • 九、代码bug调试
    • 9.1 bug种类
    • 9.2 调试解决bug
    • 9.3 搜寻bug
    • 9.4 修正bug
  • 十、代码构建
    • 10.1 编程语言
    • 10.2 构建系统
  • 十一、代码优化
    • 11.1 优化是什么
    • 11.2 什么导致优化
    • 11.3 为什么不进行优化
    • 11.4 为什么进行优化
    • 11.5 优化的技术
  • 十二、代码安全
    • 12.1 危险
    • 12.2 敌人
    • 12.3 脆弱的代码
    • 12.4 防范
  • 参考

前言

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

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

书中一共分24个章节,下面是读书笔记+个人理解,一共分4篇博文发布,每篇6章,当前是 07章-12章。

七、代码工具

7.1 在意工具

  • 没有一套核心的软件工具,就不可能创建程序,没有编辑器或者编译器,将寸步难行
  • 尽可能全面了解常用工具
  • 使用工具可以提高效率,但是可能需要花费时间来学习它,取舍在自己,当且仅当让自己生活更轻松的时候使用它们

7.2 了解工具

  • 首先需要了解手头有哪些工具,知道在哪可以获得工具,即使你现在不需要它们
  • 匀出一段时间来磨炼使用工具的技巧,这一点很重要
  • 将合适的工具放在合适的任务上,不要杀鸡用牛刀

7.3 选择工具

现代工具有很多,IDE 将很多工具都整合在一起

  • 源代码编辑器:VS Code, Sublime, 记事本, vim, Emacs, Jetbrains全家桶

  • 文本处理工具:linux 上 文本三剑客grep/awk/sed

  • 版本控制工具:git和svn最常见

  • 源代码生成:yacc/bison 编译原理相关,语法分析器,生成合适的c/c++语言

  • 源代码美化工具:现代 IDE 一般都会有这个功能

  • 编译器:注意版本,选择合适的

  • 链接器:针对 c/c++

  • 构建工具:c和c++最常用make,java可以使用maven和gradle

  • 测试工具链:java常用的测试工具就是Junit

  • 调试器:一般IDE都会有调试功能,c/c++有gdb命令行调试器

  • 分析器:java有jconsole,Arthas

  • 代码校验器:lint, java中IDEA也有插件有此功能

  • 度量工具:java Designite

  • 反汇编程序:Java IDEA有Jad

  • 缺陷跟踪/项目管理:gitlab,禅道等项目管理程序

八、代码测试

编写的代码越多,编写的越快,bug也会越多,对此采取的方法就是测试,发布任何未经测试的代码注定失败。研究表明,即使经过最细致测试的软件,每1000行代码也会包含0.5-3个错误

8.1 Why/Who/What/When/How

  • 测试帮助找出bug,并修复它。测试只能证明有bug,而不能证明没有bug。
  • 程序员有责任为自己编写的代码进行测试,不要指望其他人来完成测试
  • 给代码中的函数,数据结构,类进行测试
  • 在编写代码的同时进行测试,越早越好,甚至可以在写代码前就编写好测试(面向测试编程)
  • 为每个发现的bug都编写一个测试,尽可能多的进行测试

8.2 测试类型

  • 单元测试:java中一般是对单个函数进行测试
  • 组件测试:单元测试的下一步,测试多个单元组成后的行为
  • 集成测试:系统多个组件之间组合测试
  • 回归测试:当发现bug修复后,就要进行回归测试,防止引入新的bug
  • 负载测试:确保代码如预期处理大量的输入数据,可以发觉与系统效率相关问题
  • 压力测试:很短时间代码处理大量输入数据,适用于高可用系统,用于确定软件的容量
  • 疲劳测试:在较高的负载下长时间运行,观测性能变化,可以检测出其他测试发现不了的bug,如内存泄露
  • 可用性测试:将软件放在现实世界环境中,看看用户觉得怎么样
  • 黑盒测试:又称为功能测试,测试者看不到代码是如何执行的,像个黑盒子,只执行前置预期操作,监督输出,数据或者操作是否预期
  • 白盒测试:白盒测试者能看到每行代码,知道所有逻辑,需要清楚每个分支作用,工作量比黑盒测试多多了,需要一些工具来计算测试覆盖率,否则白盒测试让人发疯

8.3 测试原则

  • 尽可能让代码测试自动进行,Java使用maven 的过程中就有自动进行测试的流程
  • 尽量设计代码便于测试,限制代码复杂度
  • 不要写死与系统其它部分的连接
  • 不要依赖全局变量
  • 结构化单元测试,由三部分组成,given(准备数据)- when(执行)-then(验证结果)

九、代码bug调试

bug 是软件构建的黑暗面,代码都是由人编写的,而人是会犯错的,我们都应该为错误承担责任。

如果问心有愧,有两种方法处理它,一种是推诿责任,这也是一种方便的编程工具,你可以不管它,认定这不是bug,而是feature;另一种就是找出来源并修复好。

9.1 bug种类

  • 从远处看

    • 编译失败 编译器能够告诉你哪错了,老鸟程序员能够轻松解决这种问题,而菜鸟则需要时间的沉淀
    • 运行时崩溃
    • 非预期行为 这个是最难的,程序展现错误的行为,可能是代码缺陷或者模块集成错误出现的
  • 从近处看

    • 句法错误 虽然可以通过编译,但是不是预期的,例如下面代码例子:语法是完全没问题的,但是多了个分号,导致永远会执行{}内的语句

      if (a); {
          return 1;
      }
      
    • 构建错误 c/c++项目使用makefile 构建软件,java可以用maven,注意版本和依赖

    • 语义bug

      • 段错误 c/c++ 指针访问那些未分配的内存
      • 内存溢出
      • 内存泄漏 忘记释放内存
      • 内存耗尽
      • 数学错误
      • 程序暂停 代码存在无限循环,或死锁等待等等

9.2 调试解决bug

  • 黄金法则:多动脑筋
  • 了解准备调试的代码,不能指望在不了解的代码中找到bug
  • 难易程度取决于对环境的掌控能力。不可能在线上调试代码,但是在测试环境复现取决于专业程度
  • 不要信任任何人的代码,从排除最不可能的原因开始

9.3 搜寻bug

  • 编译时错误 这种比较好处理,可能有很多条错误报出,你需要找到最先报告的那条,这是最有用的
  • 运行时错误 这种很可能是代码中某个你认为的条件并不成立,你需要一步步找到这个地方
    • 确定bug: 在bug追踪系统中记录这个 bug ( gitlab 中可以提个 issue ),写明错误的特征,全面地描述下问题,还可以在 git 上找到最近可以正常运行的版本,比较代码,方便更快找出错误
    • 使 bug 再现:记录哪些步骤可以复现 bug
    • 定位 bug:
      • 通过修改部分代码,再运行看看 bug 是否消失,这是很不成熟的做法
      • 二分查找,假设故障出现在1个20行代码的函数中,先在第10行位置打断点,判断预期,如果有则接着找前10行二分,否则后10行二分,重复如此
    • 理解问题:找到bug原因时,需要彻底研究并证明你是正确的
    • 创建测试:编写测试证明故障已修复
    • 修正bug:掌握了前两个步骤修复就很容易了
    • 证明确实修改好了bug:只有证明解决好bug,才算完成
    • 如果没修好:向他人求助,叙述下整个过程,也许会发现隐藏的某些关键点,他人的意见也许对你有帮助

9.4 修正bug

  • 修复bug时要十分小心,不要冒修改时破坏其它带代码的风险
  • 减少CV不了解的代码,会不经意间复制过来bug
  • 修复bug后要检查其它地方是否出现同样的问题

十、代码构建

代码构建很常见,在IDE中自带build功能,很多开发人员都依赖于IDE自带的构建体系,这样的构建过程往往只需要一个按钮就好,没有真正地掌握构建过程,作为老鸟程序员需要了解幕后发生的事情。

10.1 编程语言

  • 解释性语言:javascript,python等,通过解释器执行,一般速度比编译型慢
  • 编译型语言:c/c++等,将源代码转化为目标平台执行的机器指令,需要经过编译、链接等操作
  • 字节编译型语言:Java/C#等,先 生成字节码,再在虚拟机中执行

10.2 构建系统

  • 单个文件的编译和运行很简单
  • 多个模块编译,链接需要脚本来控制顺序
  • 更改了代码需要重新编译
  • c语言可以通过make来实现构建系统,这是c/c++开发者必须掌握的,
  • java 构建系统老牌是maven,现在gradle也如日中天,推荐两者都要学习
  • 掌握构建系统很重要,否则别人的代码,或者发布在github上的源码你都不知道怎么编译

十一、代码优化

11.1 优化是什么

软件优化可能在实际中表现下面含义:

  • 程序执行速度加快
  • 减少可执行文件大小
  • 提高代码质量
  • 提高计算结果准确性
  • 减少启动时间
  • 增加数据的吞吐量
  • 减少存储开销

不优化就是最好的优化,但那需要你从一开始就要考虑程序的性能,而优化的前提就是不要破坏代码的正确性

11.2 什么导致优化

  • 复杂性:导致代码运行越来越慢
  • 间接:设计一个中间层可以解决很多问题,但是也会导致大量缓慢的代码
  • 重复:重复计算,浪费宝贵的时间
  • 糟糕的设计:可能导致最基本、最细微、和最难解决的性能问题
  • I/O:总是等待输入输出的程序,性能总是糟糕的

11.3 为什么不进行优化

  • 历史上,早期的计算机性能不高,需要优化才能在合理时间内完成任务,现在算力暴涨,优化看起来不重要
  • 为了得到更快的代码,肯定在其他方面有所损害,如:可读性、复杂性增加、维护/扩展、引入冲突、时间精力
  • 可选择备选方案:更快的机器、寻找合适硬件、重新配置目标,后台异步运行缓慢的代码、处理会影响用户感知的接口、试用新版本编译器

11.4 为什么进行优化

  • 游戏领域需要逼真的图形,更快的反馈,需要良好优化的代码
  • DSP对大量的数据执行快速数字滤波,对效率有很高要求,需要优化代码
  • 嵌入式设备,往往没有充足的硬件让你获取合理的性能,需要对代码精雕细琢
  • 实时系统
  • 金融计算、科学研究的数值计算需要较高的性能
  • 如无必要,避免优化

11.5 优化的技术

  • 设计更改

    • 添加缓存或缓冲层
    • 运用池化思想,类似线程池、数据库连接池
    • 为速度牺牲精确度,只要你不被抓到,减少浮点数的精度是个例子
    • 更改数据的存储模式或在磁盘表示,例如:JDK9中String用byte[],而之前用char[]
    • 并行和线程是优化的主站前线
    • 如果能节省代码空间。可以放弃特定的语言功能
    • 移除不必要的代码
    • 为了获得更快的速度,牺牲一些设计质量。例如:减少中间层,增加耦合
    • 选择合适的数据结构和算法
  • 代码更改

    • 循环展开
    • 代码内嵌 inline
    • 常量叠算
    • 移到编译时
    • 强度折减 使用等价速度更快的操作,如:x/4 ==> x>>2
    • 子表达式 公共子表达式可避免重复计算
    • 无用代码删除
  • 容易接受的优化

    • 重复调用一个较慢的函数,不要频繁调用它,缓存结果,可能会导致不清晰但更快
    • 将函数在另一种语言实现。如Java中可通过JNI调用C/C++代码
    • 将工作推迟到必须做时在做
    • 对函数检查以避免多余的工作
    • 将不变的计算移到循环体外
    • 为复杂的计算使用查找表,用空间换时间
    • 利用异或中的短路
    • 不要重复进行相同的工作
    • 生成在运算之前才解压缩其代码的压缩的可执行文件
    • 尽量减少在远程计算机执行函数或访问网络的函数依赖
    • 了解目标部署和程序预期的运行方式
    • 编写模块化的代码

十二、代码安全

12.1 危险

  • 攻击你的系统的人,一般想要从系统中得到些什么,针对这些资源,需要保护好它们

  • 如果黑客通过社工获取到系统的权限,这是代码所不能防御的

  • 有缺陷的输入会被利用,获取整个机器的访问权限

  • 在公网上运行不安全的系统,会向整个世界打开

  • 通过特定漏洞,获取root权限

  • 信息未加密会被截获,以明码存储数据甚至内存中

  • 忘记注销,简单的密码,社工,过时软件

  • 任意的权限

  • 病毒

12.2 敌人

  • 攻击者可能是 普通的窃贼、能干的骇客、脚本小子、欺骗公司不忠诚的员工、被不公平解雇愤怒的离职员工
  • 无处不在的互联网,攻击可能来自各个时间,各个地方
  • 动机各有不同,也许是恶意的,或者好奇,投机取巧

12.3 脆弱的代码

  • 不安全的设计及体系结构
  • 缓冲溢出
  • 嵌入查询字符串
  • 竞争状态
  • 整数溢出

12.4 防范

  • 系统安装技术 即使你的代码再安全,但是所在系统不安全,也会受到攻击。
    • 在计算机只运行授信的软件
    • 运用防火墙等技术
    • 记录所有操作
    • 减少进入系统途径
    • 正确设置系统
    • 如果可以,安装“蜜罐”
  • 软件设计技术
    • 限制设计中的输入数量
    • 尽可能低权限运行程序
    • 避免开发不需要的功能
    • 不要依赖不可靠的库
    • 代码适应可以管理安全的环境
    • 避免存储敏感数据
    • 从用户获取机密数据要小心,不要显示密码
  • 代码实现技术
    • 防御性编程
    • 执行安全审核
    • 产生子进程要非常谨慎
    • 严格执行测试和调试
    • 所有操作都打包到原子事务中

参考

  1. 编程匠艺

你可能感兴趣的:(software,编程,编程匠艺,读书笔记,感悟,道,代码)