まずは以下のプログラムを見ていただきたい。
※このプログラム自体に意味はありません。参考用にサンプルで作ったものなので、ツッコミどころはいっぱいありますが、一応動きます。
<?php
$dbData = [
['id' => 1, 'type' => 1, 'col1' => '1-col1', 'col2' => '1-col2', 'col3' => '1-col3', 'col4' => '1-col4'],
['id' => 2, 'type' => 0, 'col1' => null, 'col2' => null, 'col3' => '2-col3', 'col4' => '2-col4'],
['id' => 3, 'type' => 2, 'col1' => '2-col1', 'col2' => '2-col2', 'col3' => '2-col3', 'col4' => '2-col4']
];
function fileterData(array $data)
{
$result = [];
foreach ($data as $datum) {
$id = $datum['id'];
$start = null;
$end = null;
for ($i = 1; $i <= 4; $i++) {
switch ($i) {
case 1:
$start = $datum['col' . $i];
continue 2;
case 2:
$end = $datum['col' . $i];
continue 2;
case 3:
case 4:
continue 2;
default:
continue 3;
}
}
if (!empty($start) && !empty($end)) {
$result[] = $datum['id'];
}
}
return $result;
}
print_r(fileterData($dbData));
php sample.php
を実行したときに得られる結果はなんでしょう?
php sample.php
Array
(
[0] => 1
[1] => 3
)
ですね!
つらいプログラムのトレース
メンテナンスすることになったプログラムに上のような記述がありました。
どこがつらいか皆さんはわかりますか?
私はいくつかあります。
- DBから取得したようなデータをループでフィルターしている。
- カラムに対してループ処理して、
type
とカラム名によって変数代入、カラムの意味が変わってくる。 - 2次元ループ内でswitch文、
continue 3
をしている。
一つ断っておくと、私自身は「つらいなぁ」とは思っていますが、100%否定もできないなと思っています。
というのも、DBデータをプログラム上でフィルターしたい場面は存在するし、カラムの意味を、例えばtypeカラムの値によって変更する設計はしばしば見受けられます。
できれば正規化していてほしいですが、「やむを得ない事情」というのは存在すると思うからです。
すべてが理想形どおりに進むわけはないと思っています。
continue 3
とか久しく見たことがなかった
いわゆるループ処理でよく使われるcontinue文には数字またはラベルというものを指定できます。
PHP: continue - Manual
このこと自体は私も10年以上前、専門学校でJavaを勉強していたときに習いました。
改めて当時の教本(当時はSun Microsystemsでした)を見返したところ、continue文の記載はあるものの、数値やラベルの指定を解説している箇所はありませんでした。おそらく同時の講師(めっちゃ板書がきれいな先生でした)が、基本だから抑えておくようにと思ったのだと思います。
continue 3
はどういう意味か?
上記のプログラムではcontinue 3
は意味をなしていないので、continue 2
を見ていきましょう。
と言っても、上の画像にほとんど答え書いてありますが。
ここに入ると処理は、for
のところまでスコープが戻ります。continue
以降の処理は実行されません。
ということはcontinue 3
はforeach
のところまでスコープが戻ります。
そしてプログラマは、考えるのをやめた
最初は処理がどうなっているのかプログラムをトレースしていました。
ロガーを入れてみたりしたけど、いまいちどうなっているのかわからない。
そしてcontinue 2
みたいな構文を見つけたとき、どうするか迷いましたが、トレースしてみることにしました。
ですが、数時間しても何がどうなっているのかわからなくなって、そして考えるのをやめた。
結果だけをみて、「Don't think. Feel.」することにしました。
「Don't think. Feel.」できない事態になったら?
その時考えることにしました。
とにかく今、デバッグされて、正常に動いているものを無理に時間を割いて理解するのは時間の無駄、、、と思うことにしました(本当にそれでいいのかという気はしています)。
避けるべきとされるプログラム
こういったプログラムを見るとよく思うのですが、このプログラムを作った人はすごい人なのかどうかということです。
ひとつ言えるのは、メンテナンスしていた人はすごいと思います。
入れ子のしすぎ
if文とかでもそうですが、制御ブロック、端的に言うとインデント、もう少し詳しく言うと入れ子(ネスト)は3つを超えだすと、かなり見づらく、トレース、デバッグがしづらくなり、関数やメソッドへの切り出しを考えるべきと思っています。
結局そのプログラムがリリースされるとしばらくは自分がメンテナンスすることになりますし、プログラムはだいたい80桁で折り返せって言われますよね。
縦に長いのはマウスとかでスクロールできますが、横に長いのはスクロールしづらい。
上のプログラムも同じで、せめて何らかの形で、switchは別の関数に切り出されていると、まだ可読性が増すのかなと思います。
そして早期リターンするとか。
do-while
書籍「リーダブルコード」では、do-while
も避けたほうがいいと言われています。
個人的には避けるべきだと思っていますが、Excelやスプレッドシートを処理するときにどうしても解決できない処理をdo-while
で解決したことがあります。
ここでは詳しく述べませんが、そのときひらめいたかのように「do-while
なら解決できるのでは?」と思ったのを覚えています。
ちなみに専門学校では「これを使いたがる人がいる」といった感じで教えられました。
goto
書籍「リーダブルコード」ではこれもアンチパターンとして紹介されていたような気がします。
私の先輩も避けるべきと言っていました。
goto文は、ラベルの付いたところまで処理を移動させる制御文。ループ処理に似ていますが、ちょっと違います。
goto文 - Wikipedia
ちなみにこれは専門学校でも習った気がするのですが、ノートに取ってなかったのか。。。
私はPHPerなのですが、PHP 5.3(2009年リリース)から導入されました。当時は今更なぜ?って感じでした。
その後、EC-CUBE案件をやったときにどれかは忘れましたが、プラグインの一つにgoto文が使われていました。
「嘘やろ・・・」と言ったことを覚えています。
まとめ
どんなプログラムにも、多かれ少なかれジレンマが存在していると思います。
過去の自分のプログラムを見て、「今ならもっとシンプルに作れるな・・・」、と思いながらも、デバッグも終わって安定稼働しているものを大きく変更するのは、それだけでリスクになります。
他人が書いたプログラムならなおさらです。
だからせめて、これから各プログラムは、気をつけていきたい。
そう思いながらプログラムを書いています。