PHP
PhpStorm
phpdocumentor
Docコメント

最低限書いておきたいPHPのDocコメント

More than 3 years have passed since last update.

最低限書いておきたいというよりは、最低限書いて下さいという私の切なる願いかもしれない。

そもそも何故Docコメントを書くのだろうか?

メソッドの命名さえ正確に行えるならDocコメントという存在はほぼ必要無いという考えがあり、
それは間違いでは無いでだろう。

しかし…

型が無いためにメソッド名を冗長にしてしまう

以下のコードは税率を取得するだけの単純なものだ。
TAX::getRate()と実行することで税率を取得出来る。
これなら、主語=TAX、述語=getRateと読み取れるので何をしているのか一目瞭然である。

class Tax
{
    private static $tax_rate = 0.8;

    public static function getRate()
    {
        return self::$tax_rate;
    }
}

しかし、これは戻ってくる値の型を予約出来る静的片付け言語であればの話だ。

もし、これが動的型付け言語だった場合、どんなことが予測されるだろうか?
戻ってくる値はinteger8かもしれない。
あるいはstring'8%'float0.8等幾つかの想像が掻き立てられる。

getRateという記述を見ただけでは何が返ってくるのか判断がつかないだろう。
これを正確に表すにはおそらくgetRateOfFloatなどのような冗長な命名が必要になってしまう。

複数の型を返す処理を命名で表現出来るか?

何かを取得して値を返すという処理を考えた時、その”何か”が取得出来なかった場合も考慮しなければならない。
以下のコードを見て考えてほしい。

function getBookArrayByUserIdFromCache($user_id, CacheInterface $cache)
{
    $books = $cache->read($user_id);
    return $books ?: false;
}

眉をひそめたくなるようなコードではあるが、これはPHPの文化なのであろう。
期待する値が取得出来なかった場合にはfalseを返すという文化があるようで、それは多くのコードに反映されている(私の知る限りでは…)。

さて、上記のコードはgetBookArrayとなっているので期待する返り値はBookを含んだ配列だと考えるだろう。
配列であればこれを利用するコードは以下のように書くことが想像出来ないだろうか?

$user_id = 1;
$cache = new Cache();
$books = getBookArrayByUserIdFromCache($user_id, $cache);

if (count($books) === 0) {
  echo 'ユーザは本を持っていなかった。' . PHP_EOL;
  exit;
}

foreach ($books as $book) {
    echo 'ユーザは' . $book . 'を持っている。' . PHP_EOL;
}

果たして、このコードは正しく動作するだろうか?

答えは正しく動作しない。
ユーザが本を所持していない場合、つまりfalseが返る場合PHP Warning: Invalid argument supplied for foreach()というエラーを発生させる。

これはphpのcount関数に以下のような仕様があるために発生する悲劇だ。

もしパラメータが配列もしくは Countable インターフェイスを実装したオブジェクトではない場合、 1 が返されます。

もし、このコードを正しく理解出来るようなメソッドの命名を行った場合、getBookArrayOrFalseByUserIdFromCacheのようなとても冗長な命名を行うしかない。

メソッドの実装をしっかり確認出来していたり、動的型付け言語特有の書き方if ($var) {/* ... */}などの記述を行えていれば防げる話ではあろう。
ただし、それは今回例にあげたようにメソッドの実装がシンプルな場合の話だ。
メソッドの実装が複雑化していけば行くほどにその返り値は何であるかを判断することは困難になる。

コメントさえあればcountなどという関数を使う事故は起こらなかったはず…

/**
 * ユーザIDをキーにキャッシュから本の配列を取得する。
 * 取得出来ない場合はfalseを返す。
 *
 * @param int $user_id
 * @param CacheInterface $cache
 * @return array|bool
 */
function getBookArrayByUserIdFromCache($user_id, CacheInterface $cache)
{
    $books = $cache->read($user_id);
    return $books ?: false;
}

動的型付け言語に不足する部分を埋めるものがDocコメントなのである

Docコメントを書くことで型の情報を補完することが可能になり、
またDocコメントで型の情報を補完することでメソッド名をシンプルに保つことが可能になるのだ。

Docコメントが書かれて初めてその処理が完成すると考えてしまってよいのではないだろうか?

書くべきDocコメント

PHPDocの書き方については下記リンク先を見ておくと良いだろう。
http://www.phpdoc.org/docs/latest/index.html

上記のリンクを見ても書くことの出来る内容が多くて何を書くべきか?に迷ううことや、
全部書こうとして無駄なコストを発生させてしまう恐れもある。

Docコメントを書いて処理が完成と先に述べたが、全てを細かに書く必要などはないのだ。

必ず書くべき

必ず書かなければならないのは、動的型付け言語に不足している部分
つまり型の情報だ。

型に関するアノテーション

この3つは必ず書くべきだ。(但し、戻り値の無いものについては@returnは省略しても構わない。)
パラメータや、戻り値の型を記述することでそのメソッド(関数)を利用しようと考えた時に、
何を渡せばよいか?何が返ってくるのかが明確になりコード書くうえでの迷いが無くなる。
また、幾つかのバグも抑制出来るようになるだろう。

@varについてはオブジェクトのプロパティに書くためのアノテーションだ。
これも書いておくことで、そのプロパティがどのように利用出来るかを知る術になる。

型については複数の型を返す場合にmixedと書くことが可能だが、これは避けたほうが良い。
複数の型を書く場合にはint|string|...のように記述することが出来る。
型の情報は可能な限り判断可能な形で表現しておくべきた。

説明については、省略可能であり、
プロパティ名、メソッド名の説明と型の注釈で十分に説明できているのであれば無理に記述する必要はないだろう。
(英語に不慣れなメンバーがいるのであれば書いておいて損はない、名前から意味を理解してもらえないからだ。)

@throws [Exception Type] [説明]

@throwsにはそのメソッドが発行する例外オブジェクトを定義しておくことが出来る。
例外を発行するメソッドには必須といっていいだろう。
これがあることで、開発者はそのメソッドを呼ぶときにどのようなtry ~ catchを書くべきかを判断できるようになる。

必要に応じて書くべき

状況によっては書いた方がよいものがある。

@link [url] [説明]

@linkは、そのメソッドを実装するきっかけになったやり取りへの参照を書いておくとよい。
(例えばGithubのissueであったり、redmineのチケット等)
Docコメントの中にも説明を書くことは可能だが、その説明はあくまでもそのメソッドが何をするか?に止めるべきだ。
多すぎる情報はかえって利用者の頭を混乱させる。

@final

@finalは、final修飾子が利用できないPHPのバージョンでは書くべきだろう。
一応説明しておくが、finalとはオーバーライドを禁止するための記述だ。

@access [アクセス修飾子]

@accessは、止む無く旧バージョンのPHP開発を行う場合には必須となる。
なぜなら旧バージョンのPHPにはアクセス修飾子(publicprotectedprivate)が無いからだ。
@accessに適切なアクセス修飾子を記述していないとprivateで利用することを想定したメソッドが外部からコールされてしまうという悲劇が起こる。
(もっとも、アクセス修飾子の書けないPHPなどを触ることがあるのかは分からないが。)

書く必要のないもの

残りのアノテーションは書く必要は無いだろう。
ライブラリ開発を行っている場合や、PHPDocumenterを利用してドキュメントの生成を行うのであれば@example@versionなどの情報はあったほうが良いかもしれないが、
ただ、それらのケースにあたらないのであれば不要な情報は増やす必要はない。

コメントと実態の乖離を防ぐために…

さて、コメントを書くべきではないという意見の中で最も参考にできるのが、

開発が進むにつれてコメントと処理の実態は乖離する

ということだろう。

これについては私もまっとうな反論を返すことは出来ない。
だが、ツールの利用やコードレビュー等によって限りなくこの問題を抑制することは可能だ。

ツールの利用やレビューによってコメントの正当性 > コメントと処理の乖離という状態が保てるようになればDocコメントは積極的に書くべきものになっていてもよいのではないか?

ツールについて

コメントと実態の乖離を防ぐために有用なツールが存在する。

PhpStorm(IntelliJ)

このIDEは有名すぎて、この場で多くを語ることは特に無いだろう。
Docコメントを記述しておけば、型の情報が乖離した時点でエディタ部分にalertが表示されるようになる。
この機能のお陰でDocコメントと処理の内容にズレば発生しても(開発者が注意してエディタをみていれば)すぐに問題に気付き修正することが可能だろう。

静的解析ツール

scrutinizerというCIサービスに利用されているphp-analyzerというツールがある。
メジャーバージョンはCIサービスでのみ利用可能だが、旧バージョンは Github上でApache license version2で公開されている。
この静的解析ツールを利用すればソースコードとDocコメントの乖離を指摘してくれるため、
ツールの実行さえ行えば問題に気づくことが出来る。

(探せばより優れた静的解析ツールはあるのかもしれない。)

最後に…

この投稿によって、より多くのDocコメントが書かれることを切に願います。
(それと、PhpStormの普及も願っています。)