0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

引数設計改善事例

0
Last updated at Posted at 2026-01-22

Note

  • この記事は PR *** のマージ時に Claude AI によって自動生成されました。
  • 「Simple over Easy の設計哲学」というよりは、以下の2つのことをやりました程度です。
    • 引数のオブジェクトで不要なものを減らした。
    • ただし、早すぎる最適化も微妙だと思ったので、値オブジェクトを作ることまではしなかった。

引数設計改善事例

サマリー

図面評価システムの面積計算コンポーネントにおいて、メソッドの引数設計を根本的に見直すリファクタリングを実施しました。「Simple over Easy」の原則に基づき、情報過多なValue Objectから必要最小限の座標データへと引数を変更することで、以下の成果を実現しました。

  • ビジネス価値: 面積計算の精度向上と計算処理の高速化
  • 開発効率: コード理解の容易性向上と、新機能追加時の影響範囲の最小化
  • 品質担保: 責務の明確化により、20以上のテストケースすべてがパスする堅牢な設計

背景

図面評価システムでは、建築図面から敷地面積や建築面積を自動計算し、建蔽率・容積率などの法規制チェックを行っています。従来の実装では、面積計算メソッドが包括的なジオメトリデータオブジェクト全体を受け取る設計になっていましたが、以下の課題が顕在化していました。

  1. 情報過多による複雑性: 面積計算には座標データのみが必要なのに、単位情報、フォーマット情報、バージョン情報なども含む大きなオブジェクトを引数として要求
  2. 責務の曖昧さ: データ抽出の責務がどのレイヤーにあるかが不明確
  3. テスタビリティの低下: 大きなオブジェクトをモックする必要があり、テストコードが冗長

Before/After(擬似コード)

Before: 情報過多なオブジェクトを受け取る設計

# アンチパターン: 責務が混在し、テストや再利用が困難
class DataProcessor:
    def process_data(self, input_context: ComplexRawData) -> ProcessedResult:
        """
        大きなコンテキストオブジェクトを直接受け取る
        - 処理に不要なメタデータや認証情報なども含まれている
        - 内部で「データの抽出」と「計算」の両方を行ってしまっている
        """
        # 内部で大きなオブジェクトから必要な値を掘り起こす必要があり、
        # ComplexRawDataの構造が変わるとこのクラスまで壊れる
        target_values = input_context.get_payload().get_numeric_series()
        
        result = sum(target_values) / len(target_values)
        return ProcessedResult(value=result)

After: 必要最小限のデータのみを受け取る設計

# 改善パターン: 疎結合で再利用性の高い設計
class DataCalculator:
    def calculate_average(self, values: List[float]) -> float:
        """
        純粋に計算に必要なデータのみを引数として受け取る
        - 外部の複雑なデータ構造(コンテキスト)から切り離されている
        - 単体テストが非常に容易
        """
        if not values:
            return 0.0
        return sum(values) / len(values)

# 上位レイヤー(UseCaseなど)で抽出の責務を担う
class ProcessingService:
    def execute(self, raw_data: Dict):
        # 1. 複雑な構造から「ただの数値リスト」を抽出(抽出の責務)
        raw_series = raw_data.get("payload", {}).get("values", [])
        
        # 2. 計算機には必要なデータだけを渡す(実行の責務)
        calculator = DataCalculator()
        result = calculator.calculate_average(raw_series)
        return result

技術的詳細

アーキテクチャ選定の思考プロセス

本リファクタリングでは、以下の3つの設計原則を軸に検討を行いました。

(人間コメント:いや、3つめの引数の複雑性評価だけだよ)

3. 引数設計の複雑性評価

新たなValue Objectを作成するか、プリミティブ型を直接使用するかの判断基準:

  • 引数が2-3個程度 → プリミティブ型で十分
  • 引数が5個以上 → Value Object検討
  • 引数同士に強い関連性 → Value Object検討

今回は引数が2個かつ関連性が弱いため、プリミティブ型を選択しました。

学び・知見

1. 引数設計の原則「情報の最小化」

メソッドの引数は「そのメソッドが本当に必要とする情報のみ」に限定することで、以下のメリットが得られます:

  • 依存関係の明示化: メソッドが何に依存しているかが引数から明確
  • テストの簡素化: 必要最小限のテストデータでテストケースを記述可能
  • 変更の局所化: 上流のデータ構造変更が下流の計算ロジックに波及しない

2. 責務分離の判断基準

UseCase層とDomain層の責務分離において、以下の判断基準が有効でした:

  • データアクセス・抽出: UseCase層の責務
  • ビジネスルール・計算: Domain層の責務
  • 外部システム連携: UseCase層の責務

この分離により、ドメインロジックの純粋性を保ちつつ、実用的なシステムを構築できます。

3. リファクタリングの段階的アプローチ

大規模な設計変更を安全に実行するため、以下のステップを踏みました:

  1. 現状の動作をテストで保護: 既存の全テストケースをパス
  2. インターフェースの変更: メソッド引数の変更と呼び出し元の修正
  3. 内部実装の最適化: 新しい引数構造に合わせた処理の効率化
  4. テストケースの再構築: 新しい設計に合わせたテストの再編成

各ステップでテストが通ることを確認しながら進めることで、機能退行を防止できました。

免責事項

本記事は技術的知見の共有を目的としており、実際のプロジェクトから内容を一部抽象化・簡略化して記載しています。また、GitHub Pull Requestの内容をベースに自動で下書きを生成し、編集を加えたものです。具体的な実装や数値は例示であり、実際の運用環境での結果を保証するものではありません。


Generated by Claude AI on 2026-01-22 (UTC)

0
0
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
0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?