根据我的经验,即使没有语言支持,按合同设计也是值得的。对于未被覆盖的断言的方法,连同文档字符串对于前置条件和后置条件都足够了。对于被覆盖的方法,我们将方法分为两部分:一个检查前置条件和后置条件的公共方法,一个提供实现的受保护方法,并且可以被子类覆盖。这里是后者的一个例子:
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-按合同设计/)。他们还提到了语言支持的必要性,因为断言对于确保 Liskov-substitution-principle 没有帮助,尽管我上面的示例旨在证明并非如此。我还应该提到 C++ pimpl(私有实现)习语作为灵感来源,尽管它有完全不同的目的。
在我的工作中,我最近将这种合同检查重构为更大的类层次结构(合同已经记录在案,但没有经过系统测试)。现有的单元测试显示合同被多次违反。我只能得出结论,这应该在很久以前就已经完成了,并且一旦应用了按合同设计,单元测试覆盖率就会得到更多回报。我希望任何尝试这种技术组合的人都能做出同样的观察。
更好的工具支持可能会在未来为我们提供更多功能,我对此表示欢迎。