きれいになったのに、変わった
リファクタリングとは、動きを変えずに、コードの形を整えることです。アシスタントは、重なった部分をまとめるのが得意です。
ところが、読みやすくなった版で、合計が1円ずれることがあります。見た目では分かりません。動きが同じだと、どう示すかを学びます。
このレッスンのゴール
メモ
リファクタリングを頼み、動きが変わっていないことを、前後の出力で示せる。
キーワード
| ことば | いみ |
|---|---|
| リファクタリング | 動きを変えずに、コードの形を整えること |
| 動きの固定 | 整える前の出力を、先に記録しておくこと |
| 小さな一歩 | 1回に1か所だけ変える、整理の単位 |
| 前後の比較 | 同じ入力で、前と後の出力を見比べること |
| 同じ動き | どの入力でも、前と後の出力が同じこと |
学生が入力するセルは、このプレビューでは空白です。
まず、今の動きを固定する
整える前に、今のコードが何を返すかを記録します。決まった入力を何件か選び、その出力を書き留めます。これが動きの固定です。
整えたあと、同じ入力を動かして、同じ出力が出れば、動きは変わっていません。固定がなければ、変わったことにも気づけません。
ありうる頼み方
こんな頼み方があります。「total の動きは変えないでください。税の計算を、別の関数に分けてください。1回に1か所だけ変えて、変えるたびに、前と同じ出力かを見せてください」
アシスタントの返事は、毎回違います。返ってきた変更が、頼んだ1か所だけかを、まず確かめます。
前後を同じ入力で比べる
次の Run は、フォルダー demo-refactor/ に4つのファイルを書きます。前のコード、整理した版、順番を変えた版、そして比べるスクリプトです。
比べるスクリプトは、6件の入力を前の版と別の版に動かし、出力が違った件を表示します。版はどれも手で書いたもので、アシスタントの答えではありません。
前のコードを書く
docker exec -w /home/node/project agent sh -c "mkdir -p demo-refactor && printf '%s\n' '// 売店の合計。会員は5パーセント引き、消費税は10パーセント。' 'function total(prices, member) {' ' let sum = 0;' ' for (let i = 0; i < prices.length; i++) sum = sum + prices[i];' ' if (member === true) {' ' const off = Math.floor(sum * 5 / 100);' ' const net = sum - off;' ' return net + Math.floor(net * 10 / 100);' ' } else {' ' return sum + Math.floor(sum * 10 / 100);' ' }' '}' 'module.exports = { total };' > demo-refactor/before.js"ここでは表示のみで、実行されません。無料アカウントを作成すると、実行ボタンで自分のラボのターミナルに入力できます。
整理した版を書く
docker exec -w /home/node/project agent sh -c "mkdir -p demo-refactor && printf '%s\n' '// 同じ合計を、3つの部品に分けた版。' 'function addUp(prices) {' ' let sum = 0;' ' for (let i = 0; i < prices.length; i++) sum = sum + prices[i];' ' return sum;' '}' 'function memberPrice(sum) { return sum - Math.floor(sum * 5 / 100); }' 'function withTax(sum) { return sum + Math.floor(sum * 10 / 100); }' 'function total(prices, member) {' ' let sum = addUp(prices);' ' if (member === true) sum = memberPrice(sum);' ' return withTax(sum);' '}' 'module.exports = { total, addUp, memberPrice, withTax };' > demo-refactor/after.js"ここでは表示のみで、実行されません。無料アカウントを作成すると、実行ボタンで自分のラボのターミナルに入力できます。
順番を変えた版を書く
docker exec -w /home/node/project agent sh -c "mkdir -p demo-refactor && printf '%s\n' '// 同じ部品で、順番だけを変えた版。割引の前に税をかける。' 'const parts = require(\"./after.js\");' 'function total(prices, member) {' ' let sum = parts.addUp(prices);' ' sum = parts.withTax(sum);' ' if (member === true) sum = parts.memberPrice(sum);' ' return sum;' '}' 'module.exports = { total };' > demo-refactor/trap.js"ここでは表示のみで、実行されません。無料アカウントを作成すると、実行ボタンで自分のラボのターミナルに入力できます。
比べるスクリプトを書く
docker exec -w /home/node/project agent sh -c "mkdir -p demo-refactor && printf '%s\n' 'const before = require(\"./before.js\").total;' 'const other = require(process.argv[2]).total;' 'const cases = [[[300], false], [[980], false], [[980], true], [[120, 250, 80], true], [[], true], [[1000, 50], true]];' 'let same = 0;' 'for (let i = 0; i < cases.length; i++) {' ' const a = before(cases[i][0], cases[i][1]);' ' const b = other(cases[i][0], cases[i][1]);' ' if (a === b) same = same + 1;' ' else console.log(\"違い: \" + (i + 1) + \"件目 前 \" + a + \" 後 \" + b);' '}' 'console.log(process.argv[2] + \": \" + cases.length + \" 件中 \" + same + \" 件が同じ\");' > demo-refactor/compare.js"ここでは表示のみで、実行されません。無料アカウントを作成すると、実行ボタンで自分のラボのターミナルに入力できます。
2つの版を前と比べる
docker exec -w /home/node/project/demo-refactor agent sh -c "node compare.js ./after.js; node compare.js ./trap.js"ここでは表示のみで、実行されません。無料アカウントを作成すると、実行ボタンで自分のラボのターミナルに入力できます。
よく似た版が、1円ずれる
順番を変えた版は、税を先にかけ、あとで割引をします。式は似ていますが、円未満を切り捨てる場所が変わるので、会員のとき、答えが1円ずれます。
アシスタントの整理は、読みやすさを狙います。そのため、順番のような小さな変更が入りえます。見ただけでは分かりません。比べて確かめます。
よくある間違い
注意
コードが読みやすくなれば、動きも同じだと思う人が多いです。式が似ていても、切り捨ての位置が違えば、答えは変わります。動きが同じかどうかは、前後の出力だけが言えます。
例: 2つの版の比較
| 版 | 同じだった件数 | 違った件と出力 (前、後) |
|---|---|---|
| after.js | 6 件中 6 件 | なし |
| trap.js | 6 件中 3 件 | 3件目 (1024、1025)、4件目 (470、471)、6件目 (1097、1098) |
学生が入力するセルは、このプレビューでは空白です。
さらに: 別の変更で試す
今は、この考えを別の変更で試すのに、よい時です。ラボのバグには触れません。代わりに、shop.js の写しで、変数の名前を1つ変える整理を頼みます。
頼む前に、プログラムの出力を記録します。変更のあとで、同じ入力を動かし、出力を見比べます。変わった所があれば、その入力を書き出します。