Slide 1

Slide 1 text

© 2012-2019 BASE, Inc. © 2012-2021 BASE, Inc. 2021/05/29 PHP カンファレンス沖縄 2021 炭田高輝(@tac_tanden) PHP で throw しない 例外ハンドリング

Slide 2

Slide 2 text

© 2012-2019 BASE, Inc. © 2012-2021 BASE, Inc. 自己紹介 BASE株式会社 Owners Experience Backend Group Engineering Manager 2016.09 - 新卒でWebゲーム開発 2020.01 - BASE株式会社で『BASE』の開発 カンファレンスの登壇は初めてです よろしくお願いします!     炭田高輝(tanden) Back-End Web Developer 2

Slide 3

Slide 3 text

© 2012-2019 BASE, Inc. © 2012-2021 BASE, Inc. 本日のアジェンダ 1 2 3 PHPでの例外処理 例外を使わない言語たち PHPでthrowしない例外処理 3

Slide 4

Slide 4 text

© 2012-2019 BASE, Inc. © 2012-2021 BASE, Inc. 例外とはなにか?

Slide 5

Slide 5 text

© 2012-2019 BASE, Inc. © 2012-2021 BASE, Inc. 例外とはなにか プログラミングにおける 例外とは何か? 5

Slide 6

Slide 6 text

© 2012-2019 BASE, Inc. © 2012-2021 BASE, Inc. 例外とはなにか 考えれば考えるほど難しかった 6

Slide 7

Slide 7 text

© 2012-2019 BASE, Inc. © 2012-2021 BASE, Inc. 例外とはなにか 『「例外」は、エラーや例外イベントを呼び出し 元のコードに渡すことができる特別な手段であ る。<中略> エラー状況に対処できないコードはエラーを解釈 してそれをうまく処理する機能を持っていると期 待して、システムの他の部分に制御を渡すことが できる。』(p.242) メソッド内で処理できない(しない)状態に なったことを外部に伝える手段のこと コードコンプリート第2版 第8章 防御的プログラミング 7

Slide 8

Slide 8 text

© 2012-2019 BASE, Inc. © 2012-2021 BASE, Inc. 例外とはなにか 『「例外とは予期せぬ事態に備えるためのもので あり、プログラムの通常の流れの一部には組み込 むべきでない」』(p.129) 『ヒント34:例外は例外的な問題のみに使用す ること』(p.130) 成功でも失敗でもない、ルーチン(メソッド) が処理することを想定していない状態になった ときにのみ、例外は使われるべき 達人プログラマー 24 いつ例外を使用するのか 8

Slide 9

Slide 9 text

© 2012-2019 BASE, Inc. © 2012-2021 BASE, Inc. 例外とはなにか 契約による設計における成功と失敗 『ルーチンが契約を満たす状態で実行を終えた場 合、そのルーチンコールは成功である。成功しなけ れば失敗である。』(p.528) 『例外とはルーチンコールの失敗を引き起こす可能 性のある実行時イベントである。』(p.528) 契約による設計にはこの場では深入りしません。 しかし、契約による設計を導入することで例外を 明確に定義できるようになります。 オブジェクト指向入門 第2版 原則・コンセプト 9

Slide 10

Slide 10 text

© 2012-2019 BASE, Inc. © 2012-2021 BASE, Inc. 「例外」とはなにか やはり難しい 1

Slide 11

Slide 11 text

© 2012-2019 BASE, Inc. © 2012-2021 BASE, Inc. 例外とはなにか 概念としての例外 - どのように処理すべきか織り込まれていないイベントのこと - 事前条件違反 - 事後条件違反 - その他の処理を失敗させるイベント プログラミング言語の機能としての例外 - どのように処理すべきか織り込まれていない状態になったことを外部に伝える手段 11

Slide 12

Slide 12 text

© 2012-2019 BASE, Inc. © 2012-2021 BASE, Inc. 例外ハンドリングとは?

Slide 13

Slide 13 text

© 2012-2019 BASE, Inc. © 2012-2021 BASE, Inc. 例外ハンドリング 『ルーチンの実行中に起こった例外を処理する には、正しいやり方が2つある。 R1 リトライ:例外となる状態を変更、ルーチン を最初から実行し直そうとする。 R2 失敗(組織的パニック):環境をきれいに し、実行を終了して呼び出し側に失敗を報告す る。』(p.534) 例外を捕捉して、プログラム上でリトライもし くは失敗として扱うこと オブジェクト指向入門 第2版 原則・コンセプト 13

Slide 14

Slide 14 text

© 2012-2019 BASE, Inc. © 2012-2021 BASE, Inc. 例外ハンドリングとはなにか 概念としての例外ハンドリング - 捕捉した例外をもとに、プログラムの目的のために何らかの処理を実行すること - リトライ - ユーザへ例外が発生したことをフィードバックする - ログに吐いて処理を終了する プログラミング言語の機能としての例外ハンドリング - プログラミング言語に備わっている仕組みを使って、プログラムの目的を達成するために 例外に対して何らかの処理を実装すること 14

Slide 15

Slide 15 text

© 2012-2019 BASE, Inc. © 2012-2021 BASE, Inc. 「例外」と例外 以降のページでは概念としての例外を例外と書き プログラミング言語の機能としての例外を「例外」と書きます 15

Slide 16

Slide 16 text

© 2012-2019 BASE, Inc. © 2012-2021 BASE, Inc. 本日のアジェンダ 1 2 3 PHPでの「例外」処理 「例外」を使わない言語たち PHPでthrowしない「例外」処理 16

Slide 17

Slide 17 text

© 2012-2019 BASE, Inc. © 2012-2021 BASE, Inc. PHPの「例外」ハンドリング - 呼び出されるメソッド側で例外クラスを throwする - 呼び出し側で例外クラスを捕捉する - 捕捉するために呼び出すメソッドを try-catchで囲む - try-catchブロックの後で「常に実行したい 処理」を行いたい場合 finallyブロックを追 加 - finally内で実装された処理は例外が throw されなくても実行される PHPの例外処理 17 function division(float $x, float $y) float { if ($y === 0) { throw new Exception('0 division'); } return $x/y; } try { echo division(0); } catch (Exception $e) { // 例外を捕捉して何らかの処理を行う } finally { // 必ず実行したい処理を行う }

Slide 18

Slide 18 text

© 2012-2019 BASE, Inc. © 2012-2021 BASE, Inc. throw と try-catch-finallyの難しさ 仮説 throw / try-catch-finallyは 開発者にとって扱うのが難しいのではないか 1

Slide 19

Slide 19 text

© 2012-2019 BASE, Inc. © 2012-2021 BASE, Inc. throw と try-catch-finallyの難しさ - returnではない何か...? - throwというものがあるらしい - throwすると処理はどうなるの...? - どうやら処理は中断している様子 - throwしたら次はどこに処理が移るの? - ステップ実行するしかないか... 新卒でまだエンジニアになりたての頃の遠い記憶 19

Slide 20

Slide 20 text

© 2012-2019 BASE, Inc. © 2012-2021 BASE, Inc. throw と try-catch-finallyの難しさ - throwしてcatchする箇所まで処理がジャンプする - goto文のような使い方ができてしまう - 呼び出し元のメソッドは、呼び出し先のメソッドのどこでどんな例外がthrowされるのか 知っておく必要がある - 依存関係が生まれる - throw / try-catch-finallyの複雑さに対して、アプリケーションにとって例外ハンドリン グはどこまで必要なのかのトレードオフを設計する必要がある - トレードオフの設計は難しい 例外をthrow / try-catch-finallyで「適切に」処理するのはかなり難しいのではないか 20

Slide 21

Slide 21 text

© 2012-2019 BASE, Inc. © 2012-2021 BASE, Inc. 本日のアジェンダ 1 2 3 PHPでの「例外」処理 「例外」を使わない言語たち PHPでthrowしない「例外」処理 21

Slide 22

Slide 22 text

© 2012-2019 BASE, Inc. © 2012-2021 BASE, Inc. 「例外」を使わない言語たち 「例外」を使わずに 例外を処理する 言語がある 2

Slide 23

Slide 23 text

© 2012-2019 BASE, Inc. © 2012-2021 BASE, Inc. 「例外」を使わない言語たち 今回取り上げる3つの言語 Rust - すべて自学自習による知識で、残念ながら有識者のレビューを受けたものではありません - 間違っていたらフィードバックいただけると嬉しいです Scala Go 2

Slide 24

Slide 24 text

© 2012-2019 BASE, Inc. © 2012-2021 BASE, Inc. Scalaの例外処理 - Scalaではtry-catch-finally節も用意されている - Javaのコードを取り込むことも可能なため - しかしScalaでは、関数型言語の性質上、副作用をさけるために例外は好まれない 「例外は副作用」とは - プログラミング上の副作用 - 関数が値を受け取り値を返すことを主作用とすると、それ以外のプログラムの状態を変化させる作 用を副作用 - 「例外」は主作用以外でプログラムの状態を変更してしまうことから副作用に分類できる 24

Slide 25

Slide 25 text

© 2012-2019 BASE, Inc. © 2012-2021 BASE, Inc. Scalaの例外処理 Either either = (2者のうち)どちらか一方 2 エラー処理の文脈で 成功 or 失敗のどちらかを表すことに適した 抽象クラス

Slide 26

Slide 26 text

© 2012-2019 BASE, Inc. © 2012-2021 BASE, Inc. Scalaの例外処理 Either Eitherを継承したLeft, Rightを利用する 2 Left: 失敗時の値 Right: 成功時の値 を入れることが多い 英語の”right”(正しい)にかけているらしい

Slide 27

Slide 27 text

© 2012-2019 BASE, Inc. © 2012-2021 BASE, Inc. Scalaの例外処理 Eitherを返す関数 27 def division(x: Float, y: Float): Either[String, Float] = if(y == 0.0) Left("0 division") else Right(x/y) - 上記のような関数からのEither型の返り値に対して、match式やmap、flatMapによって 関数を適用してRightやLeftの値に対して処理を行う

Slide 28

Slide 28 text

© 2012-2019 BASE, Inc. © 2012-2021 BASE, Inc. Rustの例外処理 - 『Rustには例外が存在しません』(Rust公式ガイド p.107) - 代わりに2種類のエラーを使う - 回復不能なエラー - 回復可能なエラー 28

Slide 29

Slide 29 text

© 2012-2019 BASE, Inc. © 2012-2021 BASE, Inc. Rustの例外処理 - 回復不能なエラー - 処理の実行を中止すべきエラー - panic!を呼び出すことで意図的にクラッシュさせる仕組みがある - 回復可能なエラー - ログを残したり、ユーザにフィードバックしてリトライなど - クラッシュさせるようなエラーは少ないので、エラー処理のメインはこちら - Resultを使う 29

Slide 30

Slide 30 text

© 2012-2019 BASE, Inc. © 2012-2021 BASE, Inc. 例外ハンドリング(再掲) 『ルーチンの実行中に起こった例外を処理する には、正しいやり方が2つある。 R1 リトライ:例外となる状態を変更、ルーチン を最初から実行し直そうとする。 R2 失敗(組織的パニック):環境をきれいに し、実行を終了して呼び出し側に失敗を報告す る。』(p.534) 例外を捕捉して、プログラム上でリトライもし くは失敗として扱うこと オブジェクト指向入門 第2版 原則・コンセプト 30

Slide 31

Slide 31 text

© 2012-2019 BASE, Inc. © 2012-2021 BASE, Inc. Rustの例外処理(再掲) - 回復不能なエラー - 処理の実行を中止すべきエラー - panic!を呼び出すことで意図的にクラッシュさせる仕組みがある - 回復可能なエラー - ログを残したり、ユーザにフィードバックしてリトライなど - クラッシュさせるようなエラーは少ないので、エラー処理のメインはこちら - Resultを使う バートランド・メイヤーの例外ハンドリングの定義そのもの しかし、言語機能としての「例外」はない 31

Slide 32

Slide 32 text

© 2012-2019 BASE, Inc. © 2012-2021 BASE, Inc. Rustの例外処理 Result enum Result { Ok(T), Err(E), } 3 列挙子にOkとErrをもったenum型のクラス

Slide 33

Slide 33 text

© 2012-2019 BASE, Inc. © 2012-2021 BASE, Inc. Rustの例外処理 Result fn division(x: f64, y: f64) -> Result(f64, ZeroDivisionError) { if y == 0.0 { Err(ZeroDivisionError) } else { Ok(x/y) } } 3 match式やResult型に用意されているメソッドを使って 成功時の値、エラーの値の処理を行う

Slide 34

Slide 34 text

© 2012-2019 BASE, Inc. © 2012-2021 BASE, Inc. Goの例外処理 - Goにも「例外」が存在しません - “We believe that coupling exceptions to a control structure, as in the try-catch-finally idiom, results in convoluted code. “ (Go document Frequently Asked Questions (FAQ)) - convoluted = 入り組んだ - 例外処理をどうしているのか - 多値返却 34

Slide 35

Slide 35 text

© 2012-2019 BASE, Inc. © 2012-2021 BASE, Inc. Goの例外処理 多値返却 func Division(x float64, y float64) (float64, error) { if y == 0.0 { return nil, err } else { return x/y, nil } } 3 呼び出しもとで、返却されたerrorをチェックしnilでなければ失敗、nilであれば成功 呼び出し元が必ず例外処理を行うようにする

Slide 36

Slide 36 text

© 2012-2019 BASE, Inc. © 2012-2021 BASE, Inc. 本日のアジェンダ 1 2 3 PHPでの「例外」処理 「例外」を使わない言語たち PHPでthrowしない「例外」処理 36

Slide 37

Slide 37 text

© 2012-2019 BASE, Inc. © 2012-2021 BASE, Inc. PHPでthrowしない例外ハンドリング - アイディアとしては2つ - Either, Resultのようなクラスを作り、メソッドの返り値として使う - 多値返却 37

Slide 38

Slide 38 text

© 2012-2019 BASE, Inc. © 2012-2021 BASE, Inc. Either, Resultのようなクラスを作れるか - php-fp/php-fp-eitherというライブラリがある - PHPでEitherのようなオブジェクトにmapで関数を当てられるようにしたライブラリ - 4年前で更新が止まっている - 型付けができない - PHPにはジェネリクスがない - Left / Right もしくは Ok, Errの中身の値の型を柔軟に変更できない - そもそもenum型もない なのでこちらの線で進めるのは難しそう 38

Slide 39

Slide 39 text

© 2012-2019 BASE, Inc. © 2012-2021 BASE, Inc. 多値返却 - PHP7: タプルで返却 - 成功:[$result, null] - 失敗:[null, new RuntimeException()] - 上記のような形で返してあげることは可能 - しかし、メソッドの返り値が array にしかできない - PHP8: Union型が使えそう 多値返却の線はありかもしれない 39

Slide 40

Slide 40 text

© 2012-2019 BASE, Inc. © 2012-2021 BASE, Inc. 実際に行ったPHP7での多値返却 - タプルでの多値返却 - メリット - throwとtry-catchを書かずにすべて return で実装することができた - コードがシンプルになったのと、ユニットテストが書きやすかった - デメリット - メソッドの返り値が array にしかできない - PHPStanでの静的解析はarrayの中身までは見ることができなさそう?(未検証) 40

Slide 41

Slide 41 text

© 2012-2019 BASE, Inc. © 2012-2021 BASE, Inc. 実際に行ったPHP7での多値返却 - 成功/失敗の値をもったDTO - タプルでは返り値の型定義をarrayにしかできないことを解決すべく、DTOに成功/失敗の 値を持たせることを試みた - 成功と失敗の値はコンストラクタから渡してインスタンス化する - 成功:(null, $result) - 失敗:(new RuntimeException(), null) - $dto->getResult(); - $dto->getError(); 41

Slide 42

Slide 42 text

© 2012-2019 BASE, Inc. © 2012-2021 BASE, Inc. 実際に行ったPHP7での多値返却 - 成功/失敗の値をもったDTO - タプルでは返り値の型定義をarrayにしかできないことを解決すべく、DTOに成功/失敗の 値を持たせることを試みた - メリット - 返り値の型付けができた - デメリット - 実装が面倒で複雑になった(ジェネリクスがないためいちいちresultの型に合わせてク ラスを作る必要がある) - PHPStanで静的解析ができなさそう(要検証) 42

Slide 43

Slide 43 text

© 2012-2019 BASE, Inc. © 2012-2021 BASE, Inc. PHP7での多値返却 - やるならタプルでの多値返却 - 返り値はarrayのみしか型付けできないデメリット - 追記:PHPdocにarray shapes記法で組み込みではないが型付けは可能 - 実装自体はすごくシンプルになる部分のトレードオフ - 型を付けようとDTOのような、結果とエラーの値をもったオブジェクトを使うのは失敗 43

Slide 44

Slide 44 text

© 2012-2019 BASE, Inc. © 2012-2021 BASE, Inc. PHP8での多値返却 - Union型が使えるのではないか - float | ZeroDivisionException、など - Union型であれば、PHPStanでの静的解析も適用できるのではないか - 本当は使い勝手の検証も含めて、実際に手元で何か開発しようと思っていたのですが時間 がなくできませんでした - 今後の検証課題です 44

Slide 45

Slide 45 text

© 2012-2019 BASE, Inc. © 2012-2021 BASE, Inc. PHPでのthrowしない例外ハンドリング - PHP7:タプルによる多値返却 - PHP8:Union型による多値返却(厳密には多値返却ではない。使い勝手含めて要検証) - Union型の検証次第だが、総合して考えるとPHPで throw / try-catch-finally による例 外ハンドリングは避けられなさそう - 仮にジェネリクスとenum型がPHPに実装されたらまた検討してみてもいいかもしれません 45

Slide 46

Slide 46 text

© 2012-2019 BASE, Inc. © 2012-2021 BASE, Inc. 参考文献 - Steve McConnell,クイープ訳,CODE COMPLETE 第2版 上 完全なプログラミングを目指して, 日経BP - アンドリュー・ハント,デービッド・トーマス,村上 雅章訳,達人プログラマー,初版,ピアソン・ エデュケーション - バートランド・メイヤー,酒匂 寛訳,オブジェクト指向入門,第2版,原則・コンセプト,翔泳社 - Jim Blandy,JasonOrendorff,中田 秀基訳,プログラミングRust,初版,オライリー - Steve Klabnik,Carol Nichols, 尾崎 亮太訳,プログラミング言語Rust公式ガイド,初 版,KADOKAWA - 瀬良 和弘,水島 宏太,河内 崇,麻植 泰輔,青山 直紀,実践Scala入門,初版,技術評論社 46

Slide 47

Slide 47 text

© 2012-2019 BASE, Inc. © 2012-2021 BASE, Inc. ご清聴ありがとうございました