web-dev-qa-db-ja.com

検証を伴う契約ベースのプログラミング

すべての関数が入力パラメーターと戻り値を検証するビルドコントラクトベースの大規模なプロジェクトがあります。ただし、たとえば、関数への呼び出しがnullでないことを確認する場合もあります(同じ関数がnullを返さないことも確認されます)。

そして私は考え始めています、これの本当の価値は何ですか?ユーザーやデータベースなどからの入力を確認することに関しては、私は全員参加していますが、それがドメイン内にあるときは、むしろ不必要に強いと感じています。

それは、すべての単体テストを削除し、すべてのアサート/チェックを使用して本番環境でテストするようなものです。

そして、これらのチェックを使用すると、関数のユーザーとしてあなたは実際には何もそれから何も得ません。なぜなら、契約を取得するには、コードに入り、すべてのアサーションをチェックして契約を理解する必要があるためです(たとえば、nullが許可されているかどうか)。また、チェックが失敗した場合は、いずれかの方法で解決する必要がある不適切なプログラミングのために、例外がスローされます。

それで、このようなコードを書くことの本当の利点は何ですか?そして、読みやすくするために関数のドキュメントにコントラクトを入れ、ユニットテストでプログラミングエラーがないことを「検証」する方が便利ではないでしょうか。

2
munHunger

関数/メソッドのコントラクトは非常に現実的なものです。例としてsqrt(x)を使用する:

  • xは実数でなければなりません
  • xはnullであってはなりません
  • x> = 0
  • nullではない戻り値
  • 戻り値> = 0

コントラクトに準拠していない関数を呼び出すか、コントラクトが保証しない方法でその戻り値を使用すると、必然的にエラーやバグが発生します。

次に理解することは欠陥のある情報でタスクを続行することはクラッシュするよりもはるかに悪いです。バグが発生し、その関数のincorrectデータを使用してアプリケーションを実行し続けると、データを汚染/破損するリスクがあります。この破損により、アプリケーションがウイルスのように拡散し、他の場所でクラッシュが発生し、さらに多くのデータが破損する可能性があります。その微妙な問題で時間内に問題に気付かなかった場合、バックアップから回復することさえできないかもしれません。
これは、速く/早く失敗することをユーザーに知らせる "申し訳ありませんが、現在、これを行うことはできません。明日戻ってください」


ほとんどの日常的なコードのコントラクトの多くの点を推測できます(たとえば、パラメーターと戻り値は、明記されていない限りnullではありません)。名前が明確に示されていない限り、関数はパラメーターを変更しません(addTo、... )。

しかし、すべての大規模なプロジェクトでは、関数のユーザーがコード全体を読むことによってしか理解できないかなりの量のコントラクト要素があります。定義した規則に従って常にプログラミングを行うことにどれほど努力しても、常にいくつかの関数があります。

ここで、プログラムが出力ガベージ(アビオニクスなど)ではなくクラッシュすることがいかに重要であるかに応じて、契約違反を防止したいとします(前述のように、バグやエラーが必ず発生するためです)。これを行うには、基本的にソフトウェアエンジニアが利用できる3つのオプションがあります。

  • コードのユーザーが使用する前に徹底的に読むことを期待する
  • ドキュメントのコメントに契約を文書化し、コードのユーザーが使用する前にそのドキュメントを読むことを期待する
  • コントラクトを関数にコード化し、違反した場合はクラッシュする

最初のオプションはほとんど機能しません。人々は通常、それが期待することをしていないときに他のコードを読むだけです現在のタスクで
2番目のオプションは、コードを扱うプログラマーによっては、ほんの少しだけ優れています。 docblockを読む人もいれば、docblockがそこにあることすら知らない人もいます。代わりに、docblockが10秒で教えてくれたことをインターネットで10分間調査します。
3番目のオプションは、ユーザーに選択肢を与えません。関数が正しく呼び出されない場合、さらに害を与える前にプログラムがクラッシュします。関数が適切に動作しない場合、出力アサーションはプログラムをクラッシュさせます。

それから、契約に変更があるものもあります。それらは通常、無意識のうちに起こります。ただし、関数コントラクトが変更されると、その使用法の多くも調整する必要があります。これを見る別の方法:ドキュメント(特にコントラクト)は、実行されないコードです。そのため、かなり早く腐敗する運命にあります。そして腐ったコードはあなたのために何の役にも立ちません。

オプション1では、テストスイートでカバーされているバグを引き起こさない限り、運はありません。オプション2も同様です。
オプション3では、契約を調整しない限り、多くの場所でテストスイートが失敗します。これにより、少なくともchanceが与えられ、契約が変更されたことに気付き、それに応じてクライアントコードを調整します。


このような主張を過度に使用する場合、極端な方法が最善の選択肢になることはほとんどありません。 IMOでは、プロジェクト全体(またはコードベース全体)の規則を定義することが重要です。その後、規約から逸脱した場合にアサーションを使用できます。 sqrt(x)の例では、アサーションは次のようになります。

  • xは実数でなければなりません
  • x> = 0
  • nullではない戻り値
  • 戻り値> = 0

ユニットテストに関する質問に対処するには、コントラクトアサーションはユニットテストの代わりにはなりません。彼らはお互いを補完します。もう一度sqrt(x)を見てください。出力がnullではなく、> = 0であることをアサートしても、sqrt(4) == 16かどうかはまったくテストされません。

3
marstato