本稿の目標
- 自作関数の使い道がわかる
- 自作関数を作り、利用できるようになる
機能を拡張してみよう
サンプルで作ってきた連続攻撃の処理だが、こんな形に変更してみよう。
- きちんとダメージ表示する
- 連続攻撃中に倒したときに表示を変える
サンプル
例によって最初にサンプルを示す。
<?php $attack_times = $_REQUEST['times'] ?>
<?php $enemy_life = 20 ?>
<html>
<head>
<title>あなたの連続攻撃</title>
</head>
<body>
<h1>あなたの連続攻撃</h1>
<?php for($current = 0; $current < $attack_times; $current++) { ?>
<p>あなたの攻撃!(<?php echo $current + 1?>回目)</p>
<p style="padding-left: 2em;">
<?php if ($enemy_life <= 0) { ?>
敵はもういない
<?php } else {
$damage = rand(5,10);
$enemy_life -= $damage;
?>
<?php echo $damage ?>のダメージを与えた!
<?php if($enemy_life <= 0 ) { ?>
<strong>敵を倒した</strong>
<?php } ?>
<?php } ?>
</p>
<?php } ?>
</body>
</html>
これで5回攻撃をしてみよう。http://localhost:8000/continuous_attack.php?times=5
ダメージはランダムなので必要なターン数などは異なるだろうが、似たような結果になっただろうか。
解説
ごちゃごちゃしているが、こんな形になっている
$enemy_life = 20 // 敵のライフ初期値
// $count回攻撃を行う
for ($i = 0; $i<$count; $i++){
if ($enemy_life <= 0) {
// すでに倒しているとき
}
$damage = rand(5,10);
$enemy_life -= $damage;
// ダメージメッセージ
if ($enemy_life <= 0) {
// 倒したメッセージ
}
}
サンプルコードを見たときの感想はなんだっただろうか。
汚い!、なるほど、よくわからんなど、ネガティブなものではなかっただろうか。
安心してほしい。筆者もそう感じている。こんなコードを生み出し、メンテし続けるようになってはならない。
解決策を提示しよう。
改善後サンプル
<?php
$attack_times = $_REQUEST['times'];
/**
* 敵が死んでいるか判断
*
* @return boolean 敵が死んでいればtrue
*/
function is_dead($enemy_life)
{
return $enemy_life <= 0;
}
/**
* 敵を倒した場合のみメッセージを表示する
*
* @return string 倒したときのメッセージ
*/
function get_message_if_defeat_enemy($enemy_life)
{
$message = '';
if (is_dead($enemy_life)){
$message = '<strong>敵を倒した</strong>';
}
return $message;
}
/**
* 攻撃を実行し、メッセージを表示する。
*
* @return int 攻撃後の敵ライフ
*/
function attack_with_message($enemy_life)
{
$damage = 0;
if (is_dead($enemy_life)){
echo '敵はもういない';
} else {
$damage = rand(5,10);
echo $damage . 'のダメージを与えた!';
echo get_message_if_defeat_enemy($enemy_life);
}
return $enemy_life - $damage;
}
?>
<html>
<head>
<title>あなたの連続攻撃</title>
</head>
<body>
<h1>あなたの連続攻撃</h1>
<?php $enemy_life = 20 ?>
<?php for($current = 0; $current < $attack_times; $current++) { ?>
<p>あなたの攻撃!(<?php echo $current + 1?>回目)</p>
<p style="padding-left: 2em;">
<?php $enemy_life = attack_with_message($enemy_life) ?>
</p>
<?php } ?>
</body>
</html>
行数多くなってるじゃないか!
結局わからないよ!
そんな声が聞こえてきそうで不安だが、とりあえず解説をしていこう。
解説
関数呼び出し
まず注目してほしいのはhtml部分である。
<html>
<head>
<title>あなたの連続攻撃</title>
</head>
<body>
<h1>あなたの連続攻撃</h1>
<?php $enemy_life = 20 ?>
<?php for($current = 0; $current < $attack_times; $current++) { ?>
<p>あなたの攻撃!(<?php echo $current + 1?>回目)</p>
<p style="padding-left: 2em;">
<?php $enemy_life = attack_with_message($enemy_life) ?>
</p>
<?php } ?>
</body>
</html>
ダメージ表示の部分がシンプルになっている。
$enemy_life = attack_with_message($enemy_life)
の部分は、ダメージ計算後の敵ライフを保持するために変数へ格納している。
ダメージ表示はどのようにしているかというと、関数の中でecho
を使い表示しているため、html部分では意識していない1
自作関数
htmlから関数を呼び出してダメージ計算を行い、表示したがどのように実現されていたのだろうか。
まさかダメージを計算して表示するような関数が用意されていたわけではない。
ここで関数を自作することが役に立つ。
/**
* 攻撃を実行し、メッセージを表示する。
*
* @return int 攻撃後の敵ライフ
*/
function attack_with_message($enemy_life)
{
$damage = 0;
if (is_dead($enemy_life)){
echo '敵はもういない';
} else {
$damage = rand(5,10);
echo $damage . 'のダメージを与えた!';
echo get_message_if_defeat_enemy($enemy_life);
}
return $enemy_life - $damage;
}
関数は次のようにして自作することができる。
/**
* Docコメント
*/
function 関数名(引数1, 引数2, 引数...)
{
// 何らかの処理
return 戻り値;
}
Docコメントは、その関数を初めて見る人が処理の内容を読み解かなくても良いように用意するもので、書くことを是非心がけていただきたい。
前稿で、関数とは入力に対して期待する結果を返してくれるものと解説した。
入力は引数として定義した変数で受け取ることができ、結果はreturn
で返すことができる。
これが例えば画面に表示するだけの関数の場合、結果を返さないこともあるので、return
が必須というわけではない。
ただ、プログラムになれるまでは何らかのreturn
を意識して関数を作るようにしよう。
というのは、「その関数が何のためにあるのか」を見失わないようにすることで、その関数の存在意義を考えられるからだ。
何度も利用するための関数
/**
* 敵が死んでいるか判断
*
* @return boolean 敵が死んでいればtrue
*/
function is_dead($enemy_life)
{
return $enemy_life <= 0;
}
死亡判定は2回出てくるので、関数にしておけば条件が変わった場合に1カ所を修正すれば良い。
これは変数と同じ思想である。
初心者の頃には面食らうが、if
の中で書いていた条件式は比較演算子を利用したもので、演算子と呼ぶからには演算後の値を持っている。つまり、これによってtrue/falseの値を戻すことができるわけだ。
ネストを減らすための関数
/**
* 敵を倒した場合のみメッセージを表示する
*
* @return string 倒したときのメッセージ
*/
function get_message_if_defeat_enemy($enemy_life)
{
$message = '';
if (is_dead($enemy_life)){
$message = '<strong>敵を倒した</strong>';
}
return $message;
}
導入編で言及したように、一度の処理で制御構文が増えると、どの条件で何が行われるのかを理解することが困難になる。
これをネストが深いと呼ぶ。
今回はattack_with_message
がif
で分岐をしている中で、他にも条件判断が必要となっているので、if
を書きたいところだが、そうするとこの関数を読み解くときに3つの状態を気にしなければならない。
なのでここでの条件判断は2種類の状態に絞っておいて、別の関数get_message_if_defeat_enemy
に委譲することで、考えることが減る。
まとめ
関数を作るとき
- 何度も呼ばれる同じ処理があるとき
- 見た目がごちゃごちゃして理解を妨げるとき
- ネストが深くなるとき
- やっていることが明確になる単位に分けたいとき
関数の用途
- 処理をまとめて、何度も呼ばれやすくする
- コード理解を助けるために処理をまとめる
- ネストを減らして、分岐処理の脳内状態管理を楽にする
- 1処理でやりたいこと(関心)を明確にして、コードの意図を読む人に伝える
関数の作り方
function 関数名(引数1, 引数2, 引数...)
{
// 何らかの処理
return 戻り値;
}