代码调试,教给你(代码调试工具有哪些)
off999 2025-04-11 04:19 20 浏览 0 评论
昨天我和一些朋友一起调试代码,他们做程序员这一行都不太久,我向他们展示了一些代码调试技巧。
今天早上我在想,我应该如何教授他们学习代码调试?我在Twitter上发了一条推文说,我从来没有见过任何好的调试代码的指南。像往常一样,我得到了很多有帮助的回答,现在我对如何教授代码调试技巧/描述调试过程有了些想法。
调试资源
我希望有更多的关于代码调试的书籍/指南,在这里我有两个推荐:
David Agans 写的《Debugging》:有几个人向我推荐了这本《Debugging》,它看起来是一本很好的关于代码调试的书,用简短的篇幅阐述了一些代码调试策略。这本书我还没有读过,但是我已经买了一本,我希望我读完后决定是否应该推荐它。这本书中阐述的一些代码调试应该遵循的规则似乎很有道理,比如说“了解系统”,“让它失败”,“别想了,先看看”,“分而治之”,“一次只改变一件事情”,“保持审查详细记录”,“从一个新的角度看问题”,和“如果你没有修复它,它就不会修复”等等。另外,这本书还有一张吸引人的的代码调试的海报。
John Regehr写的“How to debug(如何调试)”:How to Debug是John Regehr基于他自己在大学里教授嵌入式系统课程的经验写的一篇非常好的博客文章(
https://blog.regehr.org/archives/199),里面有很多针对代码调试的好建议。他还发表了一篇博文(
https://blog.regehr.org/archives/849)来评论4本关于代码调试的书籍,包括了David Agans s写的这本《Debugging》。
重现你的bug(但是要怎么做?)
接下来在这篇文章里,我将尝试整理大家针对我的关于代码调试的推文发来的各种不同的观点和看法。
从这些看法中很明显地看出,所有人都同意这一点:如果你想弄清楚发生了什么,那么能够持续地重现一个bug非常重要。我对如何做到这一点有直觉,但是对于怎样才能从“我看到这个bug两次”跨越到“我可以根据需要在笔记本电脑上持续地再现这个bug”这一点,我不知道怎么解释,而且我想知道你用来调试的技术是否依赖于这些不同的开发领域:后端web开发,前端开发,移动开发,游戏开发,C++编程,嵌入式开发等等。
快速重现bug
所有人也都同意,能够快速地重现bug是非常有用的(如果每次更改都需要3分钟来检查是否有帮助,那么迭代就太慢了)。
这里有一些建议的方法:
对于那些需要在浏览器中进行很多次点击才能重现的bug,用Selenium记录你点击的内容,并让Selenium重播UI交互(详细的建议请见这里:
https://twitter.com/AnnieTheObscure/status/1142843984642899968);
如果你能够的话,编写一个重现错误的单元测试。这样做还有另外一个好处:如果这个单元测试有意义的话,你可以稍后将它添加到测试套件中;
编写一个脚本,或者找到一个命令行命令帮助你做它(比如curl MY_APP.local/whatever))。
承认bug可能是你写的代码引起
有时我看到一个问题,我会说“哦,X库有个bug”,或者“哦,这是DNS错误造成的”,或者“哦,不是我的代码,而是其它地方的错误造成的”。确实有时候一个bug不是我写的代码造成的!但一般来说,在一个已经验证的库和我上个月编写的代码之间,通常是我上个月编写的代码才是真正的问题所在 。
开始实验
@act_gardnerd在Twitter上给出了一个很好的简短的回答(
https://twitter.com/act_gardner/status/1142838587437830144),解释了你在再现你的bug之后,你需要做什么。原文如下:
我试着鼓励人们首先对这个bug有个全面的理解,比如说:什么正在发生?你期望会发生什么?什么时候会发生?什么时候不发生?然后运用他们对系统的心理模型来猜测可能发生的破坏,并进行实验。实验可以是更改或删除代码,从一个REPL调用API,尝试新的输入,使用调试器(debugger)或print语句来获取内存中的值。
我认为这里可能需要循环地重复以下步骤:
猜测可能发生的错误的某一个方面(比如说,“这个变量被设置为X,它应该是Y”,或“发送到服务器的请求是错误的”,或“这段代码根本没有运行过”等等)。
做实验来验证这个猜测。
重复循环,直到你明白发生了根源所在。
一次只改变一件事情——所有人都肯定地同意,在做实验来验证一个假设时,一次只改变一件事情是很重要的。
检查你的假设
很多调试工作都基于一个假设:你确定的事情是真的(比如说:“等一下,这个请求是要发送到新服务器,对吧,不是旧服务器????)。但是实际上……不是真的。我试图列出一些常见的错误假设。下面是一些例子:
此变量设置为X(“该文件名绝对正确”);
该变量的值不可能在X和Y之间变化;
这段代码以前没有问题;
此函数执行X;
我正在编辑正确的文件;
我写的那一行代码不可能有任何拼写错误,只是一行代码而已;
文档是正确的;
我正在查看的代码在某个时刻被执行;
这两段代码是按顺序执行的,而不是并行执行的;
这段代码在调试模式和发布模式下编译(使用或不使用-O2开关,或…)时,会做同样的事情;
编译器没有错误(这是故意放在最后的一个错误,很少有人会认为编译器会出错)。
获取信息的奇招
有很多正常的方法可以做实验来检查你对代码所做的假设/猜测(比如,打印变量值,使用调试器,等等)。但是,有时候你所处的环境更为困难,你无法打印出内容,也无法访问调试器(可能是执行这些操作不方便,因为要处理的事件太多)。这里有一些应对方法:
在手机上添加声音:“在移动开发世界里,这条建议给了我很大帮助。Xcode可以在你遇到断点时播放声音(并且代码不停止而继续执行下去)。我把它们放在代码中的某个位置,然后听嗡嗡的叮当声来指示代码中发生的错误”(欲知详情,请查看上面提到的推文)。
关于使用Xcode播放iOS代码调试的声音,这里(
https://qnoid.com/2013/06/08/Sound-Debugging.html)有一些很有趣的讨论。
添加发光二极管(LED):“很久以前,当我们在Transputer网格上做嵌入式开发时,我们将发光二极管连接到每个芯片的一个未使用的管脚上。它在诊断并行性问题上出奇地有效。”
string: “我的网络教授告诉我这样一个故事,在早期的以太网时代,他在施乐公司(Xerox)看到了一个黑客:他使用一个带有放大器,马达和一根绳子的同轴电缆接头。网络越忙,线就转得越快。”
Peep是一个“Network Auralizer”,可以将系统上发生的事情转换成声音。我花了10分钟试图让它编译,但迄今为止失败了,但它看起来很有趣,我想继续尝试它!!
这里我想重点强调一下:信息是最重要的,你需要做任何必要的事情来获取信息。
编写代码使其更易于调试
一些人提到的另外一个观点是:我们可以改进程序,使其更加易于调试。tef对此有一篇很好的文章:编写易于删除和调试的代码(
https://programmingisterrible.com/post/173883533613/code-to-debug)。我觉得下面这一点很正确:
可调试的代码并不一定干净,而充斥着检查或错误处理的代码很少能让人愉快地阅读。
我个人认为:“易于调试”的一种解释是“每当出现错误时,程序都会以易于理解的方式向你准确地报告发生的事情”。每当我的程序有问题并且报告这样的错误信息“Error:无法连接到某个IP的端口443:连接超时”时,我都想说:“谢谢,这就是我想知道的事情”。有了这样的错误信息,我就可以检查我是否需要修复防火墙,或者我是否由于某种原因得到了错误的IP地址。
最近我碰到一个简单的例子:我向一个我写的服务器发出请求,得到的回应是“upstream connect error or disconnect/reset before headers”。这是一个nginx错误,在本例中基本上是因为“程序在响应一个请求而发送任何内容之前崩溃了”。找出崩溃的原因是很容易的,但是有更好的错误处理方式(返回错误而不是崩溃)可以节省我一点时间,因为我不必去检查崩溃的原因,我只需阅读错误信息,知道发生了什么就可以了。
错误消息好过无提示的程序失败
为了更接近“每次出现错误时,程序都会以一种易于理解的方式向你报告发生的事情”的梦想,你还需要遵守这条“立即返回错误消息”的铁律,而不是默默地向另一个功能写入不正确的数据或者传递无意义的数据,谁都不知道它会拿这些数据做什么,结果只会让你头痛。要做到这点,意味着你要添加如下代码:
if UNEXPECTED_THING:
raise "oh no THING happened"
获得正确的错误信息并不容易,因为你在程序当中哪里犯了错误并不总是显而易见的,但是这样做确实有很大帮助。
failure:返回一堆错误,而不仅仅是一个错误
为了返回更加易于调试的有用错误,Rust提供了一个非常令人难以置信的错误处理库failure,它基本于允许你返回一系列错误,而不仅仅是一个错误,因此你可以打印出一堆错误,如:
"error starting server process" caused by
"error initializing logging backend" caused by
"connection failure: timeout connecting to 1.2.3.4 port 1234".
这比仅仅返回connection failure: timeout connecting to 1.2.3.4 port 1234本身要有用得多,因为它还告诉你和IP 1.2.3.4有关的其它一些重要的信息(比如上面这个错误就显示它和日志后端有关!)。我认为它也比返回带有堆栈跟踪信息的connection failure: timeout connecting to 1.2.3.4 port 1234的错误信息更加有用:因为它将堆栈跟踪信息中的关键的出错部分总结出来,这样你就不需要读取堆栈跟踪中的每一行(因为其中一些可能不相关!).
其它语言中的类似于Rust语言failure库的工具有:
Go语言:它的习惯用法似乎是把你的一堆错误串成一个大字符串,这样你就得到了一长串的像这样的错误提示:“error:第一个错误:error:第二个错误:error:第二个错误”。它工作得很好,但是它的错误信息的结构比failure库能提供的要差得多。
Java语言:我听说Java可以给出异常的原因(Causes of exceptions), 但是我自己没有用过。
Python 3:你可以使用raise ... from设置异常的“__cause__”属性,然后你的异常将被这句话分开:The above exception was the direct cause of the following exception:..
如果你知道其它语言中如何处理程序错误的方法,请告诉我,我会很感兴趣!
了解错误消息的含义
我经常理所当然地认为代码调试的一个子技巧是:正确理解错误消息的含义!我在这里(
https://pythonforbiologists.com/29-common-beginner-errors-on-one-page/)看到了这个很好的图形,它解释了常见的Python错误以及它们的含义,并且将一些错误如 NameError, IOError,等等分离开来。
我认为解释错误消息很困难的一个原因是理解一个新的错误消息可能意味着学习一个新的概念。比如,NameError可能代表“你的代码使用了一个它定义的变量作用域之外的一个变量”,但是要真正理解它的意思,你首先得搞清楚什么是变量作用域。我在学习Rust的时候经常碰到这样的问题,Rust编译器会提示我“你有一个奇怪的lifetime错误”,而我就会想“呃,好吧,Rust,我知道了,现在我就去搞清楚lifetime是如何工作的!”
很多时候,错误消息都往往是由一个与消息文本根本不相干的错误引起的,比如说“upstream connect error or disconnect/reset before headers”这个错误可能意味着“Julia,你的服务器崩溃了!”当你切换到一个新的开发领域时,理解错误消息的技能通常是不可转移的(假如我明天开始大量地编写React或其它编程语言的代码,一开始我可能根本不知道任何错误消息的含义!)。所以这个问题绝对不仅仅是初学者需要面临的问题。
结语
当我在谈到代码调试技巧时,我总感觉我遗漏了一件重要的事情,那就是对人们在代码调试中哪里会遇到困难的一种更深入的理解。通常我们很容易说:“好吧,你需要重现这个问题。那么先让我们进行最小化的重现,你可以开始猜测和验证你的猜测,改进你对系统的思维模式,找出问题所在,然后解决问题。最后写一个测试,希望它不再重现”,但是,实际上,我们很难确定人们到底会在哪里遇到困难和最难的部分是什么。对我自己而言代码调试最难的地方是什么,我通常会有点思路。但是对那些新人而言,代码调试最难的地方是什么,依然是云里雾里,毫无头绪。
声明:本文来源于网络,如有侵权,请联系我删除。
相关推荐
- 软件测试|Python requests库的安装和使用指南
-
简介requests库是Python中一款流行的HTTP请求库,用于简化HTTP请求的发送和处理,也是我们在使用Python做接口自动化测试时,最常用的第三方库。本文将介绍如何安装和使用request...
- python3.8的数据可视化pyecharts库安装和经典作图,值得收藏
-
1.Deepin-linux下的python3.8安装pyecharts库(V1.0版本)1.1去github官网下载:https://github.com/pyecharts/pyecharts1...
- 我在安装Python库的时候一直出这个错误,尝试很多方法,怎么破?
-
大家好,我是皮皮。一、前言前几天在Python星耀群【我喜欢站在一号公路上】问了一个Python库安装的问题,一起来看看吧。下图是他的一个报错截图:二、实现过程这里【对不起果丹皮】提示到上图报错上面说...
- 自动化测试学习:使用python库Paramiko实现远程服务器上传和下载
-
前言测试过程中经常会遇到需要将本地的文件上传到远程服务器上,或者需要将服务器上的文件拉到本地进行操作,以前安静经常会用到xftp工具。今天安静介绍一种python库Paramiko,可以帮助我们通过代...
- Python 虚拟环境管理库 - poetry(python虚拟环境virtualenv)
-
简介Poetry是Python中的依赖管理和打包工具,它允许你声明项目所依赖的库,并为你管理它们。相比于Pipev,我觉得poetry更加清爽,显示更友好一些,虽然它的打包发布我们一般不使...
- pycharm(pip)安装 python 第三方库,时下载速度太慢咋办?
-
由于pip默认的官方软件源服务器在国外,所以速度慢,导致下载时间长,甚至下载会频繁中断,重试次数过多时会被拒绝。解决办法1:更换国内的pip软件源即可。pip指定软件源安装命令格式:pipinsta...
- 【Python第三方库安装】介绍8种情况,这里最全看这里就够了!
-
**本图文作品主要解决CMD或pycharm终端下载安装第三方库可能出错的问题**本作品介绍了8种安装方法,这里最全的python第三方库安装教程,简单易上手,满满干货!希望大家能愉快地写代码,而不要...
- python关于if语句的运用(python中如何用if语句)
-
感觉自己用的最笨的方式来解这道题...
- Python核心技术——循环和迭代(上)
-
这次,我们先来看看处理查找最大的数字问题上,普通人思维和工程师思维有什么不一样。例如:lst=[3,6,10,5,7,9,12]在lst列表中寻找最大的数字,你可能一眼能看出来,最大值为...
- 力扣刷题技巧篇|程序员萌新如何高效刷题
-
很多新手初刷力扣时,可能看过很多攻略,类似于按照类型来刷数组-链表-哈希表-字符串-栈与队列-树-回溯-贪心-动态规划-图论-高级数据结构之类的。可转念一想,即...
- “千万别学我!从月薪3000到3万,我靠这3个笨方法逆袭”
-
3年前,我还在为房租而忧心忡忡,那时月薪仅有3000元;如今,我的月收入3万!很多人都问我是如何做到的,其实关键就在于3个步骤。今天我毫无保留地分享给大家,哪怕你现在工资低、缺乏资源,照着做也能够实...
- 【独家攻略】Anaconda秒建PyTorch虚拟环境,告别踩坑,小白必看
-
目录一.Pytorch虚拟环境简介二.CUDA简介三.Conda配置Pytorch环境conda安装Pytorch环境conda下载安装pytorch包测试四.NVIDIA驱动安装五.conda指令一...
- 入门扫盲:9本自学Python PDF书籍,让你避免踩坑,轻松变大神!
-
工作后在学习Python这条路上,踩过很多坑。今天给大家推荐9本自学Python,让大家避免踩坑。入门扫盲:让你不会从一开始就从入门到放弃1《看漫画学Python:有趣、有料、好玩、好用》2《Pyth...
- 整蛊大法传授于你,不要说是我告诉你的
-
大家好,我是白云。给大家整理一些恶搞代码,谨慎使用!小心没朋友。1.电脑死机打开无数个计算器,直到死机setwsh=createobject("wscript.shell")do...
- python 自学“笨办法”7-9章(笨办法学python3视频)
-
笨办法这本书,只强调一点,就是不断敲代码,从中增加肌肉记忆,并且理解和记住各种方法。第7章;是更多的打印,没错就是更多的打印第八章;打印,打印,这次的内容是fomat的使用与否f“{}{}”相同第九...
你 发表评论:
欢迎- 一周热门
-
-
python 3.8调用dll - Could not find module 错误的解决方法
-
加密Python源码方案 PyArmor(python项目源码加密)
-
Python3.8如何安装Numpy(python3.6安装numpy)
-
大学生机械制图搜题软件?7个受欢迎的搜题分享了
-
编写一个自动生成双色球号码的 Python 小脚本
-
免费男女身高在线计算器,身高计算公式
-
将python文件打包成exe程序,复制到每台电脑都可以运行
-
Python学习入门教程,字符串函数扩充详解
-
Python数据分析实战-使用replace方法模糊匹配替换某列的值
-
Python进度条显示方案(python2 进度条)
-
- 最近发表
-
- 软件测试|Python requests库的安装和使用指南
- python3.8的数据可视化pyecharts库安装和经典作图,值得收藏
- 我在安装Python库的时候一直出这个错误,尝试很多方法,怎么破?
- 自动化测试学习:使用python库Paramiko实现远程服务器上传和下载
- Python 虚拟环境管理库 - poetry(python虚拟环境virtualenv)
- pycharm(pip)安装 python 第三方库,时下载速度太慢咋办?
- 【Python第三方库安装】介绍8种情况,这里最全看这里就够了!
- python关于if语句的运用(python中如何用if语句)
- Python核心技术——循环和迭代(上)
- 力扣刷题技巧篇|程序员萌新如何高效刷题
- 标签列表
-
- python计时 (54)
- python安装路径 (54)
- python类型转换 (75)
- python进度条 (54)
- python的for循环 (56)
- python串口编程 (60)
- python写入txt (51)
- python读取文件夹下所有文件 (59)
- java调用python脚本 (56)
- python操作mysql数据库 (66)
- python字典增加键值对 (53)
- python获取列表的长度 (64)
- python接口 (63)
- python调用函数 (57)
- python qt (52)
- python人脸识别 (54)
- python斐波那契数列 (51)
- python多态 (60)
- python命令行参数 (53)
- python匿名函数 (59)
- python打印九九乘法表 (65)
- centos7安装python (53)
- python赋值 (62)
- python异常 (69)
- python元祖 (57)