私は、仕事中の多数のPythonベースのプロジェクトでDBCの使用を開始したいと考えており、他の人がDBCでどのような経験をしたのか疑問に思っています。これまでのところ、私の調査では次のことがわかりました。
私の質問は次のとおりです。成熟した本番コードにPythonでDBCを使用しましたか?それはどれだけうまく機能しましたか/努力する価値がありましたか?どのツールをお勧めしますか?
見つけたPEPはまだ受け入れられていないため、これを行うための標準的な方法や受け入れられている方法はありません(ただし、いつでも自分でPEPを実装できます!)。ただし、ご存知のように、いくつかの異なるアプローチがあります。
おそらく最も軽量なのは、単にPythonデコレータを使用することです。 Pythonデコレータライブラリ には、事前/事後条件用のデコレータのセットがあります。簡単に使用できます。そのページの例を次に示します。
>>> 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
ここで、クラスの不変条件について言及します。これらはもう少し難しいですが、私が行う方法は、不変条件をチェックする呼び出し可能オブジェクトを定義し、すべてのメソッド呼び出しの最後に、事後条件デコレータのようなものにその不変条件をチェックさせることです。最初のカットとして、おそらくポストコンディションデコレータをそのまま使用できます。
私の経験では、言語サポートがなくても、契約による設計は行う価値があります。アサーションがオーバーライドされないメソッドの場合、事前条件と事後条件の両方にdocstringとともに十分です。オーバーライドされるメソッドの場合、メソッドを2つに分割します。事前条件と事後条件をチェックするパブリックメソッドと、実装を提供する保護されたメソッドで、サブクラスによってオーバーライドされる場合があります。後者の例を次に示します。
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/エピソード-51-契約による設計/ )。また、上記の例はそうでないことを示すことを目的としていますが、アサーションはリスコフの置換原則を保証するのに役立たなかったため、言語サポートの必要性についても言及しました。インスピレーションの源としてC++ pimpl(プライベート実装)イディオムについても言及する必要がありますが、それはまったく異なる目的を持っています。
私の仕事では、最近、この種のコントラクトチェックをより大きなクラス階層にリファクタリングしました(コントラクトはすでに文書化されていますが、体系的にテストされていません)。既存の単体テストでは、契約が複数回違反されていることが明らかになりました。これはずっと前に行われるべきだったと私は結論付けることができます、そしてそのユニットテストカバレッジは契約による設計が適用されるとさらに報われる。このテクニックの組み合わせを試してみる人は誰でも同じ観察をすることを期待しています。
より良いツールサポートは、将来私たちにさらに多くの力を提供するかもしれません、私はそれを歓迎します。
プロダクションコードで事前/事後条件/不変条件を使用したかったのですが、現在のすべての契約による設計ライブラリには、有益なメッセージと適切な継承が欠けていることがわかりました。
したがって、私たちは 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
ライブラリは、本番環境(有益なメッセージのため)と開発中(バグを早期に発見できるため)の両方で非常に役立つことがわかりました。