Object-Oriented Conferenceの発表資料です。 https://fortee.jp/object-oriented-conference-2020/proposal/1224f293-8624-4448-866f-5d1b991d377f
カンファレンスの感想はこちら。 https://dnskimox.hateblo.jp/entry/2020/02/22/104342
契約による設計事始め2020/02/16 Object-Oriented Conference@dnskimox
View Slide
自己紹介男爵所属:Alp,Inc. / Scalebase(サブスクリプション管理のSaaS)開発言語:Scala、TypeScript好きな話題:OOP、DDD、開発プロセスTwitter:@dnskimox
今日は契約による設計の話をしますhttps://en.wikipedia.org/wiki/Design_by_contract
契約による設計事始め▸ 契約による設計とはなにか?▸ 事前条件と事後条件▸ クラス不変表明▸ 表明と例外の関係▸ バリデーションとの違い▸ 明日から使える実践方法
1.契約による設計とはなにか? どこから来た用語で何を意味するのか?
バートランド・メイヤー著『オブジェクト指向入門 第二版』開放/閉鎖の原則、コマンドとクエリの分離原則、統一形式アクセスの原則、契約による設計、単一責任選択の原則、etc...https://en.wikipedia.org/wiki/Bertrand_Meyer
“キーコンセプトは契約による設計(Design by Contract)である。クラスと顧客の間の関係は、お互いの権利と義務を表した正式な同意と見なすことができる。すべてのモジ ュールのそのような要求と責任の詳細な定義があって、はじめて信頼性の高い大規模システムが実現できるのである。
顧客モジュールと供給者モジュール● データを利用● サービスを利用要求と責任の詳細な定義 = 契約 が必要
“顧客モジュールと供給者モジュールの間の契約は、一方にとっての利益はもう一方の義務となる。効果的かつ信頼性の高いソフトウェアを作るということは、顧客と供給考の間で適切なコミュニケーションを重ねた末、ベストな妥協案にあたる契約を作成することである。
契約の形を表明する道具事前条件、事後条件、クラス不変表明、ループ不変表明、etc...
様々な種類の表明事前条件ルーチンを利用する際に求められる要求事後条件ルーチンの終了時に保証される特性クラス不変表明クラスインスタンスを特徴付け、その生存中にずっと保持される特性ループ不変表明説明省略ループ変化表明説明省略check命令説明省略
2.事前条件と事後条件 このエラーを出したのは誰だ!
メソッドのコード本体を表明で挟む事前条件コード本体事後条件
ScalaのCase classの特徴● 基本的に全てのプロパティが不変● 全てのプロパティのgetterを持つ● プロパティの一部を変更したコピーを返すメソッドがある
Statsを持つCharacterを作る
供給者側の隠された意図● 回復するメソッドでHPを減らせてしまう● ダメージを与えるはずがHPが増えている?クラスの作者の想定と異なる使われ方
メソッドに但し書きをつける?
メソッドに事前条件を仕込む
供給者の意図に反した呼び出しができなくなる
供給者の意図に則した呼び出しなのに何かがおかしい?
メソッドに事後条件を仕込む
事後条件に違反していることが判明!
事後条件を守るようにコードを修正
“ルーチンの事前条件と事後条件を定義するということは、とりもなおさず、ルーチンとそれを呼び出すものの間で契約(contract)を結んだということである。
事前条件顧客は供給者のメソッドを呼び出す際、事前条件を満たす義務を負う。契約によって生じる義務事後条件供給者は事前条件を満たして呼び出されたメソッドを抜ける際、事後条件を満たす義務を負う。
事前条件供給者は想定外の状況でメソッドを呼び出されないことが保証されるという利益を得る。契約によって生じる利益事後条件顧客は事前条件を守ってメソッドを呼び出す限り、結果が定義された特性を満たすという利益を得る。どちらのモジュールも想定すべき事項が減る
コードがシンプルになる起こり得ない分岐を書かなくて良い、デッドコードの抑制、「但し書き」のコメント不要、etc...
責任の所在が明らかに事前条件違反なら顧客側が悪い、事後条件違反なら供給者側が悪い。修正すべきコードは明白。
3.クラス不変表明 オブジェクトにとって正しい状態とはなにか?
“事前条件と事後条件は個々のルーチンの特性を記述する。このほかに、全てのルーチンで維持されなければならない、クラスに共通する全体的な特性を表す必要がある。
Statsクラスにクラス不変表明を記述する● インスタンス化の際にチェックされる● copyの呼び出し時にも毎回チェックされる● オブジェクトの生成から破棄まで守られ続ける
表明違反は即刻エラーになる Diagram featured byhttp://slidemodel.com
クラス不変表明(不変条件)あるクラスのオブジェクトは、生成されてから破棄されるまでクラス不変表明を満たし続ける義務を負う。契約によって生じる義務
クラス不変表明(不変条件)コードベース全体において、あるクラスのオブジェクトがクラス不変表明に違反するという想定をしなくて良くなるという利益を得る。契約によって生じる利益
責任の所在が明らかにクラス不変表明違反はクラス内部のコードに問題がある。事前条件かメソッドの本体を見直すべし。
4.表明と例外の関係 契約による設計なら例外は必要なくなる?
事前条件供給者は想定外の状況でメソッドを呼び出されないことが保証されるという利益を得る。契約によって生じる利益事後条件顧客は事前条件を守ってメソッドを呼び出す限り、結果が定義された特性を満たすという利益を得る。事後条件を守ることを常に保証できるか?
コードの正しさだけでは抑制できない実行時エラーファイルアクセスエラー存在しないパス、パーミッションの不一致、ディスクの物理的な呼称メモリエラーメモリ割り当ての失敗、メモリの物理的な故障OSからのシグナル入力デバイスからの割り込み、中断命令、Killデータベースエラー接続失敗、コネクション数の超過、ロック待ち時間の超過、SQLのシンタックスエラーネットワークエラーコネクション確立の失敗、コネクションの中断外部サービスのエラー認証失敗、外部サービス側のバリデーション、メンテナンス
“ルーチンが契約を満たす状態で実行を終えた場合、そのルーチンコールは成功である。成功しなければ失敗である。(中略)何らかの特別なイベントがルーチンの実行を中断させたときルーチンは失敗する。そのようなイベントを「例外」という。
例外は契約の放棄を表すデータベースエラー、ファイルへのアクセスエラー、メモリ確保の失敗、etc...
5.バリデーションとの違い 要するに入力チェックでは?
バリデーションはシステム境界の外からの入力を検証する仕組み
表明はシステム境界内のモジュール同士のやり取りを定めた規約常に真
外からのいかなる入力に対しても表明違反を起こさないようプログラミングする常に真
表明とバリデーションは根本的に異なる道具であるバリデーション 表明誰のためのもの? システムを使う人 コードを書く人違反が起きた時、どうする?入力内容を改める コードを修正する本番稼働時に必要? 必要 必ずしも必要ではない
表明は動くドキュメントコードの書き手の想定、クラスの責務の範囲、オブジェクトの「正しい状態」の定義
5.明日からできる実践方法 で、結局何から始めればいいの?
使っている言語の表明機構を調べる事前条件 事後条件 クラス不変表明Scala require ensuring assertPHP assert assert ×Ruby raise if/unless raise if/unless ×なければ表明を組み込むライブラリを探す
事前条件を書くシグネチャで表現しきれない引数の制約はなにか?バグを修正する発生からの経過時間が短いほど原因調査は簡単表明違反を通知する開発環境や本番環境からプログラマへのアラート
表明違反は例外なくバグオブジェクトの状態が異常、バリデーションの不足、事前条件が強すぎる、etc...
表明違反の修正は簡単問題のコードのすぐ近くでエラーが起きる、データが破損する前に処理が停止する、責任の所在が明白
表明を使ってシンプルで正しいコードを!ご清聴ありがとうございましたYou can find me at @dnskimox & https://dnskimox.hateblo.jp/
参考文献▸ オブジェクト指向入門 第二版 概念・コンセプト▹ 11章 契約による設計:信頼性の高いソフトウェアを構築する▹ 12章 契約が破られるとき:例外処理▸ https://en.wikipedia.org/wiki/Design_by_contract▸ https://speakerdeck.com/twada/php-conference-2016-revised▸ https://qiita.com/kawachi/items/c3cf53e0602fb53e78e9▸ https://stackoverflow.com/questions/147969/is-it-idiomatic-ruby-to-add-an-assert-method-to-rubys-kernel-class