CHAPTER 4 ・ コードを整理して育てる / Lesson 17

読みやすいコードに書き直す

名前・定数・関数の分け方。動くけれど読みにくいコードを、動きを変えずに整える練習をします。

目安 25分 ・ インストール不要

このレッスンが終わったら

「動きを変えずに、名前・定数・関数の分け方を直すのがリファクタリング。直す前後で、結果が同じか必ず確かめる」と言える。

STEP 1

こんな場面、ありませんか?

数か月前に自分で書いた(あるいは、前任者が書いた)スクリプトを開いて、何をしているのか分からないことがあります。動くけれど、読めない。そんなコードの例です。9月と8月の売上合計と、それぞれの税込の金額を出します。

動くけれど、読みにくいコードサンプルファイル付き・書き換えOK
Ctrl / ⌘ + Enter でも実行

この欄では、サンプルファイルが使えます。実行のたびに、ファイルは最初の状態に戻ります。

結果
▶ 実行を押すと、ここに結果が出ます

a b c e は、何の値でしょう。110 は、何の数字でしょう。9月用と8月用の for は、月の部分以外がまったく同じコピーです。税率が変わったとき、110 を探して直し、直し忘れる危険があります。

STEP 2

読みやすく書き直してみる

同じ仕事を、名前・定数・関数を整えて書き直します。結果が同じになることに注目してください。

書き直した版サンプルファイル付き・書き換えOK
Ctrl / ⌘ + Enter でも実行

この欄では、サンプルファイルが使えます。実行のたびに、ファイルは最初の状態に戻ります。

結果
▶ 実行を押すと、ここに結果が出ます

出力は、さきほどと同じ 1036500 3030000 と 1140150 3333000 です。コードは長くなりましたが、読む人が考えることは、ぐっと減りました。このように、動きを変えずに中身を整えることをリファクタリングと呼びます。

STEP 3

仕組みを読む

直した点を、1つずつ確かめる
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)
STEP 4

変えて、試す

① 直す前後で、結果が同じか確かめる。 リファクタリングで、いちばん大切な決まりです。直す前のコードを関数に包んで残し、同じ入力で比べます。assert は「この条件が成り立つはず」と宣言する書き方で、成り立たないとエラーで止まります。

直す前と直した後を、assert で比べるサンプルファイル付き・書き換えOK
Ctrl / ⌘ + Enter でも実行

この欄では、サンプルファイルが使えます。実行のたびに、ファイルは最初の状態に戻ります。

結果
▶ 実行を押すと、ここに結果が出ます

assert が通れば、何も起きずに次の行へ進み、同じ結果です と表示されます。

② 深い if を、早めの return で平らにする。 「条件に合わないものは、先に返してしまう」と、入れ子が浅くなります。これを早期 returnと呼びます。ここでも、空欄・0・マイナスといった端のケースを入れて比べます。普通のデータだけで比べると、書き直しで変わった動きに気づけません。

入れ子の if を、早期 return に書き直すコードは書き換えられます
Ctrl / ⌘ + Enter でも実行
結果
▶ 実行を押すと、ここに結果が出ます

③ 比べないと、気づけない違い。 「ついでに」* 110 // 100 を round(amount * 1.1) に書き直したら、どうなるでしょう。

わざと:丸めかたが変わってしまった書き直しコードは書き換えられます
Ctrl / ⌘ + Enter でも実行
結果
▶ 実行を押すと、ここに結果が出ます

10000 は同じでも、1005 で AssertionError: 1005円で違う と止まります。切り捨て(//)が、round の丸めに変わってしまったためです。見た目が整っていても、動きが変わっていないとは限りません。比べて初めて分かります(お金の丸めは、Lesson 04 の通り、会社のルールが基準です)。

④ 定数のよいところ。 定数を1か所直すと、使っている全部が変わります。ためしに、軽減税率の8%に変えてみましょう。

定数を1か所だけ変えるコードは書き換えられます
Ctrl / ⌘ + Enter でも実行
結果
▶ 実行を押すと、ここに結果が出ます
STEP 5

自分で書く

お題① 次の old は、108 と 110 の意味が読めません。定数 STANDARD_PERCENT(10)と REDUCED_PERCENT(8)を使って、new を書き直してください。True と表示されれば、直す前と同じ結果です。

お題①:マジックナンバーを定数にするコードは書き換えられます
Ctrl / ⌘ + Enter でも実行
結果
▶ 実行を押すと、ここに結果が出ます
ヒントを見る

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 に書き直すコードは書き換えられます
Ctrl / ⌘ + Enter でも実行
結果
▶ 実行を押すと、ここに結果が出ます
ヒントを見る

先に「金額が不正」の場合を 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))
STEP 6

仕事ならどう使う?

理解チェックコード中の 110 や 0.08 のような、意味の分からない数字を直接書いてしまうことを、何と呼びますか?

理解チェックリファクタリングで、いちばん大切な確認はどれでしょう?