Help us understand the problem. What is going on with this article?

今からでも遅くない!PHPerも型を書こう!

More than 1 year has passed since last update.

最初にまとめ

型を書くとオブジェクト指向をちょっと意識しやすくなるよ

本稿の目的

PHPは歴史的に型を書かない人が多いです。
そういう自分もPHP7.x系になるまで書いたことはほとんどありませんでした。
書かなくても動くしなんでそんなめんどくさい事をわざわざ、、、みたいな。

そんな自分でも今は型が書いていないと気持ち悪いと感じる位になれたので、その思考の過程をご紹介していきたいと思います。

ただ、本稿は型の素晴らしさや型安全なプログラミングとは等を正攻法で説明するというのが意図ではないです。

ちょっと普段とは違う目線で型を書くメリットを紹介してみようと思います。

型 is 何?

型とはそもそも何でしょう?
端的に言えばデータ型という事になりますが、今回は少し違った見方をしたいと思います。

それは

『型とはふるまいに対する主語です』

本稿はこんな視点で型について説明したいと思います。

そもそもオブジェクトって

オブジェクト指向というからには必ず出てくるオブジェクト。

ざっくり言うと、データと振る舞いを一つの意味のあるまとまりとし、そのオブジェクト同士を協調させながらプログラムを書いていく手法です。

ここで重要となるのは『意味のある』という所です。
なんとなく便利な物を集めたライブラリではありません。

例えば Userクラスは ユーザーに関するふるまいやデータを、Itemクラスは商品に関するデータや振る舞いを持つことになるでしょう。

その際に重要となる事は適切な名前でデータや振る舞いを定義してあげる事だと思います。
そうでないと、このオブジェクト達が適切に対象を表現できているかわかりません。

言葉だけではわかり辛いので実際にコードで見ていきたいと思います。

主語をはっきりさせる事でよりオブジェクトの振る舞いを表現する

例えば、以下のようなコードがあったとします。

<?php
class ItemService
{
    /**
     * 商品を作成する
     */
    public function create($name)
    {
        return new Item($name);
    }
}

人間的な言葉で言えば、「名前を入力したら商品を作成してくれるメソッド」です。

ただ、処理側からみると主語が全然足りません。
ここで定義されているのはcreate(作成する)くらいのものです。
これではこのふるまいが適切に定義できているとはとても言えないでしょう。

「作成する」と言われても何もわかりません。
ItemServiceにあるから商品系の何かでしょうが、もしかしたら返品となった商品を作るメソッドかもしれません。

そこで適切に主語をつける為に商品を作成してくれるメソッドにしてみようと思います。

<?php
class ItemService
{
    /**
     * 商品を作成する
     */
    public function create($name): Item
    {
        return new Item($name);
    }
}

戻り値の型を書きました。

これだけで、商品(Item)を作成する(create) と表現できるようになりました。

ただ、まだ表現できそうですね。
$name の所です。
このメソッド(ふるまい)は名前を受け取って商品を作成するのです。
なので、この「名前を受け取って」も表現したい所です。
引数も型宣言をしてみようと思います。

<?php
class ItemService
{
    /**
     * 商品を作成する
     */
    public function create(string $name): Item
    {
        return new Item($name);
    }
}

これでさらに表現が深まりました!

このメソッドは

文字列(string) を受け取って、商品(Item)を作成(create)するメソッドです!

、、、ん、なんかおかしいですね。

そうです。
$nameは変数名では名前を表していますが、データ型としては単なる文字列です。
文字列とだけ言われても、それが商品名なのか商品説明なのかすらわかりません。

なので、この定義ではふるまいを正確に表現できていなそうです。
その辺も考慮して修正してみました。

<?php

class ItemName()
{
    private $name;

    public function __construct(string $name)
    {
        $this->name = $name;
    }
}

class ItemService
{
    /**
     * 商品を作成する
     */
    public function create(ItemName $name): Item
    {
        return new Item($name);
    }
}

商品名を表す ItemNameクラスを作成し、createの引数の型として宣言しました。
これによりどうなったでしょう。

商品名(ItemName)を受け取って商品(Item)を作成(create)する
と表現できるようになりました。

これであれば一目瞭然にこのメソッドの振る舞いがわかるようになりました。
そしてそれは人間がわかると言う意味ではなく、「処理としてわかる」という意味です。

そして、この事には大きなメリットがあります。

例えば元の状態の場合、この商品作成クラスを使用する際にバグがあって、商品名ではなく商品説明を渡してしまったとします。
商品名も商品説明もデータ型としては同じ文字列です。
恐らくその間違いに気づかずに処理は完了するでしょう。
そうなると、DBの中にはおおよそ意図しない商品達が作成されていくかもしれません。
気づくのはリリース後1ヶ月経ってから。。。なんて事も最悪あるかもしれません。

ですが、主語をプログラミングに取り入れた後(型宣言を記述した後)は違います。
もしItemNameクラス以外が引数で渡されたとしたら、その時点でエラーとなりすぐに検知できるでしょう。

また、PHPはコンパイルが無いので、実行時にしか気づけ無いと言われていますが、例えばPHPStorm等の静的解析をサポートしてくれる環境であれば、ある程度は実行する前に検知する事も可能です。

オブジェクトが持つべき振る舞いを主語を持たせる事によりより明確に適切に表現する事ができるのです。

そして、オブジェクト指向を意識するに当たって、これは原点と言って良いのではないかと筆者は考えています。
言語によってはダックタイピング等、型宣言以外の方法となるかもしれません。

ただ、方法は何にせよ、オブジェクトが表現対象のオブジェクトを適切に表現し、その相互作用により堅牢で変更容易性の高いプログラミングをする事ができる、という事がオブジェクト指向のメリットだと考えています。

最後にもう一つ具体例を記したいと思います。

class ItemService
{
    /**
     * 商品一覧取得
     */
    public function findAll(): array
    {
        // なんかDBから一覧とってくる処理達。記述はテキトー
        $model = $this->connection->get('Items');
        return $model->findAll();
    }
}

商品一覧を返すメソッドです。
今このメソッドの関数シグネチャ(関数定義)で表現できているのは

全件検索(findAll)をして配列(array)を返す

という事になるでしょう。
これではやはり不十分です。

このような配列を利用した処理は頻繁に出てくるかと思います。
そう言った場合はまずはファーストクラスコレクションを作成する事を検討してみて下さい。
それだけでオブジェクトが持つ振る舞いがより一層ブラッシュアップされます。

/**
* 商品一覧ファーストクラスコレクション
*/
class ItemList implements IteratorAggregate
{
    /**
     * @var array
     */
    private $data;

    public function __construct(array $data)
    {
        $this->data = $data;
    }

    public function getIterator()
    {
        return new ArrayIterator($this->data);
    }
}

class ItemService
{
    /**
     * 商品一覧取得
     */
    public function findAll(): ItemList
    {
        // なんかDBから一覧とってくる処理達。記述はテキトー
        $model = $this->connection->get('Items');
        return new ItemList($model->findAll());
    }
}

商品一覧という集合を表現するクラスを作成してみました。
このクラスは商品一覧を表現する為のクラスです。

これによりこのメソッドが表現している事は、

全件検索(findAll)をして商品一覧(ItemList)を返す

となりました。

より正確に表現できるようになったかと思います。

また、このようなクラスは単にシグネチャとしてだけ役に立つわけではありません。
このクラスが表現したい事は「商品一覧」という集合です。
配列と同じ様にloopで回すだけではなく、例えば、「件数は何件か?」や「価格の平均はいくらか?」などの振る舞いを持つこともあるでしょう。
そうする事により、必要な知識が必要なクラスに凝集されるようになるでしょう。

序盤に書いたそもそもオブジェクトとは?という答えに近いですが、このような「意味のある表現が凝集されたもの」がオブジェクト指向で表現したい「オブジェクト」なんだと思います。

まとめ

型を書こう!と言うのは2つの意味で書きました。

  1. 型宣言をしよう
  2. 型となるクラスを作成しよう

型とは(処理が理解できる)適切な名前付けです。
クラス名、関数名、変数名は適切であるべき、というのはほとんどの方が同じだと思うのですが、
それと同じベクトルで処理がわかる名前をしっかり付けてあげましょう。

既に世の中には素晴らしい書籍や説明が沢山ありますので、今回は『主語』という表現で少し違う視点で型の良さについて説明してみました。

こんな記事がきっかけで、型を記述するきっかけになれば幸いです。

hirodragon
/Miraito Design, Inc./CEO/Founder 好き→OOP/DDD/アーキテクチャらへん
https://miraito-inc.co.jp
miraito-inc
システムデザインを中心に置いた開発により高品質で使いやすいシステムを提供いたします。業務システム構築、アプリ開発、コンサルティングまで幅広く手がけています。
https://miraito-inc.co.jp/
Why not register and get more from Qiita?
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away