はじめに:肥大化する main.py
プロジェクトの初期、私の書くPythonコードはいつもシンプルでした。
main.py の中に、DBへの接続、外部APIの呼び出し、ビジネスロジック、Slackへの通知…すべてを素直に上から下へ書き下ろしていました。
しかし半年後。main.py は3000行を超え、「神(God)ファイル」に成長していました。
# 典型的な神ファイルの一部
def process_trading_data():
conn = psycopg2.connect("...") # 直書きのDB接続
api = BinanceAPI("SECRET_KEY") # 直書きのAPIキー
data = api.get_klines()
# 複雑な計算ロジック(100行)...
cursor = conn.cursor()
cursor.execute("INSERT INTO ...") # 直書きのSQL
requests.post("https://hooks.slack.com/...", json={"text": "Done"})
このコードには絶望的な問題が1つあります。「テストが一切書けない」のです。
計算ロジックだけをテストしたいのに、実行すると本番のDBに繋がり、本番のAPIを叩き、Slackに通知が飛んでしまいます。
救済のアーキテクチャ:「依存性の注入(DI)」
この絡み合ったスパゲティをほどく魔法の概念が「依存性の注入(Dependency Injection)」です。
名前は仰々しいですが、考え方は超シンプル。「関数の中で直接作るな。外から引数として渡せ」**です。
# ✅ DIを使ったリファクタリング後
class TradingService:
# 外部依存(DBやAPI)は「外から(__init__経由で)」受け取る
def __init__(self, db_repo: AbstractDB, api_client: AbstractAPI, notifier: AbstractNotifier):
self.db = db_repo
self.api = api_client
self.notifier = notifier
def process(self):
data = self.api.get_klines()
# 複雑な計算ロジック(テスト可能!)
self.db.save(data)
self.notifier.send("Done")
DIがもたらす「3つの恩恵」
1. テストが信じられないほど簡単になる
本番のDBに繋がなくても、テスト時には「偽物(モック)」を渡すだけでロジックのテストができます。
# テストコード
service = TradingService(db_repo=MockDB(), api_client=MockAPI(), notifier=MockSlack())
service.process()
# DBがなくても、APIがメンテ中でもテストできる!
2. 変更に強くなる
「やっぱりSlackじゃなくてDiscordに通知したい」となった時、TradingService の中のコードを1行も書き換える必要はありません。外側(main.py)で渡すオブジェクトを DiscordNotifier() に変えるだけです。
3. 「何をするクラスか」が一目でわかる
__init__ の引数を見るだけで、「あ、このクラスはDBとAPIと通知機能に依存しているんだな」と、設計の全貌がパッと見で理解できるようになります。
「関数の中で import してインスタンス化する」という誘惑に勝った時、あなたのコードは「スクリプト」から「ソフトウェア」へと進化します。