4

我最近遇到了一个简单但令人讨厌的错误。我有一个列表,我想找到其中最小的成员。我使用了 Python 的内置 min()。一切都很好,直到在一些奇怪的情况下列表是空的(由于我无法预料到的奇怪的用户输入)。我的应用程序因 ValueError 崩溃(顺便说一句 - 官方文档中没有记录)。

我有非常广泛的单元测试,我会定期检查覆盖率以避免这样的意外。我也使用 Pylint(一切都集成在 PyDev 中)并且我从不忽略警告,但我未能在我的用户之前发现这个错误。

有什么我可以改变我的方法来避免这些运行时错误的吗?(这会在 Java / C# 的编译时被捕获?)。

我正在寻找的不仅仅是用一个大的 try-except 来包装我的代码。我还可以做些什么?有多少其他内置 Python 函数隐藏着这样令人讨厌的惊喜???

4

4 回答 4

7

这里的问题是格式错误的外部输入使您的程序崩溃。解决方案是在代码边界对可能的输入场景进行详尽的单元测试。你说你的单元测试是“广泛的”,但你显然没有测试过这种可能性。代码覆盖是一个有用的工具,但重要的是要记住覆盖代码与彻底测试代码不同。彻底的测试是涵盖使用场景和代码行的组合。

我使用的方法是信任内部调用者,但从不信任外部调用者或输入。因此,我明确不在接收外部输入的第一个函数之外的任何代码中对空列表案例进行单元测试。但是应该详尽地涵盖该输入功能。

min在这种情况下,我认为图书馆的例外是合理的行为——要求一个空列表是没有意义的。库不能合法地为您设置诸如 0 之类的值,因为例如您可能正在处理负数。

我认为空列表不应该到达要求的代码min- 它应该在输入时被识别,并且在那里引发异常,或者如果这对你有用,则将其设置为 0,或者其他任何有用的东西为你。

于 2010-04-15T18:50:37.910 回答
4

即使在 Java/C# 中,RuntimeError 的一类异常也是未经检查的,并且不会被编译器检测到(这就是为什么它们被称为 RuntimeError 而不是 CompileError)。

在 python 中,某些异常(例如 KeyboardInterrupt)特别棘手,因为它实际上可以在程序中的任意点引发。

我正在寻找的不仅仅是用一个大的 try-except 来包装我的代码。

请除此之外的任何东西。让异常到达用户并停止程序比让错误无声地传递要好得多(Python之禅)。

与 Java 不同,Python 不需要捕获所有异常,因为要求捕获所有异常使得程序员很容易忽略异常(通过编写空白异常处理程序)。

放松一下,让错误停止;让用户向您报告,以便您修复它。另一种选择是您进入调试器 42 小时,因为客户的数据由于空白的强制异常处理程序而到处损坏。

所以,你应该改变你的方法是认为异常是不好的;它们并不漂亮,但它们比替代品更好。

于 2010-04-15T18:26:39.103 回答
1

您可以使用随机测试:

#!/usr/bin/env python
import random
from peckcheck import TestCase, an_int, main

def a_seq(generator):
    return lambda size: [generator(size) 
                         for _ in xrange(random.randrange(size))] 

class TestMin(TestCase):
    def testInputNoThrow(self, x=a_seq(an_int)):
        min(x)

if __name__=="__main__":
    main()

要安装peckcheck,请键入:

$ pip install http://github.com/downloads/zed/peckcheck/peckcheck-0.1.v2.6.tar.gz

或者只是蛴螬peckcheck.py

于 2010-04-15T21:07:19.247 回答
0

我不知道您的问题的直接答案;如果 pylint 警告这种可能性,我也会喜欢它。鉴于空列表在各种情况下都会导致问题,我的一般做法是在使用列表之前检查列表的真实性;例如:

val = min(vals) if vals else 0

在许多情况下,这是“免费的”,因为您经常需要检查None。它还可以为特殊情况的空列表带来性能方面的回报,以避免,即启动新线程、进程或数据库事务来处理零项。

于 2010-04-15T18:41:09.513 回答