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

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

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

2016年11月3日にPHPカンファレンス2016が開催されました。本稿では,ゲストスピーカーである和田卓人さんによる講演「PHP7で堅牢なコードを書く - 例外処理,表明プログラミング,契約による設計」についてレポートします。

PHP7では例外や表明の機能が大幅に見直され,強化されました。この講演では,例外処理を設計する際の基本的な考え方や,表明(assertion)の使い方,そして表明と例外を使い分け,堅牢なコードに導くための設計手法「契約による設計(Design by Contract)」の考え方を説明しました。

画像

導入

はじめに,和田さん自身が監訳に関わった『SQLアンチパターン』に掲載されているコードを,よりひどくさせた次のコードを示しました。このコード自体は検索を行うものなのですが,いくつか問題があると言います。どこに問題があるかを会場に問いかけました。

画像

和田さんは,関数の中身はたった6行にも関わらず,そのうち4行にバグが起こる可能性があり,そのバグの状況としては次の8つが考えられると指摘しました。

  • データーベース接続確立失敗(誰かがマイグレートを実行している等の考慮がない)
  • usrpasswd等キー名が変更された
  • テーブル名やカラム名が誰かに変更された
  • $paramsnull
  • $paramsのキー名や数の不一致(値がおかしい可能性)
  • $paramsの値が文字列に変換不能(暗黙のキャストに失敗する可能性)
  • Bugクラスが未定義(Bugクラスに配列をマッピングしているが,そもそも配列がない場合の考慮がない)
  • 途中でデーターベース接続エラー

この中で一番手強いと感じるバグとして,$paramsのキー名や数の不一致」を挙げました。理由としては,裏側でWarningが出ているのにも関わらず,特にエラーで落ちたりせず,表面上の検索結果としては1件も引っかからなかった場合と同じように見えるからだそうです。つまり,正常系と見分けがつかないのが問題だと言います。この場合,和田さんは「不具合の発見が遅れるので,傷が深くなる可能性がある」と指摘しました。

そして,ソフトウェア開発の進み具合とともに「不具合の発見が遅れれば遅れるほど傷は深くなる」ことを図示しました。和田さんは「とにかくバグを早く見つけることがもっともコストを下げる方法であり,開発の後半になればなるほど関係箇所が増えて面倒になる」と述べました。

画像

さらに,ソフトウェア開発者のマイケル・ジャクソンさんの言葉を引用し,「賢明なソフトウェア技術者になるための第一歩は動くプログラムを書くこと正しいプログラムを適切に作成することの違いを認識すること」であると話しました。また,氷山の断面図のスライドを示して,「堅牢なコードは,50%以上がエラーハンドリング」と言及し,「大事なのは氷山の下のほうであり,実際に動くコードではない」と述べました。

以上がこの講演の導入部です。バグの早期発見のためにどのようなプログラミングをおこなえば良いのか,和田さんは「予防的プログラミング」「攻撃的プログラミング」「契約プログラミング」に分けて説明していきました。

予防的プログラミング

先に示した一連のコードに内包されるバグは,データーベースの接続確立失敗はインフラの問題を表し,usrpasswdのキーが変更されるのはバグであるし,テーブル名やカラム名が誰かに変更されるのはコミュニケーションの問題な気もする,とコメントした後に,まずは次の3つの心配事について考えてみると話しました。

  • $paramsnull
  • $paramsのキー名や数の不一致がある
  • $paramsの値が文字列に変換できない

そこで取り上げるのが予防的プログラミングです。これは,対処よりも予防のほうが低コストなのだから,間違ってからどうにかしようとするのではなく,そもそも間違わないようにしようというプログラミングの方法だと言います。

予防的プログラミングに近いものとして防御的プログラミングがあります。これは端的に言えば「かもしれない運転」であり,「決めつけを行わない」プログラミングの方法です。「誰も信じない。自分は自分で信じる」というのは,大事なプラクティスですが,誤解も多くあると指摘しました。典型的な誤解として,次の例を挙げました。

  • 徹底的に値を確認する
  • おかしな値が来たけど,なんとかゴニョゴニョいいようにする
  • パラメーターの中身をコメントで書きまくる

和田さんは「これは問題を解決していることにはならない」と言います。「防御的プログラミングとは悪いコードに絆創膏をあてることではない」とし,防御的プログラミングはいろいろな人が様々な解釈をしているが,和田さんが好きな解釈としては「問題発生を事前に防ごうというコーディングスタイルのことである」と述べました。

そして,次のような良いプラクティスの積み重ねこそが,防御的プログラミングであるとも話しました。

  • 可読性の高いコードと適切な命名規則
  • すべての関数の戻り値をチェック
  • デザインパターンの採用

型の制限

ここで『プログラマが知るべき97のこと』から「正しい使い方を簡単に,誤った使い方を困難に」を引用し,その中で「誤った使い方をすることが困難」に注目するとして,型を取り上げました。

例えば,型をひたすらチェックするくらいであれば,タイプヒンティングを利用しようと言います。その際,見知らぬ人ともうまくやるには「『出来てはならぬことを禁じる』のではなく,はじめから『出来ていいことだけを出来るようにする』と考える』(『プログラマが知るべき97のこと』)という言葉を引用していました。

防御的プログラミングの例として,そう変更する前と後のコードを示し,「入り口をガバガバにしていたのでいろいろチェックが必要だったが,そもそも受ける型を作っておけばここまでソースは減る」と述べました。やり方としては入ってくる値の種類を制限しようという話だとし,列挙型Enumを紹介しました。

「問題領域の知識を活用して固有の型を作ることで,取り得る組み合わせを大幅に減らせる」(『プログラマが知るべき97のこと』)ことを,和田さんはコードを指し示して説明しました。ステータスの文字列はOPEM, NEW, FIXEDのたった3種類であること,そもそもStringintを使う必要がないことを示しました。

class $status extends Enum
{
    const OPEN = "OPEN";
    const NEW = "NEW";
    const FIXED = "FIXED";
}
$status = new Status(Status::OPEN);
$status = new Status('OPEN');

$status = new Status('HOGE');<<<<エラーが出る

これで列挙型ができるため,次のようなコードで利用できるとのことです。

public static function findAll(int $assignedTo, Status $status)

以上により,$paramsに関する心配事に対応できるとしました。

多すぎる責務に起因する問題

次に,知りすぎ,多すぎる責務に起因する次の処理失敗の問題を取り上げました。

  • データーベース接続確立失敗
  • usr, passwd等キー名が変更された

この部分のコードは検索を行うコードです。データーベースに接続しに行くことは,責務ではないとし,やらなくても良い責務を減らす必要があると指摘しました。「知りすぎない。やりすぎない」が大切とのことです。

そしてPDOを作るところまでは外で行い,コンストラクタで受け取るようにコードを変更することで,グローバル変数の抹殺にも成功しました。

予防的プログラミングの話のまとめとしては「予防にまさる防御なし」と締めくくりました。また,大量のコードがかなり小さくなった(44行もあったコードが15行になった)ことを示しつつ,「防御的プログラミングは,考えなければならない条件を少なくすることが重要」と述べました。

画像

著者プロフィール

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

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

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

コメント

コメントの記入