PHPの基礎④関数 (独学エンジニア レッスン12より)

PHPの基礎を学ぶ④関数

<strong>タスクばらし</strong>
* 関数の基礎
* 無名関数
* クイズ
* 発展

# 関数の基礎
<strong>関数の引数</strong>
* 関数の引数にはいくつか種類がある。デフォルトは値渡し

リファレンス渡し
※あまり使用しない
```
function change(&$string)
{
	$string = 'after';
}
$str = 'before';
change($str);
echo $str;  // => 'after'
```

デフォルト引数
```
function purchase($num = 1)
{
	return $num . '個購入した' . PHP_EOL;
}
echo purchase();  // => 1個購入した
echo purchase(2);  // => 2個購入した
```

可変長引数
```
function product(...$numbers)
{
	$sum = 1;
	foreach ($numbers as $n) {
		$sum += $n;
	}
	return $sum;
}
echo product(2, 4, 8);  // => 64
```

名前付き引数
```
function register($name = null, $age = null)
{
	$inputs = ['name' => $name, 'age' => $age];
	foreach ($inputs as $k => $v) {
		if (isset($v)) {
			echo $k . 'に' . $v . 'を登録' . PHP_EOL;
		}
	}
}
register(age: 10);  // ageに10を登録
```

<strong>戻り値</strong>
* 関数で複数の値を返したい時は配列を使う
* return文を省略した時はnullが返される

```
function letters()
{
	return ['a', 'b', 'c'];
}
[$first, $second, $third] = letters();
echo $first;  // => 'a'
```

<strong>可変関数</strong>
* 可変関数を使うことで、関数の呼び出しを動的に行える
```
function add($x, $y)
{
	echo $x + $y;
}
function sub($x, $y)
{
	echo $x - $y;
}
$calc = 'sub';
echo $calc(20, 10) . PHP_EOL;  // => 10

$inputs = [
	['sub', 20, 10],
	['add', 20, 10],
];
foreach ($inputs as $input) {
	echo $input[0]($input[1], $input[2]) . PHP_EOL;
	// => 10, 30
}
```

# 無名関数
* 無名関数とは関数名を持たず、値として受け渡しできる関数。クロージャとも呼ばれる
* 無名関数は関数の引数に渡したい時に使うことが多い。関数を定義しない分、省スペースでスッキリ書ける
* 無名関数はその時にしか使用しない処理の際に使用することが多い

関数定義
```
function double($num)
{
	return $num * 2;
}
$numbers = [1, 2, 3];
$doubles = array_map('double', $numbers);
var=_export($doubles);  //  => [2, 4, 6]
```

無名関数
```
$numbers = [1, 2, 3];
$doubles = array_map(function ($num) {
	return $num * 2;
}, $numbers);
var_export($doubles);  //  => [2, 4, 6]
```

* 無名関数で親のスコープの変数を引き継ぎたいときはuseを使う

親スコープの変数は使えない
```
$times = 3;
$numbers = [1, 2, 3];
$products = array_map(function ($num) {
	return $num * $times;
}, $numbers);
var_export($products);
// => Warning: Undefined variable $times
```

useを使って引き継ぐ
```
$times = 3;
$numbers = [1, 2, 3];
$products = array_map(function ($num) use ($times) {
	return $num * $times;
}, $numbers);
var_export($products);  // => [3, 6, 9]
```

* 無名関数を簡単に書く構文がアロー関数
fn (引数) => 式

無名関数
```
$times = 3;
$numbers = [1, 2, 3];
$products = array_map(function ($num) use ($times) {
	return $num * $times;
}, $numbers);
var_export($products);  // => [3, 6, 9]
```

アロー関数
※親スコープの変数を自動的に使えるところだけ無名関数とは違う
```
$times = 3;
$numbers = [1, 2, 3];
$products = array_map(fn ($num) => $num * $times, $numbers);
var_export($products);  // => [3, 6, 9]
```

# 発展
<strong>関数を作成する方法</strong>
* 複雑さの低減
関数にして詳細な処理を抽象化し隠蔽することで、一度書いてしまえばその後詳細を考える必要がなくなる

具体的な処理をそのまま記載
```
$discount = 0;
if ($number >= DISCOUNT_NUMBER) {
	$discount = DISCOUNT_PRICE:
}
```

関数で抽象化
```
function discountPrice($number)
{
	$discount = 0;
	if ($number >= DISCOUNT_NUMBER) {
		$discount = DISCOUNT_PRICEl
	}
	return $discount;
}

$discount = discountPrice($number);
```

* コードの重複回避
関数にして処理を一箇所にまとめることで、変更による影響を一箇所に限定できる

重複した処理
```
if (($card1 - $card2) === 1) {
	// 処理
}
// 処理

if (($card1 - $card2) === 1 || ($card1 - $card2) === 12) {
	// 処理
}
```

関数で共通化
```
function isStraight($card1, $card2)
{
	return ($card1 - $card2) === 1;
}

if (isStraight($card1, $card2)) {
}
if (isStraight($card1, $card2) || ($card1 - $card2) === 12) {
}
```

* 1〜3行ほどの処理でも単一の機能なら関数にすることで読みやすくなる
ロジックを集約できるのは大きなメリット

関数でない処理
```
if (($card1 - $card2) === 1) {
	// 処理
}
```

関数にする
```
function isStraight($card1, $card2)
{
	return ($card1 - $card2) === 1;
}

if (isStraight($card1, $card2)) {
}
```

<strong>関数の凝集度</strong>
* 凝集度とは
凝集度とは関数内の処理がどれだけ蜜に関連しているかを表した概念

<dl>
  <dt>凝集度が低いと</dt>
  <dd>その関数が何をするものか理解しづらくなる</dd>
  <dd>再利用しづらくなる</dd>
  <dd>テストや保守がしにくくなる</dd>
  
  <dt>凝集度が高いと</dt>
  <dd>その関数がやることが明確で理解しやすい</dd>
  <dd>再利用しやすい</dd>
  <dd>テストや保守がしやすい</dd>
</dl>

* 凝集度の種類
機能的凝集度が理想だけど、それ以外の凝集度の関数があってもOK。
関数はできるだけ凝集度を高くするように設計しよう　　　　　　　　　　　　　　　　　　　　　　　　　　　　　　　　　

<dl>
  <dt>機能的凝集度</dt>
  <dd>単一のタスクを実行する関数。理想形</dd>
  <dd>ex) getUserName(), caluculateTotalPrice()</dd>
  
  <dt>情報的凝集度/逐次的凝集度</dt>
  <dd>1つの関数の中に2つ以上の機能が含まれているが、それらの機能は関連性が高く、またその順に処理していくことに意味がある関数</dd>
  <dd>ex) 生年月日→年齢を算出→定年までの年数を算出</dd>
  
  <dt>連絡的凝集度/通信的凝集度</dt>
  <dd>関数内の処理が同じデータを使用するが、それ以外に関連性がない関数</dd>
  <dd>ex) 商品を購入→購入した商品を保存、購入した商品をカートから削除</dd>
  
  <dt>手順的凝集度</dt>
  <dd>単に順番になっている処理を幾つかまとめた関数</dd>
  <dd>ex) ユーザーの名前を取得→住所を取得 / 性別を取得→職業を取得</dd>
  
  <dt>時間的凝集度/一時的凝集度</dt>
  <dd>同時に実行される複数の処理をまとめた関数</dd>
  <dd>ex) startup()</dd>
  
  <dt>論理的凝集度</dt>
  <dd>複数の処理が含まれており、渡されたフラグによっていずれかの処理が選択される関数</dd>
  <dd>ex) saveAll()</dd>
  
  <dt>暗号的凝集度</dt>
  <dd>何の関係性もない処理が複数含まれる関数</dd>
</dl>

<strong>関数を使いこなそう</strong>
* 良い関数名を付けることで関数が何をするのかが明確になる
関数が行うことを全て説明しよう
返り値を反映させる名前を付けよう
名前は必要な長さにしよう
あいまいな動詞を使わない
関数名を数値で区別しない
正確な反意語を使おう

```
get/set                           begin/end           up/down
open/close                     first/last              old/new
show/hide                      create/destroy    insert/delete 
increment/decrement     start/stop            min/max
```

* 引数は関数のインターフェイス。引数の分かりやすさが関数の品質に直結する
全ての引数を使用しよう
引数の型を明記しよう
引数の数は7個以下にしよう
複数の関数が似たような引数を使用する場合は順番を統一しよう
引数を作業用変数としては使用しない

before
```
function culculate($input) {
	$input = $input * bonusRate($input);
	$input = $input + bonusPoint($input);
	return $input;
}
```

after
```
function culculate($input) {
	$inputVal = $input;
	$inputVal = $inputVal * bonusRate($inputVal);
	$inputVal = $inputVal + bonusPoint($inputVal);
	return $inputVal;
}
```

* 考えられる全ての状況において関数が値を返すことを確認しよう
※関数の先頭で返り値を初期化して値を入れておくのも良い方法

全てのケースが網羅されていない
```
function checkHand($card1, $card2) {
	if (isStraight($card1, $card2)) {
		return 'straight';
	}
	if (isPair($card1, $card2)) {
		$name = 'pair';
	}
}
```

全てのケースを網羅
```
function checkHand($card1, $card2) {
	$name = 'high pair';
	if (isStraight($card1, $card2)) {
		$name = 'straight';  // return 'straight' もOK
	}
	if (isPair($card1, $card2)) {
		$name = 'pair';  // return 'pair'もOK
	}
	return $name;
}
```

# 参考サイト

[独学エンジニア](https://dokugaku-engineer.com/course/serverside "独学エンジニア")