Splitting the Phases of Calculation and Formatting
<?php
function statement($invoice, $plays)
{
$playFor = function ($perf) use ($plays) {
return $plays[$perf['playID']];
};
$amountFor = function ($aPerformance) use ($playFor) {
$result = 0;
switch ($playFor($aPerformance)['type']) {
case 'tragedy':
$result = 40000;
if ($aPerformance['audience'] > 30) {
$result += 1000 * ($aPerformance['audience'] - 30);
}
break;
case 'comedy':
$result = 30000;
if ($aPerformance['audience'] > 20) {
$result += 10000 + 500 * ($aPerformance['audience'] - 20);
}
$result += 300 * $aPerformance['audience'];
break;
default:
throw new Error('unknown type: ' . $playFor($aPerformance)['type']);
}
return $result;
};
$volumeCreditsFor = function ($aPerformance) use ($playFor) {
$result = 0;
$result += max($aPerformance['audience'] - 30, 0);
if ('comedy' === $playFor($aPerformance)['type']) $result += floor($aPerformance['audience'] / 5);
return $result;
};
$usd = function ($aNumber) {
$format = '$%.2f';
return sprintf($format, $aNumber / 100);
};
$totalVolumeCredits = function () use (
$invoice,
$volumeCreditsFor
) {
$volumeCredits = 0;
foreach ($invoice['performances'] as $perf) {
$volumeCredits += $volumeCreditsFor($perf);
}
return $volumeCredits;
};
$totalAmount = function () use ($invoice, $amountFor) {
$result = 0;
foreach ($invoice['performances'] as $perf) {
$result += $amountFor($perf);
}
return $result;
};
// ----------------------------------------
$result = "Statement for ${invoice['customer']}";
foreach ($invoice['performances'] as $perf) {
// print line for this order
$result .= ' ' . $playFor($perf)['name'] . ': ' . $usd($amountFor($perf)) . "(${perf['audience']} seats)" . PHP_EOL;
}
$result .= 'Amount owed is ' . $usd($totalAmount()) . PHP_EOL;
$result .= 'You earned ' . $totalVolumeCredits() . ' credits' . PHP_EOL;
return $result;
}
- HTML版レンダリング関数も作りたい
-
現状の
statement
関数は2種類の処理が混ざっている- 料金計算処理
- PlainText文字列の構築処理
- 現状の
statement
関数をコピペしてHTML版を作ると、料金計算処理が重複してしまう - そこで、処理を分割する
-
まず料金計算処理のstubを作り、少しずつ処理を移していく
-
新たに適用したリファクタリングパターン
Separated Into Two Files
-
処理を2つに分けた
- 中間データ生成
- レンダリング
- 別ファイルに再配置
- repos
- index.php
<?php
use App\CreateStatementData;
function statement($invoice, $plays)
{
$createStatementData = new CreateStatementData();
return renderPlainText($createStatementData($invoice, $plays));
}
function renderPlainText($data)
{
$result = "Statement for ${data['customer']}";
foreach ($data['performances'] as $aPerformance) {
// print line for this order
$result .= ' ' . $aPerformance['play']['name'] . ': ' . usd($aPerformance['amount']) . "(${aPerformance['audience']} seats)" . PHP_EOL;
}
$result .= 'Amount owed is ' . usd($data['totalAmount']) . PHP_EOL;
$result .= 'You earned ' . $data['totalVolumeCredits'] . ' credits' . PHP_EOL;
return $result;
}
// stub
function renderHtml($data)
{
return '';
}
function usd($aNumber)
{
$format = '$%.2f';
return sprintf($format, $aNumber / 100);
}
- CreateStatementData.php
<?php
namespace App;
class CreateStatementData
{
public function __invoke($invoice, $plays)
{
$amountFor = function ($aPerformance) {
$result = 0;
switch ($aPerformance['play']['type']) {
case 'tragedy':
$result = 40000;
if ($aPerformance['audience'] > 30) {
$result += 1000 * ($aPerformance['audience'] - 30);
}
break;
case 'comedy':
$result = 30000;
if ($aPerformance['audience'] > 20) {
$result += 10000 + 500 * ($aPerformance['audience'] - 20);
}
$result += 300 * $aPerformance['audience'];
break;
default:
throw new Error('unknown type: ' . $aPerformance['play']['type']);
}
return $result;
};
$playFor = function ($perf) use ($plays) {
return $plays[$perf['playID']];
};
$volumeCreditsFor = function ($aPerformance) {
$result = 0;
$result += max($aPerformance['audience'] - 30, 0);
if ('comedy' === $aPerformance['play']['type']) $result += floor($aPerformance['audience'] / 5);
return $result;
};
$totalVolumeCredits = function ($data) {
return array_reduce(
$data['performances'],
function ($accumulator, $aPerformance) {
return $accumulator + $aPerformance['volumeCredits'];
},
0
);
};
$totalAmount = function ($data) {
return array_reduce(
$data['performances'],
function ($accumulator, $aPerformance) {
return $accumulator + $aPerformance['amount'];
},
0
);
};
$enrichPerformance = function ($aPerformance) use (
$playFor,
$amountFor,
$volumeCreditsFor
) {
// PHPの配列は値渡し
$aPerformance['play'] = $playFor($aPerformance);
$aPerformance['amount'] = $amountFor($aPerformance);
$aPerformance['volumeCredits'] = $volumeCreditsFor($aPerformance);
return $aPerformance;
};
$statementData = [];
$statementData['customer'] = $invoice['customer'];
$statementData['performances'] = array_map(
$enrichPerformance,
$invoice['performances']
);
$statementData['totalVolumeCredits'] = $totalVolumeCredits($statementData);
$statementData['totalAmount'] = $totalAmount($statementData);
return $statementData;
}
}
- コード量は増えてしまった
- 他の点がすべて同じだとしたら、コード量が多いことは悪いこと
- だが、「他の点がすべて同じ」になることはまずない
Brevity is the soul of wit, but clarity is the soul of evolvable software.
英語
-
in the digital age, frailty’s name is software.
-
デジタル時代において、弱者の名はソフトウェアである
- すぐ壊れる(不具合が入る)ということ
- “Frailty, thy name is woman” (Hamlet)のもじり
-
-
Brevity is the soul of wit.
- 言は簡潔を尊ぶ(Hamlet)