CHAPTER 4 ・ コードを整理して育てる / Lesson 17
読みやすいコードに書き直す
名前・定数・関数の分け方。動くけれど読みにくいコードを、動きを変えずに整える練習をします。
このレッスンが終わったら
「動きを変えずに、名前・定数・関数の分け方を直すのがリファクタリング。直す前後で、結果が同じか必ず確かめる」と言える。
こんな場面、ありませんか?
数か月前に自分で書いた(あるいは、前任者が書いた)スクリプトを開いて、何をしているのか分からないことがあります。動くけれど、読めない。そんなコードの例です。9月と8月の売上合計と、それぞれの税込の金額を出します。
この欄では、サンプルファイルが使えます。実行のたびに、ファイルは最初の状態に戻ります。
▶ 実行を押すと、ここに結果が出ます
a b c e は、何の値でしょう。110 は、何の数字でしょう。9月用と8月用の for は、月の部分以外がまったく同じコピーです。税率が変わったとき、110 を探して直し、直し忘れる危険があります。
読みやすく書き直してみる
同じ仕事を、名前・定数・関数を整えて書き直します。結果が同じになることに注目してください。
この欄では、サンプルファイルが使えます。実行のたびに、ファイルは最初の状態に戻ります。
▶ 実行を押すと、ここに結果が出ます
出力は、さきほどと同じ 1036500 3030000 と 1140150 3333000 です。コードは長くなりましたが、読む人が考えることは、ぐっと減りました。このように、動きを変えずに中身を整えることをリファクタリングと呼びます。
仕組みを読む
a, b, c, d, e, x何の値か分からない名前 → sep, aug, rows, r など、中身が分かる名前に110何の数字か分からない → TAX_PERCENT という名前を付けた定数に同じ for が2回月だけが違うコピー → month_total(rows, month) という1つの関数にopen ... close閉じ忘れの危険 → with open(...) に(基礎編で学びました)110 のように、意味の分からない数字が直接書かれたものをマジックナンバーと呼びます。名前を付けた定数(TAX_PERCENT)にすると、意味が読め、直す場所も1か所になります。定数は大文字で書くのが慣習です。
直す前
c = a * 110 // 100
直した後
sales_with_tax = with_tax(sales_sep)
コメントは、読めば分かることではなく、なぜそうしたのかを書きます。
読めば分かるコメント
# 108 を掛ける amount = amount * 108 // 100
「なぜ」を書いたコメント
# P05(ミネラルウォーター)は軽減税率の8%。10%とは別に計算する amount = amount * 108 // 100
Python には、書き方の共通の決まりもあります。PEP 8(ペップ・エイト)と呼ばれ、要点は次の通りです。
| 対象 | 決まり | 例 |
|---|---|---|
| 変数・関数の名前 | 小文字と _ | month_total |
| 定数 | 大文字と _ | TAX_PERCENT |
| クラスの名前 | 単語の頭を大文字 | SalesLine |
= , のまわり | スペースを入れる | a = f(x, y) |
変えて、試す
① 直す前後で、結果が同じか確かめる。 リファクタリングで、いちばん大切な決まりです。直す前のコードを関数に包んで残し、同じ入力で比べます。assert は「この条件が成り立つはず」と宣言する書き方で、成り立たないとエラーで止まります。
この欄では、サンプルファイルが使えます。実行のたびに、ファイルは最初の状態に戻ります。
▶ 実行を押すと、ここに結果が出ます
assert が通れば、何も起きずに次の行へ進み、同じ結果です と表示されます。
② 深い if を、早めの return で平らにする。 「条件に合わないものは、先に返してしまう」と、入れ子が浅くなります。これを早期 returnと呼びます。ここでも、空欄・0・マイナスといった端のケースを入れて比べます。普通のデータだけで比べると、書き直しで変わった動きに気づけません。
▶ 実行を押すと、ここに結果が出ます
③ 比べないと、気づけない違い。 「ついでに」* 110 // 100 を round(amount * 1.1) に書き直したら、どうなるでしょう。
▶ 実行を押すと、ここに結果が出ます
10000 は同じでも、1005 で AssertionError: 1005円で違う と止まります。切り捨て(//)が、round の丸めに変わってしまったためです。見た目が整っていても、動きが変わっていないとは限りません。比べて初めて分かります(お金の丸めは、Lesson 04 の通り、会社のルールが基準です)。
④ 定数のよいところ。 定数を1か所直すと、使っている全部が変わります。ためしに、軽減税率の8%に変えてみましょう。
▶ 実行を押すと、ここに結果が出ます
自分で書く
お題① 次の old は、108 と 110 の意味が読めません。定数 STANDARD_PERCENT(10)と REDUCED_PERCENT(8)を使って、new を書き直してください。True と表示されれば、直す前と同じ結果です。
▶ 実行を押すと、ここに結果が出ます
ヒントを見る
STANDARD_PERCENT = 10 と REDUCED_PERCENT = 8 を作ります。new の中では、reduced が True なら REDUCED_PERCENT、そうでなければ STANDARD_PERCENT を、percent という名前に入れて使うと読みやすくなります。
答えの例を見る
STANDARD_PERCENT = 10
REDUCED_PERCENT = 8
def old(price, reduced):
return price * 108 // 100 if reduced else price * 110 // 100
def new(price, reduced):
if reduced:
percent = REDUCED_PERCENT
else:
percent = STANDARD_PERCENT
return price * (100 + percent) // 100
print(all(old(p, r) == new(p, r) for p in [1500, 999, 35000] for r in [True, False]))お題② 次の check_before を、早期 return で書き直して check_after にしてください。仮のルールです。金額が0以下なら「金額が不正」、5,000円以上で領収書が「なし」なら「領収書が必要」、それ以外は「OK」です。
▶ 実行を押すと、ここに結果が出ます
ヒントを見る
先に「金額が不正」の場合を return し、次に「5000以上で領収書なし」の場合を return し、最後に "OK" を return します。
答えの例を見る
def check_before(amount, receipt):
if amount > 0:
if amount >= 5000 and receipt == "なし":
return "領収書が必要"
else:
return "OK"
else:
return "金額が不正"
def check_after(amount, receipt):
if amount <= 0:
return "金額が不正"
if amount >= 5000 and receipt == "なし":
return "領収書が必要"
return "OK"
cases = [(-100, "あり"), (0, "なし"), (1240, "なし"), (5000, "なし"), (15400, "あり")]
print(all(check_before(a, r) == check_after(a, r) for a, r in cases))仕事ならどう使う?
理解チェックコード中の 110 や 0.08 のような、意味の分からない数字を直接書いてしまうことを、何と呼びますか?
理解チェックリファクタリングで、いちばん大切な確認はどれでしょう?