2
2

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

共通メソッドの引数肥大化と依存を避ける設計Tips

Last updated at Posted at 2025-08-16

はじめに

あるプロダクトで、複数のAPIから共通のSELECTのためのrepository.selectを呼び出す構成を取っていました。

イメージとしては次のような形です。

A処理 ⇒ repository.select
B処理 ⇒ repository.select

当初はシンプルで問題なく動作していましたが、プロダクトが成長するにつれて課題が出てきました。

発生した課題

A処理の要件が増え、repository.selectに新しいパラメータを追加する必要が生じました。
その結果、

  • B処理側は大きな変更は不要なのに、
  • 共通repository.selectに依存しているため、B処理側も修正を余儀なくされる

という状況になりました。

さらに、使用している言語が動的型付け言語だったため、

  • 引数が合わなくても実行できてしまう
  • テストをすり抜けたデグレードが発生するリスクが高まる

という問題が顕在化してきました。

改善の方針

そこで、次のように呼び出し構造を整理しました。

A処理 ⇒ repository.select
B処理 ⇒ 中間メソッド ⇒ repository.select

実装イメージ

# 共通の repository.select
# 期間などのフィルタは任意指定。None は「その条件を適用しない」意味で扱う。
repository.select(user_id=123, start_date=date(2025,1,1), end_date=date(2025,1,31))

# 中間メソッド
def select_user_summary(user_id: int):
    """
    B処理は期間指定を行わないユースケース。
    明示的に None を渡して repository.select に委譲する。
    """
    return repository.select(
        user_id=user_id,
        start_date=None,   # フィルタ無し
        end_date=None,     # フィルタ無し
    )

# B処理側は専用の中間メソッドを呼ぶだけ
rows = select_user_summary(123)
  • B処理用の中間メソッドを新設
  • B処理は、自分に必要なパラメータだけを渡す
  • 中間メソッドは、それ以外のパラメータに対しては None を明示的に設定して repository.selectへ委譲

この仕組みが成り立つ前提として、repository.select の新規パラメータは任意(オプション)であり、None = 条件未適用 という契約を守る必要があります。必須化するような破壊的変更は吸収できません。

改善後のメリット

  • 意図しない引数の混入を防止できる
  • B処理側のコード修正が最小限で済む
  • デグレのリスクを低減でき、安心してA処理側の進化に対応可能
  • テストコードでA処理・B処理がそれぞれ独立に動作確認できる

特に検索系では「デフォルト条件の解釈違い」がバグ源になりやすいため、None=条件未適用 を契約として固定するのは効果的。

補足: 他のアプローチとの比較

今回のケースでは「中間メソッドを設ける」方法を採用しました。
他のアプローチと比較すると次のようになります。

1. 型ヒント・型チェックの導入

動的型付け言語では、mypypydantic のようなツールを用いた型チェックやスキーマバリデーションが考えられます。
ただし、今回の本質である 「A処理の拡張がB処理に波及する」構造的依存 は解決しません。

一方で、中間層の契約(例:Noneの意味)を型で表現することで退行を早期に検知できるため、中間層+型導入の併用は有効。

2. 共通メソッドをAPIごとに分割する

repository.selectを A処理用・B処理用に完全分割する方法も考えられます。
ただし完全に別メソッドにすると、共通処理の重複が増え保守コストが高まります。
今回の 中間メソッド(ファサード/Adapter) は、呼び出し契約を分離しつつ、共通のクエリ組み立ては repository に集約できる点でバランスが取れています。

3. 中間層を設ける(今回採用)

  • メリット:
    • A処理の進化がB処理に波及しない
    • 共通処理は repository.selectに集約でき、重複も最小化できる
    • 実装コストが低い
  • デメリット:
    • None を「条件未指定」として扱うチーム規約の共有が必要

今回の判断

  • 型導入では依存関係の問題は解決しない
  • 完全分割では重複コードが増える

このため、中間メソッドを設ける方法が最もバランスが良いと判断しました。
結果として、A処理の拡張時にB処理への修正は不要となり、デグレードのリスクを大きく下げることができたと考えています。

まとめ

共通化は便利ですが、成長するプロダクトの中では「引数肥大化」がリスクになると思います。
もともとはA処理、B処理で同じ責任を果たしていたところ、A処理が進化することによって、もともとのrepository.selectが単一責任の原則から外れつつあったと考えています。
今回の取り組みでは、中間層を設けることで依存関係を整理し、デグレードを防ぐ仕組みを導入できました。

  • 共通化のメリットとデメリットを天秤にかける
  • 必要に応じて「責務を切り分ける」

この2点を意識することで、将来の変更に強い設計を作れると考えています。

2
2
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
2
2

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?