PHP のソースコードを読みやすくするための工夫あれこれ

PHP Advent Calendar 2011 に参加しています。
PHP に関する記事を日替わりで書こうというイベントですね。
昨日の redsnow_ さんからバトンを受け取ってこの記事が14日目。

読みやすいってどういうこと

ソースコードは読みやすい方がいいし、
あとから修正しやすい方がいい。
難読化したい場合を除けば、
この点では誰もが一致するんじゃないかと思います。

でも、じゃあどういうのが読みやすいか
どのようにしてあればメンテナンスが楽かについては
人によって意見がわかれそう。

ということで、普段こういうふうにやってるよ、という例と
どうするのがいいかなあ、と思っているところと
人様のコードを見て「おおこれはいいね」と思ったものを書いてみます。

同じものを見て「そりゃあンた当たり前だろう」と思う人もいれば
「そんな書き方してる人がいるのか!」と驚く人もいるかもしれない。
そうなったら面白いな、と思って書いてみます。

スペースとインデント

えっとスペースとインデントの話は後述の話の基にもなるんだけど
あまり面白くなよく見る話ですし、まとめていきましょう。

  • 基本的に演算子と値の間、演算子と式の間はスペース。
    + とか = とか . とか && とか === とか。
  • でも加算子 (++) や減算子 (--) はくっつける。
  • 値をカンマ区切りするときはカンマの後ろにスペース。
  • 配列のキーと値を結ぶやつ、何て呼ぶのか知りませんけど => の前後にスペース。(名前わかりました。文末に追記あり)
  • 式を囲む () の前後にスペース。
  • ただし式が入れ子になるときは内側の式の前後にはスペースを加えない。
  • 関数やクラス定義の (){} の間にスペース。
  • ブレスで囲まれた範囲の行はひとつ分インデント。
  • インデントは使ってるフレームワークに倣うけどたいだいタブ。

むりやり全部入れてみた。

public function countHoge($users) {
	$hoge = 0;
	foreach ($users as $user) {
		$user = $this->_filterHoge($user, array('mode' => 'add'));
		if (($this->mode === 'hoge' && $user['hoge']) || $user['pro']) {
			$hoge++;
		}
	}
	return $hoge;
}

! と式の間

WordPress のプラグイン Ktai Style のソースを見て
「なるほどな」と思ったのがこれ。

前項のスペーシングのところで
「基本的に演算子の前後はスペース」って書いたけど、
否定の論理演算子 ! ではスペースを取ってなかった。

if (!$this->data)

でもこれだと、$ と並んでしまってるせいもあってか
! があまり目立たない。
あるかないかで意味が真逆になる大事な演算子なのに。

この方が際立ちますね。

if (! $this->ktai)

しかも公式の PHP マニュアルでも
! の後にはスペースが入ってる。

PHP: 論理演算子 – Manual

! $a 	否定 	$a が TRUE でない場合 TRUE

いままでやってなかったけど、この方が読みやすいかも。
トライしてみようかな。

振り返ってみると

「演算子」について言えば、これまで

  • 否定の !
  • 加算子の ++
  • 「負にする」の -
  • エラー制御の @ (これも演算子)

などでは対象となる値や式との間にスペースを取ってこなかった。

要するに値と値、式と式の間に入る演算子は前後にスペース、
そうでないものはスペースを入れない
というルールでやってきたような気がする。

PHP マニュアルでは

PHP マニュアルでは、次のものの後にはスペースが入ってるみたい。

  • 否定の論理演算子 !
  • 否定のビット演算子 ~
  • 型演算子 instanceof (ある変数が特定クラスのオブジェクトのインスタンスであるかどうかを調べるもの)

まあ instanceof の場合、スペースを入れないとこんなことになるから

($a instanceofMyClass)

必ずいるけど。

配列

PHP と言えば配列、配列と言えば PHP.
好むと好まざるとにかかわらず、
PHP と配列は切っても切れない縁ですね。

よく見かける書き方で、
配列が読みやすく書きやすなってるな、と思うものをいくつか。

キーと値ごとに1行

連想配列をずらずら書き並べると
どれがどれやらわかりにくいので

$params = array('conditions' => array('User.id' => $id, 'Post.status' => 'public'), 'limit' => $this->limit);

キーと値のペア1つごとに1行使う書ことが多いですね。
そして階層ごとにインデント。

$params = array(
	'conditions' => array(
		'User.id' => $id,
		'Post.status' => 'public'
	),  
	'limit' => $this->limit
);

最後の要素の後にも ,

PHP の配列では、要素の間は , で区切るわけですが
上記のように連想配列を書く場合は
最後の要素の後にも , を入れておくと後から便利なことがありますね。

こういう配列があったとして

array(
	'nts' => '肉玉そば',
	'ntu' => '肉玉うどん'
)

もしかすると後から別の要素を追加したくなるかもしれない。

ここで最後の要素の後に , が入っていれば

array(
	'nts' => '肉玉そば',
	'ntu' => '肉玉うどん',
)

そのまま新しい要素を貼り付けるだけでいけますね。

array(
	'nts' => '肉玉そば',
	'ntu' => '肉玉うどん',
	'ntsw' => '肉玉そばダブル',
	'ntuw' => '肉玉うどんダブル',
)

論理的にはあまり美しくないんだけど
こうしておいた方が断然便利なので
変更の可能性がある連想配列はだいたいこうしてます。

列をそろえる

頻繁に書き換える可能性があるものはともかく
いったん設定したら当分変えることのなさそうな設定値などは
縦方向に列をそろえておいた方が読みやすいですね。

これだと値の頭出しがばらばらなので、

public $defaults = array(
	'og:url' => false,
	'og:title' => false,
	'og:site_name' => false,
	'og:image' => false,
	'og:description' => false,
);	

=> の前にスペースを入れてそろえると

public $defaults = array(
	'og:url'         => false,
	'og:title'       => false,
	'og:site_name'   => false,
	'og:image'       => false,
	'og:description' => false,
);	

ただ要素の数が多い場合などは
いちいち手作業ではやってられません。
何かいい方法をご存じだったら教えてください。

長い文字列

文字列があまりに長いと見るのにも編集するのにも厄介なので、
たとえ一続きのものであっても
適当な長さで切った方がみんなの幸せのため。

例えば IPv6 のアドレスにマッチさせる正規表現はものすごく長くなるので
CakePHP の Validation クラスでは複数行に区切ってある。

$pattern  = '((([0-9A-Fa-f]{1,4}:){7}(([0-9A-Fa-f]{1,4})|:))|(([0-9A-Fa-f]{1,4}:){6}';
$pattern .= '(:|((25[0-5]|2[0-4]\d|[01]?\d{1,2})(\.(25[0-5]|2[0-4]\d|[01]?\d{1,2})){3})';
$pattern .= '|(:[0-9A-Fa-f]{1,4})))|(([0-9A-Fa-f]{1,4}:){5}((:((25[0-5]|2[0-4]\d|[01]?\d{1,2})';
$pattern .= '(\.(25[0-5]|2[0-4]\d|[01]?\d{1,2})){3})?)|((:[0-9A-Fa-f]{1,4}){1,2})))|(([0-9A-Fa-f]{1,4}:)';
$pattern .= '{4}(:[0-9A-Fa-f]{1,4}){0,1}((:((25[0-5]|2[0-4]\d|[01]?\d{1,2})(\.(25[0-5]|2[0-4]\d|[01]?\d{1,2}))';
$pattern .= '{3})?)|((:[0-9A-Fa-f]{1,4}){1,2})))|(([0-9A-Fa-f]{1,4}:){3}(:[0-9A-Fa-f]{1,4}){0,2}';
$pattern .= '((:((25[0-5]|2[0-4]\d|[01]?\d{1,2})(\.(25[0-5]|2[0-4]\d|[01]?\d{1,2})){3})?)|';
$pattern .= '((:[0-9A-Fa-f]{1,4}){1,2})))|(([0-9A-Fa-f]{1,4}:){2}(:[0-9A-Fa-f]{1,4}){0,3}';
$pattern .= '((:((25[0-5]|2[0-4]\d|[01]?\d{1,2})(\.(25[0-5]|2[0-4]\d|[01]?\d{1,2}))';
$pattern .= '{3})?)|((:[0-9A-Fa-f]{1,4}){1,2})))|(([0-9A-Fa-f]{1,4}:)(:[0-9A-Fa-f]{1,4})';
$pattern .= '{0,4}((:((25[0-5]|2[0-4]\d|[01]?\d{1,2})(\.(25[0-5]|2[0-4]\d|[01]?\d{1,2})){3})?)';
$pattern .= '|((:[0-9A-Fa-f]{1,4}){1,2})))|(:(:[0-9A-Fa-f]{1,4}){0,5}((:((25[0-5]|2[0-4]';
$pattern .= '\d|[01]?\d{1,2})(\.(25[0-5]|2[0-4]\d|[01]?\d{1,2})){3})?)|((:[0-9A-Fa-f]{1,4})';
$pattern .= '{1,2})))|(((25[0-5]|2[0-4]\d|[01]?\d{1,2})(\.(25[0-5]|2[0-4]\d|[01]?\d{1,2})){3})))(%.+)?';

これを1行で書くと、
特にひとつの行をまとめて表示しようとするエディタでは
1行の代入がものすごい範囲を占めてしまって
場合によってはまともに表示できなってしまう。

もちろん結合する分は演算が発生するから
少しだけ処理が増えると思うんだけど
コンピュータの時間より人間の時間の方が貴重ということですね。

長い条件式

これは「どうしたものか」と思っているものです。

if 構文などの条件式が長くなる場合
どこかで改行するのか、するとしたらどこでするか。

  1. 潔く改行なし

    if (isset($user['id']) && is_array($admins) && (in_array($user['id'], $admins) || $this->mode == 'read-only')) {
    	someaction();
    }
    
  2. 長くなるところで適宜改行

    if (isset($user['id']) && is_array($admins)
     && (in_array($user['id'], $admins) || $this->mode == 'read-only')) {
    	someaction();
    }
    
  3. 論理式の間で改行、演算子は後の行に含める、演算子の前のスペースは階層にあわせる

    if (isset($user['id'])
     && is_array($admins)
     && (in_array($user['id'], $admins)
      || $this->mode == 'read-only')) {
    	someaction();
    }
    
  4. 論理式の前後で改行、演算子は前の行に含める、階層ごとにインデント

    if (
    	isset($user['id']) &&
    	is_array($admins) &&
    	(
    		in_array($user['id'], $admins) ||
    		$this->mode == 'read-only'
    	)
    ) {
    	someaction();
    }
    

可能性を挙げたらきりがない。

ルールとして明快なのは1番目、人間らしいのは2番目、
構造が見やすいのは4番目かなあ。
でも3番目に近い書き方もよく見る気がする。

これどうしてますか。

まだまだ

すべての () の内側にスペースを入れる書き方とか
スペースを含む論理式は全部 () で括る書き方とか
三項演算子とどうつきあうかとか
読みやすさに関することはいっぱいあると思うんだけど
際限がないのでひとまずこのへんにしておきます。

コーディング規約というけれど

きっちり足並みを揃えて動いてる組織ならいいんだけど
特に大勢でよってたかってコードを書くプロジェクトなどでは
どうしても書式を完璧に揃えるというのはなかなか難しいんじゃないかな。

公開されているフレームワークや CMS のソースコードを見ても
必ずしも統一されていないものが多い気がする。

でもコーディング規約というのはお互いの幸せのためにあるものであって
全員が同じ書き方をすること自体が最終目的ではないので、
「こう書いた方が読みやすいよね、修正しやすいよね」
というのが大事なところ。

ここに挙げたのは個人的な好みにすぎないんだけど、
そういうことを話題にする機会が少しでも増えて
この世から「このコードを書いたのは誰だあ!」という叫びが減るといいな。

追記

コメント欄でも教えてもらいましたけど、
=> の名前わかりました。

次の人

PHP Advent Calendar 2011
明日は @bimihoujyun さんですお楽しみに!

関連エントリ

  • このエントリーをはてなブックマークに追加

3 Responses to “PHP のソースコードを読みやすくするための工夫あれこれ”