了解一些技巧助你减少代码查错时间。

-- Maria Mckinley

在周五的下午三点钟(为什么是这个时间?因为事情总会在周五下午三点钟发生),你收到一条通知,客户发现你的软件出现一个错误。在有了初步的怀疑后,你联系运维,查看你的软件日志以了解发生了什么,因为你记得收到过日志已经搬家了的通知。

结果这些日志被转移到了你获取不到的地方,但它们正在导入到一个网页应用中——所以到时候你可以用这个漂亮的应用来检索日志,但是,这个应用现在还没完成。这个应用预计会在几天内完成。我知道,你觉得这完全不切实际。然而并不是,日志或者日志消息似乎经常在错误的时间消失不见。在我们开始查错前,一个忠告:经常检查你的日志以确保它们还在你认为它们应该在的地方,并记录你认为它们应该记的东西。当你不注意的时候,这些东西往往会发生令人惊讶的变化。

好的,你找到了日志或者尝试了呼叫运维人员,而客户确实发现了一个错误。甚至你可能认为你已经知道错误在哪儿。

你立即打开你认为可能有问题的文件并开始查错。

1、先不要碰你的代码

阅读代码,你甚至可能会想到该阅读哪些部分。但是在开始搞乱你的代码前,请重现导致错误的调用并把它变成一个测试。这将是一个集成测试,因为你可能还有其他疑问,目前你还不能准确地知道问题在哪儿。

确保这个测试结果是失败的。这很重要,因为有时你的测试不能重现失败的调用,尤其是你使用了可以混淆测试的 web 或者其他框架。很多东西可能被存储在变量中,但遗憾的是,只通过观察测试,你在测试里调用的东西并不总是明显可见的。当我尝试着重现这个失败的调用时,我并不是说我要创建一个可以通过的测试,但是,好吧,我确实是创建了一个测试,但我不认为这特别不寻常。

从自己的错误中吸取教训。

2、编写错误的测试

现在,你有了一个失败的测试,或者可能是一个带有错误的测试,那么是时候解决问题了。但是在你开干之前,让我们先检查下调用栈,因为这样可以更轻松地解决问题。

调用栈包括你已经启动但尚未完成地所有任务。因此,比如你正在烤蛋糕并准备往面糊里加面粉,那你的调用栈将是:

  • 做蛋糕
  • 打面糊
  • 加面粉

你已经开始做蛋糕,开始打面糊,而你现在正在加面粉。往锅底抹油不在这个列表中,因为你已经完成了,而做糖霜不在这个列表上因为你还没开始做。

如果你对调用栈不清楚,我强烈建议你使用 Python Tutor ,它能帮你在执行代码时观察调用栈。

现在,如果你的 Python 程序出现了错误, Python 解释器会帮你打印出当前调用栈。这意味着无论那一时刻程序在做什么,很明显错误发生在调用栈的底部。

3、始终先检查调用栈底部

在栈底你不仅能看到发生了哪个错误,而且通常可以在调用栈的最后一行发现问题。如果栈底对你没有帮助,而你的代码还没有经过代码分析,那么使用代码分析是非常有用的。我推荐 pylint 或者 flake8。通常情况下,它会指出我一直忽略的错误的地方。

如果错误看起来很迷惑,你下一步行动可能是用 Google 搜索它。如果你搜索的内容不包含你的代码的相关信息,如变量名、文件等,那你将获得更好的搜索结果。如果你使用的是 Python 3(你应该使用它),那么搜索内容包含 Python 3 是有帮助的,否则 Python 2 的解决方案往往会占据大多数。

很久以前,开发者需要在没有搜索引擎的帮助下解决问题。那是一段黑暗时光。充分利用你可以使用的所有工具。

不幸的是,有时候问题发生在更早阶段,但只有在调用栈底部执行的地方才显现出来。就像当蛋糕没有膨胀时,忘记加发酵粉的事才被发现。

那就该检查整个调用栈。问题更可能在你的代码而不是 Python 标准库或者第三方包,所以先检查调用栈内你的代码。另外,在你的代码中放置断点通常会更容易检查代码。在调用栈的代码中放置断点,然后看看周围是否如你预期。

“但是,玛丽,”我听到你说,“如果我有一个调用栈,那这些都是有帮助的,但我只有一个失败的测试。我该从哪里开始?”

pdb,一个 Python 调试器。

找到你代码里会被这个调用命中的地方。你应该能够找到至少一个这样的地方。在那里打上一个 pdb 的断点。

一句题外话

为什么不使用 print 语句呢?我曾经依赖于 print 语句。有时候,它们仍然很方便。但当我开始处理复杂的代码库,尤其是有网络调用的代码库,print 语句就变得太慢了。我最终在各种地方都加上了 print 语句,但我没法追踪它们的位置和原因,而且变得更复杂了。但是主要使用 pdb 还有一个更重要的原因。假设你添加一条 print 语句去发现错误问题,而且 print 语句必须早于错误出现的地方。但是,看看你放 print 语句的函数,你不知道你的代码是怎么执行到那个位置的。查看代码是寻找调用路径的好方法,但看你以前写的代码是恐怖的。是的,我会用 grep 处理我的代码库以寻找调用函数的地方,但这会变得乏味,而且搜索一个通用函数时并不能缩小搜索范围。pdb 就变得非常有用。

你遵循我的建议,打上 pdb 断点并运行你的测试。然而测试再次失败,但是没有任何一个断点被命中。留着你的断点,并运行测试套件中一个同这个失败的测试非常相似的测试。如果你有个不错的测试套件,你应该能够找到一个这样的测试。它会命中了你认为你的失败测试应该命中的代码。运行这个测试,然后当它运行到你的断点,按下 w 并检查调用栈。如果你不知道如何查看因为其他调用而变得混乱的调用栈,那么在调用栈的中间找到属于你的代码,并在堆栈中该代码的上一行放置一个断点。再试一次新的测试。如果仍然没命中断点,那么继续,向上追踪调用栈并找出你的调用在哪里脱轨了。如果你一直没有命中断点,最后到了追踪的顶部,那么恭喜你,你发现了问题:你的应用程序名称拼写错了。

没有经验,小白,一点都没有经验。

4、修改代码

如果你仍觉得迷惑,在你稍微改变了一些的地方尝试新的测试。你能让新的测试跑起来么?有什么是不同的呢?有什么是相同的呢?尝试改变一下别的东西。当你有了你的测试,以及可能也还有其它的测试,那就可以开始安全地修改代码了,确定是否可以缩小问题范围。记得从一个新提交开始解决问题,以便于可以轻松地撤销无效地更改。(这就是版本控制,如果你没有使用过版本控制,这将会改变你的生活。好吧,可能它只是让编码更容易。查阅“ 版本控制可视指南 ”,以了解更多。)

5、休息一下

尽管如此,当它不再感觉起来像一个有趣的挑战或者游戏而开始变得令人沮丧时,你最好的举措是脱离这个问题。休息一下。我强烈建议你去散步并尝试考虑别的事情。

6、把一切写下来

当你回来了,如果你没有突然受到启发,那就把你关于这个问题所知的每一个点信息写下来。这应该包括:

  • 真正造成问题的调用
  • 真正发生了什么,包括任何错误信息或者相关的日志信息
  • 你真正期望发生什么
  • 到目前为止,为了找出问题,你做了什么工作;以及解决问题中你发现的任何线索。

有时这里有很多信息,但相信我,从零碎中挖掘信息是很烦人。所以尽量简洁,但是要完整。

7、寻求帮助

我经常发现写下所有信息能够启迪我想到还没尝试过的东西。当然,有时候我在点击求助邮件(或表单)的提交按钮后立刻意识到问题是是什么。无论如何,当你在写下所有东西仍一无所获时,那就试试向他人发邮件求助。首先是你的同事或者其他参与你的项目的人,然后是该项目的邮件列表。不要害怕向人求助。大多数人都是友善和乐于助人的,我发现在 Python 社区里尤其如此。

Python 七步捉虫法的更多相关文章

  1. 七步精通Python机器学习--转载

    作者简介: Matthew Mayo    翻译:王鹏宇 开始.这是最容易令人丧失斗志的两个字.迈出第一步通常最艰难.当可以选择的方向太多时,就更让人两腿发软了. 从哪里开始? 本文旨在通过七个步骤, ...

  2. python 装饰器 第七步:带有参数的装饰器

    #第七步:带有参数的装饰器 #两个基本函数用同一个装饰器装饰 def outer(arg): print(arg) #这是装饰器的代码 def kuozhan(func): print(func) # ...

  3. T117897 七步洗手法 / PJT1(洛谷)

    题目:现在有n个人需要依次使用1个洗手池洗手,进行一步洗手需要1单位时间.他们每个人至少会进行一步洗手,但是却不一定进行了完整的七部洗手. 现在你知道了他们总共的洗手时间为t,请你推测他们有多少人进行 ...

  4. Python实现客观赋权法

    本文从阐述Python实现客观赋权法的四种方式: 一. 熵权法 二. 因子分析权数法(FAM) 三. 主成分分析权数法(PCA) 四. 独立性权系数法 Python实现客观赋权法,在进行赋权前,先导入 ...

  5. Python爬虫教程-01-爬虫介绍

    Spider-01-爬虫介绍 Python 爬虫的知识量不是特别大,但是需要不停和网页打交道,每个网页情况都有所差异,所以对应变能力有些要求 爬虫准备工作 参考资料 精通Python爬虫框架Scrap ...

  6. Python爬虫教程-12-爬虫使用cookie爬取登录后的页面(人人网)(上)

    Python爬虫教程-12-爬虫使用cookie(上) 爬虫关于cookie和session,由于http协议无记忆性,比如说登录淘宝网站的浏览记录,下次打开是不能直接记忆下来的,后来就有了cooki ...

  7. python 图像处理中二值化方法归纳总结

    python图像处理二值化方法 1. opencv 简单阈值 cv2.threshold 2. opencv 自适应阈值 cv2.adaptiveThreshold 3. Otsu's 二值化 例子: ...

  8. 《Power Query数据清洗实战》捉虫……

    先道歉,<Power Query数据清洗实战>里,有虫-- 谢谢大家帮忙捉虫了. 谢谢法叔,他捉了四只--(汗) 112页第倒第二行,[追加查询],应是[合并查询]. 151.154.15 ...

  9. python排序之二冒泡排序法

    python排序之二冒泡排序法 如果你理解之前的插入排序法那冒泡排序法就很容易理解,冒泡排序是两个两个以向后位移的方式比较大小在互换的过程好了不多了先上代码吧如下: 首先还是一个无序列表lis,老规矩 ...

随机推荐

  1. redis吊锤面试官,这篇足够了!

    原理篇 redis 时单线程的为什么还能那么快? 数据都在内存中,运算都是内存级别的运算. redis既然是单线程的为什么能处理那么多的并发数? 多路复用,操作系统时间轮训epoll 函数作为选择器, ...

  2. spring-cloud-gateway动态路由

    概述 线上项目发布一般有以下几种方案: 停机发布 蓝绿部署 滚动部署 灰度发布 停机发布 这种发布一般在夜里或者进行大版本升级的时候发布,因为需要停机,所以现在大家都在研究 Devops 方案. 蓝绿 ...

  3. Python3实现xml转json文件

    使用了Python的 xml.etree.ElementTree 库,Python版本Python 3.6.6 from xml.etree import ElementTree LISTTYPE = ...

  4. iOS 响应链

    一.UIResponder app 使用响应者对象接收和处理事件,只有继承 UIResponder 的类,才能处理事件. UIApplication.UIView.UIViewController 都 ...

  5. RecyclerView 的简单使用

    自从 Android 5.0 之后,google 推出了一个 RecyclerView 控件,他是 support-v7 包中的新组件,是一个强大的滑动组件,与经典的 ListView 相比,同样拥有 ...

  6. 1047 Student List for Course (25分)

    Zhejiang University has 40,000 students and provides 2,500 courses. Now given the registered course ...

  7. Ring 笔记 - 核心概念

    Ring 是一个在 Clojure 中的对于 HTTP 的抽象,是构建 Web 应用的底层接口和库,类似于 Java 中的 Servlet 核心概念 Handler Handler 是一个定义web应 ...

  8. Elasticsearch 核心术语概念

    Elasticsearch 相当于一个关系型数据库 索引 index 类型 type 文档 document 字段 fields 跟关系型数据库对比 Elasticsearch 相当于一个数据库 索引 ...

  9. docker 容器容器之间网络通信 docker-compose.yaml 配置固定ip

    1.创建自己的桥接网络 $ docker network create --subnet=172.18.0.0/16 mynetwork 2.docker-compose.yaml 文件格式demo ...

  10. django中设置定时任务

    django中设置定时任务 在django中设置定时任务我们可以借用django-crontab这个第三包来实现 django-crontab只能在linux系统下使用 安装: pip install ...