【Python】関数には辞書やオブジェクトを渡さない:疎結合で再利用性の高い設計

関数を設計する際、引数としてどのようなデータを受け取るべきか、迷うことはないでしょうか?例えば、ユーザーの情報が詰まった辞書やオブジェクトを丸ごと渡すべきか、それとも関数が必要とする特定のデータ(名前や年齢など)だけを渡すべきか。

結論から言えば、多くの場合において、関数が必要とする最小限の、そして最もシンプルなデータ型(int, strなど)を引数として受け取る方が、はるかに優れた設計になります。

このアプローチは、コンポーネント間の依存関係を減らす「疎結合(Loose Coupling)」という考え方に基づいています。今回は、なぜこの設計が推奨されるのか、その具体的なメリットをコードと共に見ていきましょう。

目次

問題点:データ構造に依存した「密結合」な関数

あるユーザーの生年から年齢を計算する関数を考えてみます。ユーザーデータが辞書として与えられると想定し、次のような関数を作成しました。

import datetime

# 悪い例:ユーザー情報の辞書全体を受け取ってしまっている
def calculate_age_from_dict(user_data: dict) -> int:
    """ユーザー辞書から生年を取得し、年齢を計算する。"""
    
    # この関数は'birth_year'というキーの存在を「知っている」必要がある
    birth_year = user_data["birth_year"]
    current_year = datetime.date.today().year
    return current_year - birth_year

# 実行例
user_a = {"id": 1, "name": "Taro", "birth_year": 1995}
age = calculate_age_from_dict(user_a)
print(f"年齢: {age}")

この calculate_age_from_dict 関数は正しく動作しますが、いくつかの重大な問題を抱えています。

  1. 暗黙の依存: この関数は、引数 user_data が辞書であり、かつ "birth_year" というキーを持つことを暗黙的に期待しています。もし将来、キーの名前が "year_of_birth" に変更されたり、データ構造がオブジェクトに変更されたりした場合、この関数は即座にエラーとなります。これは、関数とデータ構造が強く結びついた「密結合」の状態です。
  2. 低い再利用性: この関数は、{"birth_year": ...} という構造の辞書以外には使えません。例えば、単に 1995 という数値だけを使って年齢を計算したい、といった単純な用途には全く使えないのです。
  3. 煩雑なテスト: この関数をテストするためには、毎回 {"birth_year": 1995} のような辞書を作成する必要があります。年齢計算というロジックそのものをテストしたいだけなのに、余計なデータ構造の準備が必要です。

解決策:必要なデータだけを受け取る「疎結合」な関数

これらの問題を解決するには、関数が本当に必要としているデータ、つまり birth_year という数値だけを引数として受け取るように修正します。

import datetime

# 良い例:計算に必要な「生年」というint型だけを受け取る
def calculate_age(birth_year: int) -> int:
    """生年から現在の年齢を計算する。"""
    current_year = datetime.date.today().year
    return current_year - birth_year

この calculate_age 関数は、先ほどの問題点をすべて解決しています。

  1. 明確なインターフェース: 関数シグネチャ def calculate_age(birth_year: int) を見るだけで、この関数が整数値の birth_year を必要とすることが一目瞭然です。データ構造に関する暗黙の知識は一切不要です。
  2. 高い再利用性: この関数は特定のデータ構造に依存しないため、あらゆる場面で再利用できます。呼び出し側が、自身の持つデータ構造から birth_year を取り出して渡す責任を負います。Python# 辞書から呼び出す場合 user_a = {"id": 1, "name": "Taro", "birth_year": 1995} age_a = calculate_age(user_a["birth_year"]) print(f"辞書からの計算結果: {age_a}") # 直接数値を渡す場合 age_b = calculate_age(2005) print(f"直接指定した計算結果: {age_b}")
  3. 容易なテスト: テストは非常にシンプルになります。必要なのは数値だけです。Pythondef test_calculate_age(): assert calculate_age(1995) == 30 # 2025年時点

クラスとの連携

この疎結合な関数は、クラス設計においても非常にうまく機能します。

class User:
    def __init__(self, name: str, birth_year: int):
        self.name = name
        self.birth_year = birth_year

    @property
    def age(self) -> int:
        # 疎結合なユーティリティ関数を呼び出す
        return calculate_age(self.birth_year)

# Userオブジェクトから年齢を取得
user_obj = User(name="Jiro", birth_year=2000)
print(f"Userオブジェクトの年齢: {user_obj.age}")

User クラスが自身の birth_yearcalculate_age 関数に渡しています。これにより、ドメインの知識(Userが年齢を持つこと)と、純粋な計算ロジック(生年から年齢を計算すること)を綺麗に分離できています。


まとめ

関数を設計する際の原則は、**「関数が必要としない情報に依存させるな」**ということです。

  • 引数は、可能な限りプリミティブな型(int, str, boolなど)にする。
  • 辞書やオブジェクトを渡すのではなく、呼び出し側で必要なデータを取り出して渡す。

このアプローチにより、個々の関数は独立した再利用可能な部品となり、テストも容易になります。結果として、システム全体が変更に強く、保守しやすいクリーンなアーキテクチャへと繋がっていくのです。

よかったらシェアしてね!
  • URLをコピーしました!
  • URLをコピーしました!

この記事を書いた人

私が勉強したこと、実践したこと、してることを書いているブログです。
主に資産運用について書いていたのですが、
最近はプログラミングに興味があるので、今はそればっかりです。

目次