ドメインエラーを扱う
LT;DR
システムには以下の 3 種類のエラーが存在する
e.g. 請求の段階で却下された注文 や 無効な製品コードを含む注文
例外を発生させ、上位レベル(main 関数)で捕捉する
e.g. メモリ不足、ゼロによる演算、null 参照
e.g. ネットワークのタイムアウト や 認証失敗
例外ではなくエラーを返すと、コードの 可読性 が落ちるので、別途対応が必要 hr.icon
システムに現れる 3 種類のエラー
実際の現場で、このような自体に対処するための手順がすでに用意されている場合、コードにはこれを反映させるべき
e.g. 請求の段階で却下された注文 や 無効な製品コードを含む注文
処理不可能なシステムエラー や プログラマの見落としによるエラー
e.g. メモリ不足、ゼロによる演算、null 参照
ビジネスプロセスの一部ではなく、ドメイン にも含まれていない e.g. ネットワークのタイムアウト や 認証失敗
それぞれのエラーの実装方法
ドメインエラー
パニック
適切かつ最も高いレベル(e.g. main)で捕捉すべき
Go の panic をイメージすると分かりやすい code:fsharp
type workflowPart1 = ...
type workflowPart2 input =
if input = 0 then
raise (DivideByZeroException())
...
let main() =
try
let result1 = workflowPart1()
let result2 = workflowPart2 result1
printfn "the result is %A" result2
with
| :? OutofMemoryException ->
printfn "exited with OutofMemoryException"
| :? DivideByZeroException ->
printfn "exited with DivideByZeroException"
| ex ->
printfn "exited with %s" ex.Message
インフラストラクチャエラー
ただし、インフラストラクチャエラーの多くは、ドメインエラーと同等に扱ったほうが役立つ
ドメイン として扱うことで、「どんなエラーが起こる可能性があるか」を考えざるをえなくするため e.g. 外部サービスを利用できない場合
ビジネスプロセスをどう変更すべきか
顧客にはどう伝えるべきか
これらは開発チームだけで対処できるものではなく、ドメインエキスパートや プロダクトオーナー も含めて検討する 型によるドメインエラーのモデリング
プリミティブ型を使わず、ユビキタス言語を用いて、ドメインに特化した型を作成する 一般的に、エラーは 選択型 としてモデリングし、別途対応が必要なエラーの種類ごとにケースを用意する code:fsharp
type PlaceOrderError =
| ValidationError of string
| ProductOutOfStock of ProductCode
| RemoteServiceError of RemoteServiceError
ValidationError: 長さやフォーマットのエラー、プロパティのチェックに利用
ProductOutOfStock: 顧客が在庫切れの製品を購入しようとしたときに利用
1. うまくいかない可能性のあるすべての事柄について、コード内で明示的なドキュメントとして機能する
2. エラーに関連する追加情報も明示的に示される
3. 要件の変換に応じて、型を簡単かつ安全に拡張(縮小)できる
一般的に、エラーケースは前もって定義するのではなく、開発していく中で浮かんでくるものである
なぜ安全か?
エラー処理はコードの見た目を悪くする
例外の利点は、正常処理のコードがきれいになる点
code:fsharp
let validateOrder unvalidatedOrder =
try
let orderId = ...
let customerInfo = ...
let shippingAddress = ...
with
...
各ステップでエラーを返すと煩雑になる
code:fsharp
let validateOrder unvalidatedOrder =
let orderIdResult = ...
if orderIdResult is Error then
return
let customerInfoResult = ...
if customerInfoResult is Error then
return
try
let shippingAddressResult = ...
if shippingAddressResult is Error then
return
...
with
...
潜在的なエラーを捕捉する try ~ catch が組み合わされると、より 可読性 が落ちる