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