Python部落(python.freelycode.com)组织翻译,禁止转载,欢迎转发。

下文是Aaron Maxwell投递的客座博文,他是Advanced Python Newsletter的作者。

错误代码千千万,在Python中,有一种反面教材是难搞之王。

在其他两位工程师每人花费三天的时间试图去搞定一个Unicode编码的“玄学”问题而徒劳无功后,我仅仅花费了一天时间就定位到了错误的子句,尽管很累,但是很开心。十分钟后,我们就有了应对该bug的方法。

我们本可以用十分钟而不是宝贵的七天来解决这个问题,这样的事实让我们很痛苦。当然,这样说也有点鲁莽……

下面的这段代码就是关键点,这一小段代码是Python开发者能够写出来的最具有自我毁灭性的代码片段之一:

这一段代码还有很多其他的写法,如“except Exception:”或者“except Exception as e”,这给后续的工作带来了很大的麻烦:忽略和隐藏了错误的发生,并且不给出任何提示,否则在一般情况下类似的问题是很容易解决的。

为什么我说这段代码是当今Python世界中最可怕的代码呢?

l人们写这段话是因为知道这里发生某种特定类型的异常,然而,捕获异常后却忽略所有的错误……甚至是那些不可预料的异常。

l当这个bug出现时——经常出现,因为生产环境中总是有这样的代码——你可能都不知道代码库的哪部分出现了错误。这可能会耗费上好几小时沮丧的时光去才能发现错误竟然是出现在try语句块中。

l就算你发现了错误,在你想要解决问题时,却发现缺少必要的提示信息。这个错误/异常的类型是什么?涉及到哪些调用和数据接口?错误最开始出现在哪一个文件的哪一行代码?

l更糟糕的是,这很有可能伤害到在当前代码上工作的工程师的士气,乐趣甚至是自尊。错误出现时,故障排查人员可能需要花费几个小时去理解代码。他们会觉得自己是个糟糕的码农,因为他们需要几个小时才能找到错误。事实上并不是。捕获了异常而又对错误放任不管,这样的问题很难定位,排除,修复。

无论是独自工作还是作为团体中一份子,在我作为Python民工的十年开发经历中,这是我遇到的最能够打击士气,降低生产力和应用可靠性的代码片段,如果你有其他更厉害的代码,欢迎讨论。

我们为什么会写出这样的代码?

当然,没有人故意写这样的代码给团队成员增加压力和破坏应用的可靠性。我们之所以写这个是因为在try语句块中,代码在某些特定情况下可能会执行失败。乐观地进行尝试并且捕获异常是解决这种问题的一种很优秀,很Python的做法。

更阴险的是,去捕获异常,然后不报出任何对应的处理并不是这个可怕的想法中最糟糕的时候,然而,当你按下保存按钮时,你就将你的代码处于“万劫不复”的深渊:

lBugs能够在开发过程中避免被发现地命运,最终会被推送到实际生产环境中。

l当你发现bug的存在之前,它可能已经存活了数分钟,数小时,数天甚至是数周。

l这样的bug很难定位。

l即使你知道哪里会出现异常,你也很难去修复这个bug。

注意,我并不是说不去捕获异常。有很多必需的理由去捕获异常并进行处理,但就是千万不要让它静悄悄地溜走。比如当你处理一项至关重要的事务时,你甚至不想让它简单地执行完就算了,比较明智的做法是插入try语句来捕获异常,并把相应的堆栈追踪信息记录使用logging.ERROR记录下来,然后再继续执行。

解决之道

所以如果你不想捕获范围太宽广的异常,有什么替代的办法呢?有两种选择。

在大多数情况下,最好建议你去捕获更加特定的异常,如:

这是你首先应当做的尝试。它需要你对相关的代码有一些了解,如此才可能推断出会发生什么类型的异常。当你是第一次写自己的代码时,这种方法还是比较简单的。不过当清理别人代码时,这就会让你痛苦万分的。

如果有些代码需要捕获所有的异常,如在顶层循环中长时间运行的程序,捕获的每个异常需要把相关的堆栈追踪信息写入日志或者文件,同时要有相应的时间戳。如果你是使用Python的logging模块,做起来时非常简单的,每个logger对象都有叫exception的方法,它接受一个字符串做参数。如果你在异常捕获的时候调用这个方法,捕获的异常连同堆栈追踪信息都会被自动记录下来。

这个日志包含错误信息,后面几行是堆栈追踪信息。

歪瑞一贼!

如果你的应用程序并不是使用logging模块来进行记录呢?假设你不想重构你的代码,你仅仅需要找到异常语句,并对堆栈追踪信息进行格式化输出。在Python3里很容易做到的。

在Python2中,你再多做一丢丢工作就好了:因为exception对象没有相对应的堆栈追踪信息。你可以在except语句块中调用sys.exc_info()函数来实现。

正如你所看到的,你可以把上述两种代码中的traceback-logging函数进行整合,从而可以忽略你是在Python2还是Python3下工作。

挽救措施

“好的吧,Aaron,你成功说服了我。我为我过去做的蠢事而流泪悔恨。我现在能做点什么补救措施?”我很高兴你这样问,下面的一些方法你可以尝试一下。

在你的编程规范中明确地制止它

如果你的团队有代码评审这一环节,你们应该会有代码编写的指导手册。如果没有,也很容易创建——就跟新建一个wiki页面一样。你需要把以下两条建议加入:

l如果有些代码需要捕获所有的异常,如在顶层中长时间运行的程序语句,那么每个捕获的异常都需要将其相关的堆栈追踪信息记录下来,包括时间戳。不仅仅是异常的类型和信息,也包括整个的追踪信息。

l对于其他的大多数异常来说,尽可能捕获精确类型的异常,如值错误,连接超时等。

为已经存在的except字句列清单

上面的方法能够帮助你避免未来的错误。然而已经存在的异常捕捉过广的except语句怎么办?很简单:在bug追踪系统中列出except字句清单,然后去一个个地修复它们。这是简单且有效的解决问题的办法。你可以现在就着手去做。

我建议你为每一个仓库或应用去建立清单,在你的代码中找到每个Exception,然后去优化它(你只需要在代码库通过检索找出“except”和“except Exception”)。你可以把它转换成处理某种特定类型的异常,或者如果你对代码不清楚的话,修改except语句去记录堆栈追踪信息。

你还可以进一步优化,为需要指定特定类型的异常建立一个清单。如果感到这个异常可以更加精确时,但是你对代码的内部结构不清楚时可以这样做。在这种情况下,你需要记录该异常的堆栈追踪信息;单独为此创建一个清单来记录;把它委派到某个对代码更加了解的家伙手里。如果你在单个的try/except中花费超过5分钟的时间去思考找出一个特定的异常时,我推荐你这样做。

培训团队中的其他成员

你是否参加定期的工程会议?一周一次?两周一次?还是一个月一次?你可以讲解这个“反面典型”,讲一下它对团队生产力造成的危害和及其简单的解决办法。

更好的是,你可以直接去找技术负责人或者工程经理来阐述这个问题。因为他们也对团队的生产力很关心。你可以把这篇文章的链接发给他们。如果有需要的话,让我跟他们直接通话来说服他们。

你甚至可以扩展到更广的社区。你是否会参加Python相关的工程师聚会?他们是否有“闪电会谈”这种环节?你是否可以在下次聚会时申请5到15分钟的发言时间?把这个伟大的“福音”传递给他们。

为什么要记录所有的堆栈追踪信息?

在上面的文字中,我一次次说到记录所有的堆栈追踪信息,而不仅仅是异常的信息。这样看起来可能工作量更大一点,你可能会迷失一个个与之相关的模块中。难道仅仅记录信息本身不就够了么?

不,远远不够。即使是一个经过精心设计的异常信息也只能告诉你异常语句在哪里,在哪个文件的哪一行里。通常来说并没有缩减多少范围。好吧,我们来假设一下最好的情况,当然,仅仅记录信息也比什么都不记录好,但是它并不会告诉你问题的源头在哪。很有可能出现在一个完全不同的文件或者模块里面,而人工去猜测很难办到的。

更复杂的是,在真实的开发环境中,团队成员的各种代码都有可能调用抛出异常的语句。也许当Foo类中的bar方法调用时会出现,但函数bar()调用时却不会出现异常。仅仅记录错误信息是无法区分这两者的区别的。

最近我所经历的事故发生在一个中等规模的开发团队中,大概50人,我是新来的,负责处理近四个月来一直出现的Unicode编码问题。异常被捕获了,消息被记录了,但是没有其他的信息可供参考。两个资深的工程师已经在这个问题上花费了数日时间,最终放弃了,他们没法找到问题所在。

他们是德高望重的程序员,最终,出于无奈,他们把这个问题交给了我,借助他们先前的经验,我成功复现了问题的发生,并记录下相关的堆栈追踪信息。六个小时之后,我找到了问题所在。一旦我设置了堆栈追踪信息,你知道我用多长时间就解决这个问题了么?

十分钟。仅仅十分钟。一旦我们有了堆栈追踪信息,修复的方法自然就有了。如果能够提早记录堆栈追踪信息,我们本可以节省下一个工程师一周的时间。还记得我前面说过的话么?在你解决问题时,设置堆栈追踪信息与否,解决bug的时间可能是几天或者是几分钟。我可不是开玩笑的。

(有趣的是,这样的经历也有了一个好的结果,它促使我开始书写更多关于Python的内容,我们能够更加高效地利用这门语言。)

英文原文:https://realpython.com/blog/python/the-most-diabolical-python-antipattern/

译者:崔子橙

python.freelycode.com-最难搞的python“反面”代码相关推荐

  1. 学python和java哪个难?,java和python哪个难学

    java和python哪个好学 ①python比Java简单,学习成本低,开发效率高;②Java运行效率高于python,尤其是纯python开发的程序,效率极低;③Java相关资料多,尤其是中文资料 ...

  2. python flask和django_真正搞明白Python中Django和Flask框架的区别

    在谈Python中Django框架和Flask框架的区别之前,我们需要先探讨如下几个问题. 一.为什么要使用框架? 为了更好地阐述这个问题,我们把开发一个应用的过程进行类比,往往开发一个应用(web应 ...

  3. python分布爬虫_13天搞定Python分布爬虫(第七天)(Scrapy)

    43-爬虫的基本使用 pip install Scrapy 注:windows平台需要依赖pywin32,pip install pypiwin32 创建工程:开发工具命令行(Terminal):sc ...

  4. python基础知识-一篇文章搞定Python全部基础知识

    原标题:一篇文章搞定Python全部基础知识 前言: 1.Python软件安装 第一章.字符串及数字变量 1.变量 要点提炼:Python变量为强类型动态类型.换言之,变量很任性,你给他int,他就是 ...

  5. python数据分析建模-十分钟搞懂“Python数据分析”

    原标题:十分钟搞懂"Python数据分析" 引言:本文重点是用十分钟的时间帮读者建立Python数据分析的逻辑框架.其次,讲解"如何通过Python 函数或代码和统计学知 ...

  6. python爬虫分布图_13天搞定Python分布爬虫!成为炙手可热的爬虫工程师

    原标题:13天搞定Python分布爬虫!成为炙手可热的爬虫工程师 1.什么是爬虫? 网络爬虫也叫网络蜘蛛,如果把互联网比喻成一个蜘蛛网,那么爬虫就是在网上爬来爬去的蜘蛛,爬虫程序通过请求url地址,根 ...

  7. python修饰器太难搞_【Python】小说爬虫界面版(各种BUG已修复)

    [Python] 纯文本查看 复制代码import tkinter as tk import re import urllib.request import os import time from u ...

  8. python import from class_彻底搞懂Python 中的 import 与 from import

    对不少 Python 初学者来说,Python 导入其他模块的方式让他们很难理解.什么时候用import xxx?什么时候用from xxx import yyy?什么时候用from xxx.yyy ...

  9. 百道Python面试题实现,搞定Python编程就靠它

    机器之心报道 机器之心编辑部 想要备战 Python 面试,这两个项目有千道 Python 问题与实现. 之前机器之心介绍了 PHD 大牛的求职之路,很多读者感觉这位大牛太厉害了,他的经历对我们帮助不 ...

最新文章

  1. sql server和mysql分页查询_sql server和mysql中分别实现分页功能
  2. 【学习笔记】28、类的方法及参数介绍
  3. docker实战系列之搭建rabbitmq
  4. Android Fragment 真正的完全解析(上) (转载)
  5. 学习笔记:关于科学方法在社会科学中的局限性
  6. atmega328p引脚图_Arduino UNO兼容ATmega328开发板-Seeeduino v4.0,附原理图/PCB/使用说明等...
  7. python统计中文字数_使用Python计算.pdf文档中的总字数
  8. 国外硕博论文下载网址资源
  9. 苹果CMS v10模板:大橙子vfed完美版视频网站模板
  10. Flutter Tabbar 自定义选中下标 自定义Indicator
  11. Tik Tok怎么赚钱?零基础电商卖家新手快速入行指南
  12. android时间轴折线图,echarts时间轴折线图
  13. 华芯通关闭引发的深度思考
  14. 【GT跑车】GT跑车是什么意思 GT跑车有哪些
  15. 原来酷炫的大屏,用Excel就能做
  16. Windows命令行常用命令
  17. Tomcat:-Djava.net.preferIPv4Stack=true只支持ipv4
  18. Android7.1.2源码解析系列】Android编译系统翻译------Android_Build_System(/build/core/build-system.html)
  19. 斯诺登“现身”CES展会 称想回到美国
  20. 不止一面的百变 ACE

热门文章

  1. 16年寒假随笔(2)
  2. go语言笔记——指针,和C用法以及本质一样,但不支持指针的+-运算!
  3. AngularJS2.0 quick start——其和typescript结合需要额外依赖
  4. BDB c++例子,从源码编译到运行
  5. sphinx索引分析——文件格式和字典是double array trie 检索树,索引存储 – 多路归并排序,文档id压缩 – Variable Byte Coding...
  6. docker mysql 主从库配置
  7. NXT(未来币)(阿朵)节点钱包
  8. UML中关联关系和依赖关系的区别(转)
  9. [LOJ 6288]猫咪[CF 700E]Cool Slogans
  10. [JZOJ6075]【GDOI2019模拟2019.3.20】桥【DP】【线段树】