『SaaSプロダクト開発のScala活用事例』の発表資料です。 https://alp.connpass.com/event/239935/
Scalaで始める表明プログラミング2022/03/04 SaaSプロダクト開発のScala活用事例@dnskimox
View Slide
自己紹介男爵所属:Alp, Inc. / Scalebase開発言語:Scala、Apex/LWC(Salesfore)好きな話題:OOP、DDD、アジャイル開発Twitter:@dnskimox
Scalaで始める表明プログラミング▸ 契約による設計と表明▸ 事前条件と事後条件▸ 不変条件の実装方法▸ Eitherと組み合わせる▸ まとめ
1.契約による設計と表明
バートランド・メイヤー著『オブジェクト指向入門 第二版』開放/閉鎖の原則、コマンドとクエリの分離原則、統一形式アクセスの原則、契約による設計、単一責任選択の原則、etc...https://en.wikipedia.org/wiki/Bertrand_Meyer
“キーコンセプトは契約による設計(Design byContract)である。クラスと顧客の間の関係は、お互いの権利と義務を表した正式な同意と見なすことができる。すべてのモジ ュールのそのような要求と責任の詳細な定義があって、はじめて信頼性の高い大規模システムが実現できるのである。『オブジェクト指向入門 第2版 原則・コンセプト 』
顧客モジュールと供給者モジュール● データを利用● サービスを利用お互いの権利と義務を表した正式な同意 = 契約
“顧客モジュールと供給者モジュールの間の契約は、一方にとっての利益はもう一方の義務となる。効果的かつ信頼性の高いソフトウェアを作るということは、顧客と供給者の間で適切なコミュニケーションを重ねた末、ベストな妥協案にあたる契約を作成することである。『オブジェクト指向入門 第2版 原則・コンセプト 』
意図明白なインターフェースのこと?
それもある。が、それだけではない
“我々に必要なのは、内部を徹底的に調べなくても、設計要素の意味と操作の実行結果を理解できるようにする方法である。(中略)「契約による設計」学派は次の段階に進み、クラスとメソッドについて、開発者によって保証される「表明」を作成する。『エリック・エヴァンスのドメイン駆動設計 』
契約の形を表明する道具事前条件、事後条件、クラス不変表明、ループ不変表明、etc...
2.事前条件と事後条件
メソッドのコード本体を表明で挟む事前条件コード本体事後条件
BankAccount(銀行口座)
deposit(預け入れ)を実装
withdraw(引き出し)を実装
顧客側のメソッド呼び出し
供給者側の隠された意図● 預け入れで残高を減らせてしまう● 「0円を引き出す」という操作ができてしまうクラス設計者の想定と異なる使われ方をしている
メソッドに但し書きをつける?
メソッドに事前条件を仕込む
供給者の意図に反した呼び出しができなくなる
表明は動くドキュメントコードの書き手の想定、クラスの責務の範囲、オブジェクトの「正しい状態」の定義
供給者の意図に則した呼び出しなのに何かがおかしい?口座残高がマイナスに。それをクラス設計者は想定しているのか?
メソッドに事後条件を仕込むensuring: 確実にする、 保証する、 確保する
事後条件に違反していることが判明!
事後条件を守るようにコードを修正
“ルーチンの事前条件と事後条件を定義するということは、とりもなおさず、ルーチンとそれを呼び出すものの間で契約(contract)を結んだということである。『オブジェクト指向入門 第2版 原則・コンセプト 』
義務顧客は供給者のメソッドを正当な方法で呼び出さなければならない。事前条件によって生じる契約利益供給者はメソッドが呼び出された際、特定の仮説が満たされると保証される。
義務供給者はメソッドを抜ける際、定義された状態を満たしていなければならない。事後条件によって生じる契約利益顧客はメソッドの呼び出し後に、特定の性質が得られることを保証される。どちらのモジュールも想定すべきパターンが減る
コードがシンプルになる起こり得ない分岐を書かなくて良い、デッドコードの抑制、「但し書き」のコメント不要、etc...
責任の所在が明らかに事前条件違反なら顧客側が悪い、事後条件違反なら供給者側が悪い。修正すべきコードは明白。
3.不変条件の実装方法
“事前条件と事後条件は個々のルーチンの特性を記述する。このほかに、全てのルーチンで維持されなければならない、クラスに共通する全体的な特性を表す必要がある。『オブジェクト指向入門 第2版 原則・コンセプト 』不変条件(クラス不変表明)
“クラス不変条件は、あらゆる操作が終わった時のオブジェクトの状態に関する表明となる。不変条件は、集約全体に対しても宣言することができ、整合性に関するルールを厳密に定義する。『エリック・エヴァンスのドメイン駆動設計 』
Scalaには不変条件を定義する構文がない
ScalaにはCase Classがある
BankAccountクラスに不変条件を記述する● インスタンス化の際にチェックされる● copyの呼び出し時に毎回チェックされる● オブジェクトの生成から破棄まで守られ続ける*ただしイミュータブルな場合に限る
不変条件違反は即座にエラーになる
義務全てのクラスのオブジェクトは、生成されてから破棄されるまで不変条件を満たし続けなければならない。不変条件によって生じる契約利益コードベース全体において、いかなるときもオブジェクトが定義された性質を満たしていると保証される。
責任の所在が明らかに不変条件違反はクラス内部のコードに問題がある。事前条件かメソッドの本体を見直すべし。
ところで、表明エラーは副作用なのでは?
“もう一つのよくある誤解は、表明を制御構造(すなわち、特殊なケースを取り扱う技術)と考えることである。(中略)sqrtが事前条件を持つ場合、x< 0での呼び出しは特殊なケースではない。単純明快、それはバグである。『オブジェクト指向入門 第2版 原則・コンセプト 』
表明エラーが起きたらコードを修正すべしオブジェクトの状態が異常、バリデーションの不足、事前条件が強すぎる、etc...
表明違反の修正は簡単問題あるのコードのすぐ近くでエラーが起きる、データが破損する前に処理が停止する、責任の所在が明白
副作用の局所化とは分けて考えよう
4.Eitherと組み合わせる
義務供給者はメソッドを抜ける際、定義された状態を満たしていなければならない。事後条件によって生じる契約利益顧客はメソッドの呼び出し後に、特定の性質が得られることを保証される。事後条件を守ることを常に保証できるか?
コードの正しさだけでは抑制できない実行時エラーファイルアクセスエラー存在しないパス、パーミッションの不一致、ディスクの物理的な呼称メモリエラーメモリ割り当ての失敗、メモリの物理的な故障OSからのシグナル入力デバイスからの割り込み、中断命令、Killデータベースエラー接続失敗、コネクション数の超過、ロック待ち時間の超過、SQLのシンタックスエラーネットワークエラーコネクション確立の失敗、コネクションの中断外部サービスのエラー認証失敗、外部サービス側のバリデーション、メンテナンス
“ルーチンが契約を満たす状態で実行を終えた場合、そのルーチンコールは成功である。成功しなければ失敗である。(中略)何らかの特別なイベントがルーチンの実行を中断させたときルーチンは失敗する。そのようなイベントを「例外」という。『オブジェクト指向入門 第2版 原則・コンセプト 』
例外は契約の履行失敗を表すデータベースエラー、ファイルへのアクセスエラー、メモリ確保の失敗、etc...
Eitherを使って契約の履行失敗を表すパターン
バリデーションとは何が違うの?
バリデーションはシステム境界の外からの入力を検証する仕組み
表明はシステム境界内のモジュール同士のやり取りを定めた契約常に真
外からのいかなる入力に対しても表明違反を起こさないようプログラミングする常に真
表明とバリデーションは存在目的の異なる道具であるバリデーション 表明誰のためのもの? システムを使う人 コードを書く人違反が起きた時、どうする?入力内容を改める コードを修正する本番稼働時に必要? 必要 必ずしも必要ではない
BankAccountオブジェクトを作る際のバリデーション
5.まとめ
表明を使ってモジュール間の契約を定義する表明エラーが起きたときはコードを修正する不変条件でデータの破損を未然に防ぐ
表明を使ってシンプルで堅牢なコードを書こう!ご清聴ありがとうございました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