『オブジェクト設計スタイルガイド』のススメ

この記事はPHPアドベントカレンダー13日目の記事です。
普段はPHPのレガシープロジェクトの機能追加や改善をしたり、PHPのソースコードからクラス図を自動生成するためのツール(php-class-diagram)を作ったりしている、しめじ(smeghead)です。

オブジェクト設計スタイルガイド

オブジェクト設計スタイルガイド(amazon)
今年読んだ本『オブジェクト設計スタイルガイド』が、実用的で有効な指針が網羅的に紹介されていてとても良い本でした。自分の最近の仕事でも、紹介されている指針をおおいに参考にさせてもらってます。『オブジェクト設計スタイルガイド』の中でも簡単に実践できる指針を紹介してみたいと思います。

サンプルコードについて

この本に出てくるサンプルコードは、PHPのようですがPHPではありません。以下のような幾つかの点でPHPではない架空の疑似コードで書かれています。
  • 変数の先頭に付ける$が無い
  • メソッド呼び出しの->.になっている
これは、PHPコードを見難いと感じる人に配慮した結果なのかもしれません。しかし普通に json_encodeのようなPHP特有の関数が出てきたりするので、完全にPHPを前提にして構成されていると思います。(個人的には動作するPHPで書いてくれた方が嬉しかったです)

指針の例

オブジェクトの設計についての説明の最初に、オブジェクトの種類は、サービスオブジェクトとそれ以外のオブジェクトの2種類に分類できるとされています。各章毎に指針が分類されて紹介されています。いくつか簡単な指針を紹介してみます。

9.8 デフォルトでメソッドやプロパティをprivateとする

オブジェクトの種類に依らず、オブジェクトのメソッドのプロパティのアクセス修飾子は、なるべく公開するものを減らすため、デフォルトをprivateにすることが推奨されています。オブジェクトを使う側から見ても、公開するプロパティは少ない方が使い方が明確になり、意図しない使われ方をするリスクを減らすことができるので、単純ですが効果的な指針だと思います。

9.7 デフォルトでクラスをfinalとする

可能であれば全てのクラスの定義をfinalにすることが推奨されています。実際にこのガイドを実践してみると、ここ最近は継承があまり使われなくなっていることも影響してると思いますが、新規クラスを作る時にfinalを付けることができない場面は、想像以上に少ないです。
クラス宣言にfinalを付けるということは「このクラスを継承して機能拡張をしてくれるな」という表明なので、デフォルトでクラスをfinalにすることは理に適っていると思います。
<?php
declare(strict_types=1);
final class Product{
    ...
}

9.1 振る舞いを設定可能にするためのコンスタラクタ引数を導入する

ファイルにログを出力するサービスオブジェクトのクラスに、ログファイルのパスが直書きされていると、いろいろ不都合があります。実行するOS環境によって違うパスに出力したかったり、テスト時に別のパスに出力して検証したいかもしれません。メソッドの振る舞いを設定可能にするために、コンストラクタで出力ファイルのパスを指定する形にする方法が推奨されています。
この例は、オブジェクトの設定(stringの値)をコンストラクタで注入するという簡単なものですが、より複雑な要求に応えるための類似する指針も多くあり参考になりました。
<?php
declare(strict_types=1);
final class FileLogger
{
    public function log($message): void
    {
        file_put_contents(
            '/var/log/app.log',
            $message,
            FILE_APPEND
        );
    }
}
変更後
<?php
declare(strict_types=1);
final class FileLogger
{
    public function __construct(string $filePath)
    {
        $this->filePath = $filePath;
    }
    public function log($message): void
    {
        file_put_contents(
            $this->filePath,
            $message,
            FILE_APPEND
        );
    }
}
$logger = new FileLogger('/var/log/app.log');

2.10 コンストラクタの中ではプロパティへの代入以外は何もしない

この指針は、サービスオブジェクトに対する指針です。
サービスオブジェクトのコンストラクタは依存関係を注入することが目的であり、それ以外の副作用を伴なう処理は行うべきではないと理解しました。
コンストラクタの引数が、オブジェクトが動作するために適切な値であるかどうかのチェックは行なうが、コンストラクタの引数をプロパティに代入するに留めるのが良いということです。例として出てきていた避けるべきものは、「FileLoggerサービスのコンストラクタで、出力対象のディレクトリが存在しなければディレクトリを作成するという処理が副作用を伴なう処理である。」というものでした。
どうしても必要な副作用を伴なう前処理が必要なら、外部のLoggerFactoryでそれらの処理を行なうようにすることで、FileLoggerサービスのコンストラクタは、プロパティへの代入だけを行なう形にすることもできます。

まとめ

本に書かれている簡単な指針をいくつか紹介してみました。他にもオブジェクトを使うさまざまな場面での指針が、章毎に分類されて示されています。
もちろん、全ての指針に準拠しなければならないという強制的なものではありません。自分の考え方と合わない指針もあるかもしれません。それぞれの指針の意図するあるべき姿を考えてみることで、オブジェクト設計で考慮すべきポイントを学ぶことができます。
また、他のひとのコードをレビューすることがある人にとっても、章毎に分類された指針は改善点を文書化する際のよい指標になります。
全体的にとても読み易い本だと思うので、PHPを使う多くの人達にお勧めします。

コメントする

メールアドレスが公開されることはありません。 が付いている欄は必須項目です


reCaptcha の認証期間が終了しました。ページを再読み込みしてください。

このサイトはスパムを低減するために Akismet を使っています。コメントデータの処理方法の詳細はこちらをご覧ください