PHPカンファレンス2016 レポート

和田卓人さん,PHPで堅牢なコードを書く—例外処理,表明プログラミング,契約による設計 〜PHPカンファレンス2016

この記事を読むのに必要な時間:およそ 4 分

攻撃的プログラミング

攻撃的プログラミングの「攻撃的」とは,⁠なにかおかしいことが起きたら,例外を発生させたりエラーを発生させて即時落とす。速やかに停止させる。無理やり動作させても,どうせその先で落ちるならさっさと停止させる。これで傷が浅くなる。障害を抱えて中途半端に動いているプログラムよりも死んだプログラムのほうがいい」と紹介しました。

「そんなにかんたんにシステムを落としていいのか?」という懸念については「アーキテクチャとプログラミングスタイルは別である」だとし,正当性と堅牢性を次のように説明しました。

  • 正当性とは,不正確なことを出すくらいなら死ぬこと
  • 堅牢性とは,ほどほどに死なないこと

和田さんは「個々のクラスは正当性を重視し,穏当なエラー画面等に変えるのはフレームワークとか外側のレイヤーですべきである」と述べ,システム全体としては堅牢性を重視し,個別としては正当性を重視する考え方が望ましいと話しました。

表明プログラミング

正当性の話として,ここで「Bugクラスが未定義」という心配事を取り上げました。この問題は,表明プログラミングで解決できると指摘しました。⁠起こるはずがない」と思っているのであれば,表明しようということだと言います。

PHP7ではassert関数を使って書けます。特にPHP7では,文字列を渡す形ではなく,評価式を渡すことができる完全なものになったと言います。思い込みが思い込みの通りであることを確かめる……つまり「Bugクラスがある」と考えているなら,assert(class_exists("Bug"));と書こうと話しました。

しかし,このようにassertを書いて実行してもWarningしか出ないし,落ちないと言及しました。これはPHP7のassertの標準設定でassert.Exceptionが0となっているため,警告を出すのに留まっていると指摘しました。この設定は「今まで動いていたシステムを落とさないようにするためだと考えられる」と述べていました。ただ今回は自殺してほしいので,assertExceptionにして,表明違反で落とすように設定します。

なお,表明のメリットは大きく分けて,⁠コミュニケーション」「デバッグツール」としての観点があると言います。コミュニケーションについては,書き手がコードの読み手に情報を与えられるという話で,バグをその原因に近いところで発見しやすくなります。デバッグツールについては,テストコードを書いているのであればさほど重要ではなく,コミュニケーションメリットのほうが大きいと説明しました。

さらに本来のエラー処理に表明を使うのではなく,あくまでも暗黙の前提を明らかにするために使うべきであると注意しました。和田さんは「エラーの発生が予測できる時は,エラー処理のコードを使いましょう」と述べました。

「表明を書き過ぎたら,コードがどんどん遅くなるのではないか?」という疑問については,PHP7のassertは単なる関数ではなく言語構造なので,実行段階ではオフにできると紹介しました。

  • zend_assertions:1(コードが生成される)
  • zend_assertions:0(コードを生成するが,実行時には読み飛ばす)
  • zend_assertions:-1(コードが生成されない)
画像

zend_assertions-1に設定すればノーコストになるとのことです。ただし,オフにされることを意識してコードを書く必要があると注意しました。例えばassert(end($users));のようなコードを書くと,このコードの前後で内部ポインタの位置がずれてしまうため,このような表明を書いてはいけないと指摘しました。

以上により,⁠Bugクラスが未定義」という心配事に対応できました。

エラーハンドリング

「途中でデーターベース接続エラー」という心配事については,⁠エラーは無視しても何も良いことはない。不安定なコードにもなる。だから戻り値を使うようにして,エラーハンドリングをしたほうが良い」⁠プログラマが知るべき97のこと⁠⁠)と説明しました。

戻り値を使うとfalseのチェックができ,エラーを処理できますが,コードは増えます。例として9個も多重ネストをしているエラーハンドリングのコードを示しました(インデントの形状から波動拳コードとして紹介しました⁠⁠。この深いネストのコードは特定の条件をチェックしてfalseならばすぐ落とすといったコードを書くことで,発生を防止できると紹介しました。

しかし,たくさんチェックしているコードは堅牢ですが,肥大化しがちで無視されやすいのが問題だと話しました。

例えば,PDOでエラーになった時の処理は,わりと読み飛ばされて無視されやすく,テスト中は見つからずに問題が発覚した時に調べることになると言います。和田さんは「気がつきにくさは問題を深くさせる」と述べました。

この対策としては,PDOを例外モードにするPDO::ERRMODE_EXCEPTIONことです。例外を発生させた結果として,falseの代わりにPDOExceptionが発生するようになって無視できず,わかりやすいコードになると説明しました。

責務を移譲(今回の例では,メソッドの返り値から例外へと責務を移譲)したら,その暗黙の前提をassertに書くようにします。和田さんは,assertならば,無視できない方法でエラー状態を知らせることができる。エラーを握りつぶすことは可能だが,そういうコードは見つけやすい。握りつぶしには根拠が必要であり,理由もなく握りつぶすのは書き手の姿勢に問題があるとすぐにわかる。超攻撃的スタイル」と述べました。

契約プログラミング

最後の心配事は「テーブル名やカラム名が誰かに変更された」です。和田さんはこの問題に対応するために,誰の責務かをはっきりさせる「契約による設計」を取り上げました。

契約による設計は,『オブジェクト指向入門 第2版 原則・コンセプト』に解説されています。ソフトウェアモジュールの権利と責任を文章化(そして承諾)し,プログラムの正しさを保証するための完結かつパワフルな技法とのことです。

正しさの公式は,数学的に{P}A{Q}のように表現されます。事前条件Pが成り立つ時にプログラムAを実行し,その実行後には必ず事後条件Qが成り立つならば,プログラムAは正しいという表現だそうです。

和田さんは「コードを呼び出す人と,呼び出される人との間での取り決めがあれば約束を守る」ことであると説明しました。そして,次のフローチャートを示し,⁠常に失敗するかどうかで,メソッドが動かなかった時にバグなのか,例外的状況なのかを切り分けるようになる」と指摘しました。

画像

契約による設計を行った時,実行時の表明違反はそのソフトウェアにバグがある証拠であり,⁠事前条件違反は呼び出し側にバグがあり,事後条件違反は供給者側にバグがある」と言及。和田さんは「表明によって,呼び出し側か供給側のバグなのか見分けることができる」と述べました。

さらに,PHP7の例外のツリーを紹介しました。catch Exceptionを書くだけではfalseを返すに等しいので,ツリーの適切な例外を返すようにすべきだと言います。ErrorLogicExceptionはバグで,この2つは供給者が必ず直す必要があります。RuntimeException(例外)は時折起きるため,供給者が直す必要があるかもしれないし,ないかもしれないとしました。

画像

例えばPDOExceptionが発生した場合,さらに分解してあげると誰のバグなのかわかるようになるとも指摘しました。LoginExceptionならば自分のバグであり,assertを出すようにしておけば自分のバグであると気がつけるようになると話しました。和田さんは「これで事後条件を守ることができる」と述べました。

画像

まとめ

最後に,和田さんは次のようにまとめて,講演を締めくくりました。

  • 予防的プログラミング「予防にまさる防御なし。ガバガバにするな」
  • 攻撃的プログラミング「Fail fast。障害を抱えて中途半端に動いているプログラムよりも死んだプログラムのほうがダメージは少ない。バグは死んだところに近いところにあったほうがいい」
  • 契約プログラミング「バグと例外を区別し,さらに誰の責任かも見分けられるようにする。俺のバグとお前のバグ」

著者プロフィール

中村慎吾(なかむらしんご)

仕事でPHPを使うようになって10数年。楽しい事には何でもやってみるスタイル。趣味が講じて(拗らせて?)会社も作りました。Web開発となるとPHPは手放せません。

Twitter:@n416
URLhttp://kisaragi-system.co.jp