我希望在工作中开始在大量基于Python的项目上使用DBC,并且想知道其他人使用它的经历。到目前为止,我的研究结果如下:
我的问题是:您是否将DBC与Python一起用于成熟的生产代码?它的效果如何/值得努力?你会推荐哪些工具?
答案 0 :(得分:17)
您找到的PEP尚未被接受,因此没有标准或可接受的方式(但是 - 您可以自己实施PEP!)。但是,正如您所发现的,有一些不同的方法。
可能最轻量级的只是简单地使用Python装饰器。 Python Decorator Library中有一组前置/后置条件的装饰器非常简单易用。这是该页面的一个例子:
>>> def in_ge20(inval):
... assert inval >= 20, 'Input value < 20'
...
>>> def out_lt30(retval, inval):
... assert retval < 30, 'Return value >= 30'
...
>>> @precondition(in_ge20)
... @postcondition(out_lt30)
... def inc(value):
... return value + 1
...
>>> inc(5)
Traceback (most recent call last):
...
AssertionError: Input value < 20
现在,您提到了类不变量。这些有点困难,但我想要的方法是定义一个可调用来检查不变量,然后像每个方法调用结束时的后置条件装饰器检查那样不变。作为第一个剪辑你可能只是按原样使用后置条件装饰器。
答案 1 :(得分:11)
根据我的经验,即使没有语言支持,按合同设计也是值得的。对于未被覆盖断言的方法,以及docstrings对于前置条件和后置条件都足够了。对于被重写的方法,我们将方法拆分为两个:检查前置和后置条件的公共方法,以及提供实现的受保护方法,并且可以由子类重写。这是后者的一个例子:
class Math:
def square_root(self, number)
"""
Calculate the square-root of C{number}
@precondition: C{number >= 0}
@postcondition: C{abs(result * result - number) < 0.01}
"""
assert number >= 0
result = self._square_root(number)
assert abs(result * result - number) < 0.01
return result
def _square_root(self, number):
"""
Abstract method for implementing L{square_root()}
"""
raise NotImplementedError()
我得到了平方根,作为软件工程广播(http://www.se-radio.net/2007/03/episode-51-design-by-contract/)上按合同设计的一集的合同设计的一般例子。他们还提到了语言支持的必要性,因为断言对确保Liskov替换原则没有帮助,尽管我上面的例子旨在证明其他原因。我还应该提到C ++ pimpl(私有实现)成语作为灵感来源,尽管它有着完全不同的目的。
在我的工作中,我最近将这种合同检查重构为更大的类层次结构(合同已经记录,但没有系统地测试)。现有的单元测试显示合同多次被违反。我只能得出结论,这应该是很久以前完成的,并且一旦按照合同设计,单位测试覆盖率就会更高。我希望那些尝试这种技术组合的人能够做出相同的观察。
更好的工具支持可能会在未来为我们提供更多的动力,我对此表示欢迎。
答案 2 :(得分:7)
答案 3 :(得分:5)
虽然不完全是按合同设计,但某些测试框架支持属性测试方法在概念上非常接近。
对运行时是否保留某些属性的随机测试可以轻松检查:
对于Python,有一些QuickCheck风格的测试框架:
答案 4 :(得分:4)
我们想在生产代码中使用前置/后置条件/不变式,但发现所有当前的按合同设计库都缺乏信息性和适当的继承性。
因此,我们开发了icontract。通过重新遍历该函数的反编译代码并评估所有涉及的值,将自动生成错误消息:
import icontract
>>> class B:
... def __init__(self) -> None:
... self.x = 7
...
... def y(self) -> int:
... return 2
...
... def __repr__(self) -> str:
... return "instance of B"
...
>>> class A:
... def __init__(self)->None:
... self.b = B()
...
... def __repr__(self) -> str:
... return "instance of A"
...
>>> SOME_GLOBAL_VAR = 13
>>> @icontract.pre(lambda a: a.b.x + a.b.y() > SOME_GLOBAL_VAR)
... def some_func(a: A) -> None:
... pass
...
>>> an_a = A()
>>> some_func(an_a)
Traceback (most recent call last):
...
icontract.ViolationError:
Precondition violated: (a.b.x + a.b.y()) > SOME_GLOBAL_VAR:
SOME_GLOBAL_VAR was 13
a was instance of A
a.b was instance of B
a.b.x was 7
a.b.y() was 2
我们发现该库在生产中(由于提供了有用的消息)和开发期间(因为它可以让您尽早发现错误)都非常有用。