代码
调试技术
数据库
- 《Getting started with impala》
- 《mysql 必知必会》
- 《mysql 性能调优与架构实践》
- 《Mysql 技术内幕 InnoDB 存储引擎》
- 《Redis 实战》
- 《Redis 深度历险核心原理和应用实践》
- 《redis设计与实现》
- 《七周七数据库》
- 《深入浅出mysql》
- 《高性能mysql第三版》
- 《MySQL是怎样运行的》
前端
GOLANG
- 《1 The Go Programming Language》
- 《2 The Go Programming Language》
- 《3 The Go Programming Language》
- 《Build Web Application With Golang》
- 《Go101》
- 《Network Programming with go》
- 《Building Microservices With Go》
- 《Building Restful Web Services with Go》
- 《Concurrency In Go》
- 《Go In Action(Go 实战)》
- 《Go学习笔记语言详解》
- 《Go学习笔记源码剖析》
- 《Go语言编程》
JAVA
网络
心理学
PYTHON
创业
UNIX/LINUX
分布式
系统设计
搜索引擎
开发工具
- 《Practical Vim》
- 《Vim8文本处理实战》
- 《Learn vim scrpt the hard way》
- 《Pro Git》
- 《Mastering Vim》
- 《Mastering Vim Quickly》
思维
源码
网站架构微服务
- 《微服务架构设计模式》
- 《从0开始学架构》
- 《web scalavility for startup engineers》
- 《designdatainstensive_application》
- 《designdatainstensive_application2》
- 《clean_architecture》
- 《微服务实战》
- 《微服务设计》
软件工程/项目管理
运维
金融理财
写作
互联网
区块链
技术演讲网课
- 《哔哩哔哩的go微服务实战》
- 《go业务基础库之Error&Context》
- 《Go同步和并发设计模式》
- 《300分钟吃透分布式缓存》
- 《DDD实战课》
- 《分布式技术原理与实战45讲》
- 《架构设计面试精讲》
- 《高并发系统设计40问》
- 《java并发编程78讲》
- 《中间件核心技术与实战讲》
职场
《软件调试修炼之道》
1. 山重水复疑无路
有效的调试步骤:
- 弄清楚为什么运行异常
- 修复问题
- 避免破坏其他部分
- 保持或者提高代码总体质量(可读性,架构,测试覆盖,性能等)
- 确保同样问题不要再次发生
实证方法:构建实验,观察结果
核心调试过程:
- 问题重现
- 问题诊断
- 缺陷修复
- 反思,吸取教训
澄清问题:
- 发生了什么,应该发生什么
- 一次一个问题
- 先检查简单的事情
2 重现问题
2.1 重现第一,提问第二
如何重现:
- 明确开始要做的事情(比如缺陷报告的步骤)
- 抓住重点
控制因素:
- 软件本身
- 运行环境
- 提供的输入
2.2 控制软件
2.3 控制环境
可能包含所有环境
2.4 控制输入
推测可能输入:
- 回溯工作
- 探测可能输入值
- 边界分析
- 分之覆盖
- 利用错误条件
- 引入随机性:模糊测试;生成模糊器;变异模糊器
- 记录输入值
- 日志框架。可以开启 or 关闭;日志级别;上下文(代码行数等);分析工具(ELK)
- 外部日志。代理服务器等
- 负载和压力。有些缺陷只有在某种压力才表现出来(高负载网络流量,有限的可用内存)
2.5 改进问题重现
2.5.1 最小化反馈周期。编辑-执行-重新问题时间最短
- 尽可能简单,通过二分法快速定位
- 自动化最小输入
- 最大限度减少所需时间,比如营造资源耗尽的局面。
2.5.2 不确定缺陷变为确定
软件之美在于确定性,但是不确定性从哪里来?
- 开始于不可预知的初始状态。从未经初始化的内存读取数据(强制初始化,或者内存完整性检测软件)
- 与外部系统交互: 精确控制从外部系统接收了什么
- 故意使用随机性: 伪随机数
- 多线程: 通过 sleep 等增加出现竞争的可能性
2.5.3 自动化
- 自动化测试
- 重放日志
2.6 无法重现怎么办
- 缺陷真的存在么?
- 在相同区域解决不同问题
- 让其他人参与其中。不同人的视角
- 充分利用用户群体
- 推测法:逻辑推理,想象出错的可能性
3 诊断
3.1 不要急于动手,试试科学方法
- 调试方法:
- 根据你对软件运行情况的理解,提出一个可以导致这种情况的假设
- 设计一个实验,证明假设是否正确
- 如果无法证明你的假设,重新设计然后再次实验
- 如果实验支持你的假设,继续证明,知道能够证明或者证伪
- 实验必须起到验证的作用。(与他人争辩试图推翻你的假设)
- 每次只做一个修改。多处修改可能导致错误的结论。
- 记录你做的调试。定期回顾你已经尝试过的实验和学到的东西,使用日记簿或者电子笔记。
- 不要忽略任何细节。凡是你不明白的都是潜在的缺陷。(Anything that you don't understand is potentially a bug)
- 写出假设。曾经做过的假设写到纸上
- 可以跟踪详细信息
- 确保不会忘记需要做的事情
- 短暂休息,可以尝试头脑风暴
3.2 相关策略
3.2.1 插桩
使用日志,但是不要引入额外的副作用影响了原始代码逻辑。增加日志的时候,不要同时修改代码逻辑。
3.2.2 分而治之
二分法,排除一部分模块或者代码的问题。
3.2.3 源码控制工具
git 找出历史提交,分析引入缺陷的提交
3.2.4 聚焦差异
找到特殊用例,缺陷的边界
3.2.5 向他人学习
网络搜索,同事求助
3.2.6 奥卡姆剃刀
其他条件相同情况下,最简单的解释是最好的。
3.3 调试器
代码检查,断点,单步调试,检查运行状态
- 开发初期调试器非常有用
- 让代码以特定方式运行
- 探究看不懂的代码
随着经验增加和TDD 思想出现,调试器使用会变少
3.4 陷阱
- 你做的修改是正确的么?如果没有,说明你没修改到点子上。尝试引入明显的失败比如断言让其暴露出来
- 验证假设。思维定势造成的先验假设可能会让你得不到结果
- 多重原因。问题有时候是复杂的,
- 对问题进行隔离,找到一个方法重现缺陷。
- 先找同一个区域内其他较明显的缺陷,扫清障碍,让缺陷更加明显
- 查出变化并且控制它。第三方系统或者数据库。
3.5 思维游戏
- 旁观调试法(小黄鸭)。求助其他人。倾听;提问;留意没有探究过的地方;了解接下来将会发生什么。解释问题会帮你理清思路
- 角色扮演
- 换换脑筋。潜意识帮助你
- 做些改变,什么改变都行。出去散个步最好
- 福尔摩斯原则:当你排除了一切不可能之后,无论它多么不可思议,它也一定是真相
3.6 验证诊断
- 向其他人解释你的诊断
- 检查源代码原始副本
- 多和其他人讨论,并假设你是错的
4 修复缺陷
4.1 清楚障碍
从一个干净的代码树开始修改。
4.2 测试
- 保证现有测试都可以通过
- 添加一个或者多个新的测试程序
- 修复 bug
- 证明修复起了作用
- 证明回归测试没有失败
4.3 修复原因而非修复现象
理解问题的症结所在
4.4 重构
重构:改善代码设计而不改变其行为的过程。
- 重构应该在有全面单测套件的安全网中才可以安全修改既有代码
- 重构或者修改功能,不能同时进行。 重构的同时决不能修改代码功能,同样也不能修复有缺陷的代码。(一次只做一件事)
4.5 签入
一次逻辑修改只做一次签入。方便快速回滚和定位代码问题。
4.6 审查代码
5. 反思
5.1 这到底是怎么搞的
- 如果缺陷仅仅发生在少数用户身上,你就要反思是否还有其他问题?
- 一个你以为会失败的 case,结果却成功了
5.2 哪里除出了问题?
五个为什么?
- 我们已经做到了么?
- 根本原因分析。责备没有用
- 需求是否正确,是否是模糊的,误解了
- 架构或者设计。有没有正确按照设计来做
- 测试。测试覆盖率足够么?
- 构造
5.3 它不会再发生了
- 自动验证,检查是否存在问题
- 和同事交流
- 重构代码避免被不正确使用
- 过程。检查工作过程
5.4 关闭循环
编码规范;测试规范;文档规范;报告/跟踪过程;设计指南;性能需求
6. 发现代码存在问题
6.1 缺陷追踪
- 使用缺陷追踪系统。
- 缺陷报告。具体,明确,详细的。
- 环境和配置报告。自动收集环境和配置信息,不要指望用户
6.2 与用户合作
- 简化流程
- 自动化
- 提供多种选择
- 尽量简单
- 模板不要死板
- 尊重用户隐私
有效的沟通:
- 心智模式。从用户角度设想一下
- 和非技术人员沟通
- 发布缺陷数据库
- 隐私问题;提供反馈;拜访用户
6.3 与支持人员协同工作
学会沟通;和 QA 搞好关系,以便在修复缺陷的过程中得到帮助。
7. 务实的零容忍策略
7.1 缺陷优先
早期修复缺陷是一个好的策略:
- 可能发现缺陷的过程(比如测试、代码审查、用户使用等)要连续贯穿整个开发过程
- 缺陷修复优先于其他任何事情
- 质量底下具有传染性。
- 破窗理论。发现缺陷就立马解决,不要留破窗户
7.2 调试的思维模式
既要完美,又要实用。接近零容忍,但是用务实的心态去实现。
7.3 自己来解决质量问题
面对大量bug 如何摆脱困境?
- 没有灵丹妙药。
- 停止开发有缺陷的程序。阻止事情恶化。
- 从不干净的代码中分离干净代码
- 缺陷分类
- 缺陷闪电战。集中较短时间专门修复缺陷
8 特殊案例
8.1 修复已经发布软件
集中精力减少风险。
8.2 向后兼容
将确定兼容问题加入到你的缺陷修复检查列表中
- 提供迁移方法
- 实现一个兼容模式。比如 word 就是这么做的
- 提供预警。java api 升级
- 不修复。极少数情况是一个务实的方案
8.3 并发
简单和控制
让并发软件中问题更少地发生不是一个可取修复办法。 修复并发缺陷时,避免实用 sleep 方法。
8.4 海森堡缺陷
观察者效应。尽量减少收集信息带来的副作用
8.5 性能缺陷
- 寻找瓶颈。性能分析器。通过检查代码有时候预测瓶颈是不可靠的。
- 没有瓶颈呢?是否是资源耗尽;垃圾收集;缓存丢失等
8.6 嵌入式软件
仿真器;硬件还是软件问题;
8.7 第三方软件缺陷
- 不要太快去指责。首先怀疑你自己的代码
- 报告别人代码中的缺陷。慎重自己直接修复
- linux 法则:眼睛足够多,bug 无处藏
- 报告问题:检查文档;尽可能给出足够信息
9. 理想的调试环境
9.1 自动化测试
- 结果明确,通过还是失败
- 环境独立
- 所有测试可以独立运行
- 全面覆盖
9.2 源程序控制
- 合理使用代码分支
- 坚持分之单一层级
- 设计 CI 构建所有正在活跃的分之
- 小的修改更加容易理解,合并和撤销
- 不要一次合并多个分之
9.3 自动构建
- 自动化构建整个过程,从开始到完成。
- 持续集成
- 静态分析(lint工具)。不要依赖未定义行为;谨慎对待编辑器警告
10. 让软件学会自己寻找缺陷
10.1 假设和断言
fail early,使用断言保护代码。
契约式编程:
- 先决条件。不如参数不能是空
- 后置条件。调用之后保持的条件,比如一个 http addHeader() 方法调用之后 headers map 比原来多一个
- 不变量。方法调用之前它的先决条件被满足就始终为真的数据。
支持开启和关闭断言功能。
软件在产品阶段应该是鲁棒的,在调试阶段应该是脆弱的。 断言是一个缺陷检测机制,不是一个错误处理机制。
10.2 调试版本
不要等待资源泄露表现出来,主动今早地检测它们。
11. 反模式
11.1 夸大优先级
质量差才是导致大量缺陷的原因。
- 定期清理缺陷
- 控制优先级
- 不要使用数字表示优先级
11.2 超级巨星
巨星效应会破坏团队。一开始匆忙实现看似非常高效率(所以被称为"巨星"),但是遗留了一堆棘手的烂摊子。
- 确保完成就是完成,需要功能被测试过了,检查过了,记录文档了。
- 任务分解为小任务
- 自负自责。谁搞得 bug 谁负责修理它。(有时候你可能要考虑拍屁股走人了,比如一堆 c 艹的烂摊子???)
11.3 维护团队
最终开发和维护都要由一个团队来完成,保持连续性。不要区分开发和维护团队。
11.4 救火模式
退一步,找出根本原因,再去解决问题
救火模式永远也不会修复任何质量问题。
11.5 重写
从心理学角度来看,开发新代码比重写旧的代码让人更加舒服。天生乐观会让我们低估了复制旧功能要付出的精力和时间。 避免彻头彻尾重写,增量式重写代码。
11.6 没有代码所有权
集体代码所有权(极限编程)
11.7 魔法
任何你不理解的事情都可能隐藏有缺陷。唯一的方法就是纪律,把你不理解的事物都当成缺陷。
如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。
绑定邮箱获取回复消息
由于您还没有绑定你的真实邮箱,如果其他用户或者作者回复了您的评论,将不能在第一时间通知您!
发布评论