「この関数、長すぎるな…」「どこから読めばいいの?」そんな気持ちになったことはありませんか?
Pythonで読みやすく保守しやすいコードを書くうえで、関数を適切に分割することはとても大切です。関数の分割は、リファクタリングの基本であり、可読性・再利用性・テストのしやすさを一気に高めます。
今回は、IT初心者の方にもわかりやすく、具体例とともに「関数の分割」の考え方と実践方法を丁寧に解説します。
まずは、なぜ関数を分割すると読みやすくなるのかを、実際のコードを通して体感しましょう。
最初に、やることが詰め込まれた「長い関数」を見てみます。
読みながら「何をしている関数なのか」「どの部分が難しいのか」を感じ取ってみてください。
def checkout(cart, tax_rate, coupons):
# 入力のバリデーション
if not isinstance(cart, list) or len(cart) == 0:
raise ValueError("cartは空でないリストである必要があります")
for item in cart:
if "price" not in item or "qty" not in item:
raise ValueError("各商品にpriceとqtyが必要です")
if item["price"] < 0 or item["qty"] <= 0:
raise ValueError("priceは0以上、qtyは1以上である必要があります")
# 小計の計算
subtotal = 0
for item in cart:
subtotal += item["price"] * item["qty"]
# クーポンの適用
discount_total = 0
for c in coupons:
if c["type"] == "rate":
discount_total += subtotal * c["value"]
elif c["type"] == "amount":
discount_total += c["value"]
# 税込み価格
total_after_discount = max(0, subtotal - discount_total)
total_with_tax = total_after_discount * (1 + tax_rate)
# 結果のフォーマット(簡易レシート)
receipt = f"小計: {subtotal:.2f}\n割引: {discount_total:.2f}\n税: {(total_with_tax - total_after_discount):.2f}\n合計: {total_with_tax:.2f}"
return total_with_tax, receipt
一見すると正しく動きそうですが、この関数は「入力チェック」「計算」「値引き」「税計算」「表示用のフォーマット」という複数の責務が詰め込まれており、読むのも直すのも大変です。
どこか一部を変えたいときに他の処理へ影響してしまうリスクも高く、テストも難しくなります。
では、これを小さな関数に分割するとどう変わるでしょうか。
ここからは、先ほどの長い関数を役割ごとに分割していきます。読みやすさ・変更のしやすさ・テストのしやすさがどう向上するかに注目してください。
def validate_cart(cart):
if not isinstance(cart, list) or len(cart) == 0:
raise ValueError("cartは空でないリストである必要があります")
for item in cart:
if "price" not in item or "qty" not in item:
raise ValueError("各商品にpriceとqtyが必要です")
if item["price"] < 0 or item["qty"] <= 0:
raise ValueError("priceは0以上、qtyは1以上である必要があります")
def calculate_subtotal(cart):
subtotal = 0
for item in cart:
subtotal += item["price"] * item["qty"]
return subtotal
def apply_coupons(subtotal, coupons):
discount_total = 0
for c in coupons:
if c["type"] == "rate":
discount_total += subtotal * c["value"]
elif c["type"] == "amount":
discount_total += c["value"]
return max(0, subtotal - discount_total)
def apply_tax(amount, tax_rate):
return amount * (1 + tax_rate)
def format_receipt(subtotal, discounted, taxed):
tax_amount = taxed - discounted
return (
f"小計: {subtotal:.2f}\n"
f"割引後: {discounted:.2f}\n"
f"税: {tax_amount:.2f}\n"
f"合計: {taxed:.2f}"
)
def checkout(cart, tax_rate, coupons):
validate_cart(cart)
subtotal = calculate_subtotal(cart)
discounted = apply_coupons(subtotal, coupons)
taxed = apply_tax(discounted, tax_rate)
receipt = format_receipt(subtotal, discounted, taxed)
return taxed, receipt
このバージョンでは、各関数がひとつのことだけを担当しています。
validate_cartは入力チェックだけ、calculate_subtotalは小計計算だけ、といった具合です。処理の流れはcheckoutで組み立てられ、読む人は上から順に「検証→小計→割引→税→表示」という筋道を追うだけで全体を理解できます。個々の関数は短くなったので、テストも簡単ですし、将来の変更(たとえば税計算の仕様変更やレシートの表示変更)も局所的に済みます。
ここでは、関数を分割するタイミングや目安を具体的に整理します。
読んでいて当てはまるかもと思ったら、分割を検討してみましょう。
こうしたサインが出たら、単一責任の原則を思い出して、一歩引いて役割ごとに切り分けてみると効果的です。
💡 単一責任の原則とは? 単一責任の原則(Single Responsibility Principle, SRP)は、ソフトウェア設計の基本的な考え方のひとつで、「1つのクラス・関数・モジュールは1つの役割だけ持つ」という原則です。
分割するときに迷いやすいのが、引数と戻り値の設計です。
迷ったら関数は入力を受け取って出力を返すというシンプルな流れを大事にしましょう。副作用(printやファイル書き込みなど)は、最小限にして境界(表示の直前など)に寄せると、テストしやすくなります。
次の例では、計算と表示を分けることで、計算部分だけを安心してテストできる形にしています。出力のフォーマット変更も、表示用関数だけを直せば済みます。
def compute_total_with_tax(amount, tax_rate):
return amount * (1 + tax_rate)
def print_total(amount, tax_rate):
total = compute_total_with_tax(amount, tax_rate)
print(f"税込合計: {total:.2f}")
このように「計算する関数」と「表示する関数」を分けると、責務がはっきりし、どこを直せばよいかがすぐに分かります。
関数の分割は、Pythonでリーダブルかつ保守しやすいコードを書くための強力なテクニックです。
長い関数を小さく切り、ひとつの関数にひとつの責務を持たせるだけで、読みやすさ・再利用性・テスト容易性が大きく向上します。今書いている関数に「そして…」が増えてきたら、それは分割のタイミングの合図かもしれません。次にコードを書くとき、あるいは既存コードを読み直すときに、ぜひ「関数の分割」を意識してみてください。あなたのPythonコードは、もっと読みやすく、もっと強くなります。