基礎から学ぶPython入門 90日コース | 実践Python - Day 29:リファクタリング

90日で身につけるPython
スポンサーリンク
スポンサーリンク

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("=== アプリ終了 ===")
Python

main() の中に、

  • 設定読み込み
  • ログ設定
  • ユーザー登録
  • 商品登録

が全部詰め込まれていて、何をしている関数なのか分かりにくくなっています。

関数に分割して「役割」をはっきりさせる

これを、役割ごとに関数に分けてみます。

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)
Python

logging_setup.py

python

import logging

def setup_logging():
    """ログ設定を行う関数です。"""
    logging.basicConfig(
        level=logging.INFO,
        format="%(asctime)s [%(levelname)s] %(message)s"
    )
Python

user.py

def register_user():
    """ユーザー登録処理を行う関数です。"""
    print("=== ユーザー登録 ===")
    username = input("ユーザー名を入力してください: ")
    email = input("メールアドレスを入力してください: ")
    print(f"登録完了: {username} ({email})")
Python

product.py

def register_product():
    """商品登録処理を行う関数です。"""
    print("=== 商品登録 ===")
    product_name = input("商品名を入力してください: ")
    price = int(input("価格を入力してください: "))
    print(f"登録完了: {product_name} ({price}円)")
Python

app.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 が何をする関数なのか分からない
  • abc が何を表しているのか分からない

という問題があります。

良い命名の例

同じ処理でも、名前を変えるだけで可読性は大きく変わります。

def print_sum(x: int, y: int):
    """2つの整数の合計を計算して表示する関数です。"""
    total = x + y
    print(total)
Python

ここでのポイントは、

  • print_sum という名前で、「合計を表示する関数」であることが分かること
  • xytotal という名前で、「何を表しているか」が伝わること
  • docstring("""...""")で、関数の役割を簡潔に説明していること

です。

命名の基本的な考え方

命名の基本として、次のようなことを意識するとよいです。

  • 動詞+目的語
    • load_config / setup_logging / register_user / send_email など
  • 意味のある名詞
    • user_name / email_address / config / logger など
  • 略語を減らす
    • cfg より config
    • usr より 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編で、コードの規模が大きくなっても、 自分やチームのために「育てやすいコード」を保ち続けるための大きな土台になっていきます。

タイトルとURLをコピーしました