Day 29:リファクタリングで「読みやすく育てるコード」を意識する
Day 29では、リファクタリング をテーマに、
- 重複コード削除
- 関数分割
- モジュール分割
- 命名
- 可読性
といったキーワードを軸に、「動くコード」を「育てるコード」に変えていく感覚を身につけていきます。 今日はアルゴリズムの話ではなく、「コードの整理整頓」の話です。
リファクタリングとは何か
「動きを変えずに、コードの中身を良くする」作業
リファクタリングとは、
- プログラムの「外から見た動き」は変えずに
- コードの「中身の構造」を改善する
作業のことです。
たとえば、
- 同じ処理があちこちにコピペされている
- 1つの関数が長すぎて読みにくい
- 変数名・関数名が何をしているのか分かりにくい
といった状態を、
- 重複をなくす
- 関数に分割する
- モジュールに分ける
- 名前を分かりやすくする
ことで、読みやすく・変更しやすくしていきます。
重要なのは、
「動作は変えない」=「テストが通るまま、コードだけをきれいにする」
というスタンスです。
重複コード削除:コピペを「共通関数」にまとめる
よくある「よくない例」
まずは、重複コードの典型例を見てみます。
# ユーザー登録処理
print("=== ユーザー登録 ===")
username = input("ユーザー名を入力してください: ")
email = input("メールアドレスを入力してください: ")
print(f"登録完了: {username} ({email})")
# 商品登録処理
print("=== 商品登録 ===")
product_name = input("商品名を入力してください: ")
price = int(input("価格を入力してください: "))
print(f"登録完了: {product_name} ({price}円)")
Pythonここでは、
- 「タイトルを表示する」
- 「入力を受け取る」
- 「登録完了メッセージを出す」
というパターンが、ほぼ同じ形で繰り返されています。
共通処理を関数にまとめる
このような重複は、関数にまとめることでスッキリさせられます。
def print_section_title(title: str):
"""セクションタイトルを表示する共通関数です。"""
print(f"=== {title} ===")
def register_user():
"""ユーザー登録処理を行う関数です。"""
print_section_title("ユーザー登録")
username = input("ユーザー名を入力してください: ")
email = input("メールアドレスを入力してください: ")
print(f"登録完了: {username} ({email})")
def register_product():
"""商品登録処理を行う関数です。"""
print_section_title("商品登録")
product_name = input("商品名を入力してください: ")
price = int(input("価格を入力してください: "))
print(f"登録完了: {product_name} ({price}円)")
Pythonここでのポイントは、
- 「タイトル表示」という共通処理を
print_section_titleに切り出していること - 重複コードをなくすことで、変更が楽になること
- たとえば「タイトルの表示形式を変えたい」とき、
print_section_titleだけ直せばよい
- たとえば「タイトルの表示形式を変えたい」とき、
です。
関数分割:長すぎる処理を「意味ごと」に分ける
長すぎる関数の例
次は、「1つの関数が長すぎる」例を見てみます。
def main():
print("=== アプリ開始 ===")
# 設定読み込み
import json
with open("config.json", "r", encoding="utf-8") as f:
config = json.load(f)
# ログ設定
import logging
logging.basicConfig(
level=logging.INFO,
format="%(asctime)s [%(levelname)s] %(message)s"
)
# ユーザー登録
print("=== ユーザー登録 ===")
username = input("ユーザー名を入力してください: ")
email = input("メールアドレスを入力してください: ")
print(f"登録完了: {username} ({email})")
# 商品登録
print("=== 商品登録 ===")
product_name = input("商品名を入力してください: ")
price = int(input("価格を入力してください: "))
print(f"登録完了: {product_name} ({price}円)")
print("=== アプリ終了 ===")
Pythonmain() の中に、
- 設定読み込み
- ログ設定
- ユーザー登録
- 商品登録
が全部詰め込まれていて、何をしている関数なのか分かりにくくなっています。
関数に分割して「役割」をはっきりさせる
これを、役割ごとに関数に分けてみます。
import json
import logging
def load_config(path: str = "config.json") -> dict:
"""設定ファイルを読み込む関数です。"""
with open(path, "r", encoding="utf-8") as f:
return json.load(f)
def setup_logging():
"""ログ設定を行う関数です。"""
logging.basicConfig(
level=logging.INFO,
format="%(asctime)s [%(levelname)s] %(message)s"
)
def register_user():
"""ユーザー登録処理を行う関数です。"""
print("=== ユーザー登録 ===")
username = input("ユーザー名を入力してください: ")
email = input("メールアドレスを入力してください: ")
print(f"登録完了: {username} ({email})")
def register_product():
"""商品登録処理を行う関数です。"""
print("=== 商品登録 ===")
product_name = input("商品名を入力してください: ")
price = int(input("価格を入力してください: "))
print(f"登録完了: {product_name} ({price}円)")
def main():
print("=== アプリ開始 ===")
config = load_config()
setup_logging()
register_user()
register_product()
print("=== アプリ終了 ===")
if __name__ == "__main__":
main()
Pythonここでのポイントは、
main()が「アプリの流れ」を示す、読みやすい関数になっていること- 詳細な処理は、それぞれの関数に分かれていること
- 「設定読み込み」「ログ設定」「ユーザー登録」「商品登録」が、名前だけで何をするか分かること
です。
モジュール分割:ファイルを「役割ごと」に分ける
1ファイルに全部詰め込むとどうなるか
プロジェクトが大きくなると、1つの .py ファイルにすべてのコードを詰め込むのは現実的ではなくなります。
たとえば、次のような状態です。
app.py
- 設定読み込み
- ログ設定
- ユーザー登録
- 商品登録
- DB接続
- API呼び出し
- メイン処理
これでは、
- ファイルが長くなりすぎて、スクロールが大変
- どこに何が書いてあるか分かりにくい
- 別の人が読むときの負担が大きい
という問題が出てきます。
役割ごとにモジュールを分ける
そこで、モジュール分割 を行います。 たとえば、次のような構成にします。
config.py # 設定読み込み関連
logging_setup.py # ログ設定関連
user.py # ユーザー関連処理
product.py # 商品関連処理
app.py # メイン処理
それぞれのファイルのイメージは次の通りです。
config.py:
import json
def load_config(path: str = "config.json") -> dict:
"""設定ファイルを読み込む関数です。"""
with open(path, "r", encoding="utf-8") as f:
return json.load(f)
Pythonlogging_setup.py:
python
import logging
def setup_logging():
"""ログ設定を行う関数です。"""
logging.basicConfig(
level=logging.INFO,
format="%(asctime)s [%(levelname)s] %(message)s"
)
Pythonuser.py:
def register_user():
"""ユーザー登録処理を行う関数です。"""
print("=== ユーザー登録 ===")
username = input("ユーザー名を入力してください: ")
email = input("メールアドレスを入力してください: ")
print(f"登録完了: {username} ({email})")
Pythonproduct.py:
def register_product():
"""商品登録処理を行う関数です。"""
print("=== 商品登録 ===")
product_name = input("商品名を入力してください: ")
price = int(input("価格を入力してください: "))
print(f"登録完了: {product_name} ({price}円)")
Pythonapp.py:
from config import load_config
from logging_setup import setup_logging
from user import register_user
from product import register_product
def main():
print("=== アプリ開始 ===")
config = load_config()
setup_logging()
register_user()
register_product()
print("=== アプリ終了 ===")
if __name__ == "__main__":
main()
Pythonここでのポイントは、
- ファイルごとに「役割」がはっきりしていること
app.pyを見れば、「アプリ全体の流れ」がすぐに分かること- 他の人が読むときも、「必要なモジュールだけを開けばよい」状態になっていること
です。
命名と可読性:名前で「何をしているか」を伝える
悪い命名の例
命名は、リファクタリングの中でも非常に重要な要素です。 たとえば、次のようなコードを見てみます。
def f(a, b):
c = a + b
print(c)
Pythonこのコードは「動き」は簡単ですが、
fが何をする関数なのか分からないa・b・cが何を表しているのか分からない
という問題があります。
良い命名の例
同じ処理でも、名前を変えるだけで可読性は大きく変わります。
def print_sum(x: int, y: int):
"""2つの整数の合計を計算して表示する関数です。"""
total = x + y
print(total)
Pythonここでのポイントは、
print_sumという名前で、「合計を表示する関数」であることが分かることx・y・totalという名前で、「何を表しているか」が伝わること- docstring(
"""...""")で、関数の役割を簡潔に説明していること
です。
命名の基本的な考え方
命名の基本として、次のようなことを意識するとよいです。
- 動詞+目的語
load_config/setup_logging/register_user/send_emailなど
- 意味のある名詞
user_name/email_address/config/loggerなど
- 略語を減らす
cfgよりconfigusrよりuser
「少し長くても、意味が分かる名前」のほうが、 将来の自分や他の人にとって優しいコードになります。
Day 29ミニテンプレート:リファクタリング練習用コード
最後に、「リファクタリング前」と「リファクタリング後」を比較できる練習用コードを示します。
リファクタリング前
# before_refactor.py
import json
import logging
def main():
print("=== アプリ開始 ===")
# 設定読み込み
with open("config.json", "r", encoding="utf-8") as f:
config = json.load(f)
# ログ設定
logging.basicConfig(
level=logging.INFO,
format="%(asctime)s [%(levelname)s] %(message)s"
)
# ユーザー登録
print("=== ユーザー登録 ===")
username = input("ユーザー名を入力してください: ")
email = input("メールアドレスを入力してください: ")
print(f"登録完了: {username} ({email})")
# 商品登録
print("=== 商品登録 ===")
product_name = input("商品名を入力してください: ")
price = int(input("価格を入力してください: "))
print(f"登録完了: {product_name} ({price}円)")
print("=== アプリ終了 ===")
if __name__ == "__main__":
main()
Pythonリファクタリング後
# after_refactor.py
import json
import logging
def load_config(path: str = "config.json") -> dict:
"""設定ファイルを読み込む関数です。"""
with open(path, "r", encoding="utf-8") as f:
return json.load(f)
def setup_logging():
"""ログ設定を行う関数です。"""
logging.basicConfig(
level=logging.INFO,
format="%(asctime)s [%(levelname)s] %(message)s"
)
def print_section_title(title: str):
"""セクションタイトルを表示する共通関数です。"""
print(f"=== {title} ===")
def register_user():
"""ユーザー登録処理を行う関数です。"""
print_section_title("ユーザー登録")
username = input("ユーザー名を入力してください: ")
email = input("メールアドレスを入力してください: ")
print(f"登録完了: {username} ({email})")
def register_product():
"""商品登録処理を行う関数です。"""
print_section_title("商品登録")
product_name = input("商品名を入力してください: ")
price = int(input("価格を入力してください: "))
print(f"登録完了: {product_name} ({price}円)")
def main():
print_section_title("アプリ開始")
config = load_config()
setup_logging()
register_user()
register_product()
print_section_title("アプリ終了")
if __name__ == "__main__":
main()
Pythonこの2つを見比べることで、
- 重複コード削除
- 関数分割
- 命名と可読性の改善
といったリファクタリングの効果を実感しやすくなります。
Day 29のまとめ
Day 29では、リファクタリング入門として、
- リファクタリング=「動きを変えずに、コードの中身を良くする」作業という理解
- 重複コードを共通関数にまとめて、変更に強くする考え方
- 長すぎる関数を役割ごとに分割して、読みやすくする方法
- モジュール分割で、ファイルごとに責務を分ける設計の感覚
- 命名と可読性を意識して、「名前だけで何をしているか分かる」コードにする工夫
をステップバイステップで体験していただきました。
ここまで来ると、「とりあえず動けばOK」から一歩進んで、 「あとから読み返しても分かりやすいコード」を意識して書く感覚が少し育っているはずです。 この感覚は、今後の実践Python編で、コードの規模が大きくなっても、 自分やチームのために「育てやすいコード」を保ち続けるための大きな土台になっていきます。
