パラメーターとしてのカスタムオブジェクトを避ける必要がありますか?


49

Studentというカスタムオブジェクトがあるとします。

public class Student{
    public int _id;
    public String name;
    public int age;
    public float score;
}

そして、生徒の情報を表示するために使用されるクラスWindow

public class Window{
    public void showInfo(Student student);
}

正常に見えますが、Windowを個別にテストするのは簡単ではありません。関数を呼び出すには、実際のStudentオブジェクトが必要だからです。そこで、Studentオブジェクトを直接受け入れないようにshowInfoを変更しようとしています。

public void showInfo(int _id, String name, int age, float score);

ウィンドウを個別にテストする方が簡単です:

showInfo(123, "abc", 45, 6.7);

しかし、修正版には別の問題があることがわかりました。

  1. 生徒の変更(例:新しいプロパティの追加)には、showInfoのメソッド署名を変更する必要があります

  2. Studentに多くのプロパティがある場合、Studentのメソッド署名は非常に長くなります。

それでは、カスタムオブジェクトをパラメーターとして使用するか、オブジェクトの各プロパティをパラメーターとして受け入れますか?


40
そして、「改善された」にshowInfoは、実際の文字列、実際のフロート、2つの実際の整数が必要です。実際のStringオブジェクトを提供するよりも、実際のオブジェクトを提供する方が優れているのStudentでしょうか?
バートヴァンインゲンシェナウ

28
パラメーターを直接渡す際の大きな問題の1つは、2つのintパラメーターがあることです。呼び出しサイトから、実際に正しい順序でそれらを渡していることの確認はありません。idand ageとor firstNameとand を入れ替えるとどうなりlastNameますか?障害が発生する可能性のあるポイントを導入しており、それが顔に吹き飛ばされるまで検出するのは非常に困難な場合があり、すべてのコールサイトに追加します
クリスヘイズ

38
@ChrisHayesああ、古いshowForm(bool, bool, bool, bool, int)方法-私はそれらを愛する...
ボリス・スパイダー

3
@ChrisHayes少なくともそのないJS ...
イェンスSchauder

2
テストの過小評価されたプロパティ:テストで独自のオブジェクトを作成/使用するのが難しい場合、APIが何らかの作業を使用する可能性があります:)
Eevee

回答:


131

カスタムオブジェクトを使用して関連パラメーターをグループ化することは、実際に推奨されるパターンです。リファクタリングとして、Introduce Parameter Objectと呼ばれます。

問題は別の場所にあります。まず、ジェネリックWindowはStudentについて何も知らないはずです。代わりに、StudentWindowのみを表示することを知っている何らかの種類が必要Studentsです。第二に、のテストを大幅に複雑にする複雑なロジックが含まれていない限り、Studentテストするインスタンスを作成してもまったく問題はありません。そのロジックがある場合は、インターフェイスを作成してモックすることをお勧めします。StudentWindowStudentStudentWindowStudent


14
新しいオブジェクトが実際には論理的なグループ化ではない場合、問題を起こす可能性があるという警告に値します。単一のオブジェクトに設定されたすべてのパラメーターを靴べらにしようとしないでください。ケースバイケースで決定します。質問の例は、明らかにそれの良い候補であるように思われます。Studentグループ化は理にかなっているとアプリの他の領域にまでトリミングする可能性があります。
jpmc26

あなたはすでにオブジェクトを持っている場合Pedantically例えば、話すStudent、それは次のようになり、オブジェクト全体を保持
abuzittin gillifirca

4
デメテル法則も覚えておいてください。バランスを取る必要がありますが、メソッドがを取得するa.b.c場合、tldrは実行しませんa。メソッドがおおよそ4つ以上のパラメーターまたは2レベルのプロパティアクセッションを持つ必要があるポイントに到達した場合、おそらくファクタリングする必要があります。また、これはガイドラインであることに注意してください-他のすべてのガイドラインと同様に、ユーザーの裁量が必要です。盲目的にフォローしないでください。
ダンパントリー

7
この答えの最初の文は解析するのが非常に難しいことがわかりました。
ヘルリッチ

5
@Qwerky私は非常に強く反対します。学生は、オブジェクトグラフのリーフ(名前、DateOfBirthなどの他の些細なオブジェクトを除く)のように聞こえますが、学生の状態のコンテナにすぎません。学生はレコードタイプである必要があるため、構築するのが難しい理由は何もありません。学生のためにテストダブルを作成することは、テストを維持するのが難しい、および/またはいくつかの派手な分離フレームワークに大きく依存するためのレシピのように聞こえます。
サラ

26

あなたはそれがだと言う

関数を呼び出すには実際のStudentオブジェクトが必要なので、個別にテストするのは非常に簡単ではありません

ただし、ウィンドウに渡す学生オブジェクトを作成するだけです。

showInfo(new Student(123,"abc",45,6.7));

電話をかけるのはそれほど複雑ではないようです。


7
ときに問題が来るStudentを意味しUniversity、多くのことをいう、FacultySとCampusし、S ProfessorSとBuilding、SのいずれもがshowInfo、実際に使っていますが、あなたはそれと供給のみ関連学生「知る」ためのテストを可能にする任意のインターフェイスを定義していませんでした組織全体を構築せずにデータ。この例Studentは単純なデータオブジェクトであり、あなたが言うように、テストは喜んでそれを扱うべきです。
スティーブジェソップ

4
問題は、学生が大学を指し、大学が多くの学部とキャンパスを指し、教授と建物があり、邪悪な人に休息がない場合に起こります。
abuzittin gillifirca

1
@abuzittingillifirca、「オブジェクトマザー」は1つのソリューションであり、学生オブジェクトも複雑すぎる可能性があります。UniversityIdと、UniversityIdからUniversityオブジェクトを提供するサービス(依存性注入を使用)を保持する方が良い場合があります。
イアン

12
Studentが非常に複雑な場合、または初期化するのが難しい場合は、模擬するだけです。Mockitoまたは他の同等の言語のようなフレームワークを使用すると、テストがより強力になります。
ボルジャブ

4
showInfoが大学を気にしない場合は、nullに設定するだけです。ヌルは生産では恐ろしく、テストでは神からの送信です。テストでパラメータをnullとして指定できることは、意図を伝え、「このことはここでは必要ありません」と言います。また、関連するデータのみを含む学生向けのある種のビューモデルを作成することも検討しますが、showInfoはUIクラスのメソッドのように聞こえることを考慮します。
サラ

22

素人の言葉で:

  • あなたが呼ぶ「カスタムオブジェクトは」通常、単にオブジェクトと呼ばれています。
  • 重要なプログラムやAPIを設計するとき、または重要なAPIやライブラリを使用するときに、オブジェクトをパラメーターとして渡すことを避けることはできません。
  • オブジェクトをパラメーターとして渡すことはまったく問題ありません。Java APIを見ると、オブジェクトをパラメーターとして受け取る多くのインターフェースが表示されます。
  • あなたが使用するライブラリのクラスは、あなたや私のような単なる人間によって書かれているので、私たちが書いたクラスは「カスタム」ではなく、ただのクラスです。

編集:

以下のようTom.Bowen89 @それはSHOWINFOメソッドをテストするためにはるかに複雑ではありません状態:

showInfo(new Student(8812372,"Peter Parker",16,8.9));

3
  1. 生徒の例では、Studentコンストラクターを呼び出してshowInfoに渡す生徒を作成するのは簡単です。だから問題はありません。
  2. サンプルStudentがこの質問のために意図的に単純化されており、構築するのがより難しいと仮定すると、テストdoubleを使用できます。テスト用のダブル、モック、スタブなどのオプションは多数あり、Martin Fowlerの記事から選択することができます。
  3. showInfo関数をより汎用的にしたい場合は、パブリック変数、または渡されたオブジェクトのパブリックアクセサーを反復処理して、それらすべてに対してshowロジックを実行することができます。その後、その契約に準拠したオブジェクトを渡すことができ、期待どおりに機能します。これは、インターフェースを使用するのに適した場所です。たとえば、ShowableまたはShowInfoableオブジェクトをshowInfo関数に渡します。この関数は、学生情報だけでなく、インターフェイスを実装するオブジェクトの情報を表示できます(明らかに、オブジェクトを渡すことができる具体的または汎用的な方法に応じて、これらのインターフェイスにはより良い名前が必要です)であり、学生がサブクラスであるもの)。
  4. 多くの場合、プリミティブを渡すのが簡単であり、パフォーマンスに必要な場合もありますが、同様の概念をグループ化できるほど、コードは一般的に理解しやすくなります。気をつけるべき唯一のことは、やり過ぎないようにして、エンタープライズ・フィズバズになってしまうことです。

3

Code CompleteのSteve McConnellは、まさにこの問題に対処し、プロパティを使用する代わりにオブジェクトをメソッドに渡すことの利点と欠点について議論しました。

詳細が間違っている場合はご容赦ください。この本にアクセスしてから1年以上が経過しているので、メモリから作業しています。

彼は、オブジェクトを使用せずに、メソッドに絶対に必要なプロパティのみを送信する方が良いという結論に達しました。メソッドは、操作の一部として使用するプロパティ以外のオブジェクトについて何も知る必要はありません。また、時間がたつにつれて、オブジェクトが変更されると、オブジェクトを使用するメソッドに意図しない結果が生じる可能性があります。

また、多くの異なる引数を受け入れるメソッドになった場合、それはおそらくそのメソッドがやり過ぎであり、より多くの小さなメソッドに分解する必要があるという兆候であると述べました。

ただし、場合によっては、実際には多くのパラメーターが必要になります。彼が提供する例は、多くの異なるアドレスプロパティを使用して完全なアドレスを構築するメソッドです(ただし、これについて考えると、文字列配列を使用することで回避できます)。


7
完全なコード2があります。この問題専用のページ全体があります。結論は、パラメーターは正しい抽象化レベルでなければならないということです。オブジェクト全体を渡す必要がある場合もあれば、個々の属性だけを渡す場合もあります。
から来た

UV。Code Completeの参照は、2倍プラスです。テストの便宜性よりも設計の良い考慮事項。デザインを好む、私たちの文脈でマコーネルが言うだろうと思います。したがって、優れた結論は、「Studentこの場合の」パラメータオブジェクトを設計に統合することです。そして、これはテストがデザイン情報を与える方法であり、デザインの完全性を維持しながら、最も投票数の多い回答を完全に受け入れます。
レーダーボブ

2

オブジェクト全体を渡すと、テストの書き込みと読み取りがはるかに簡単になります。

public class AStudentView {
    @Test 
    public void displays_failing_grade_warning_when_a_student_with_a_failing_grade_is_shown() {
        StudentView view = aStudentView();
        view.show(aStudent().withAFailingGrade().build());
        Assert.that(view, displaysFailingGradeWarning());
    }

    private Matcher<StudentView> displaysFailingGradeWarning() {
        ...
    }
}

比較のために、

view.show(aStudent().withAFailingGrade().build());

値を個別に渡す場合、行は次のように記述できます。

showAStudentWithAFailingGrade(view);

実際のメソッド呼び出しはどこかのように埋められます

private showAStudentWithAFailingGrade(StudentView view) {
    int someId = .....
    String someName = .....
    int someAge = .....
    // why have been I peeking and poking values I don't care about
    decimal aFailingGrade = .....
    view.show(someId, someName, someAge, aFailingGrade);
}

要するに、実際のメソッド呼び出しをテストに入れることができないということは、APIが悪いというサインです。


1

意味のあるもの、いくつかのアイデアを渡す必要があります。

テストが簡単。オブジェクトを編集する必要がある場合、最小限のリファクタリングが必要なのは何ですか?この関数を他の目的に再利用するのは便利ですか?目的を果たすためにこの機能を提供するために必要な情報の最小量はどれくらいですか?(これを分割することにより(このコードを再利用できるようになる可能性があります)、この関数を作成し、このオブジェクトを排他的に使用するためにすべてのボトルネックを作成するという設計上の問題に注意してください。)

これらのプログラミングルールはすべて、正しい方向に考えさせるためのガイドにすぎません。コードビーストを作成しないでください。不確かで続行する必要がある場合は、ここで方向/自分自身または提案を選んでください。あなたが「ああ方法」-おそらく戻って、それをかなり簡単にリファクタリングできます。(たとえば、Teacherクラスがある場合-Studentと同じプロパティセットが必要なだけで、Personフォームのオブジェクトを受け入れるように関数を変更します)

私は、メインオブジェクトを渡したままにしておきたいと思います。コーディングする方法によって、この関数が何をしているのかをより簡単に説明できるからです。


1

これを回避する一般的な方法の1つは、2つのプロセス間にインターフェイスを挿入することです。

public class Student {

    public int id;
    public String name;
    public int age;
    public float score;
}

interface HasInfo {
    public String getInfo();
}

public class StudentInfo implements HasInfo {
    final Student student;

    public StudentInfo(Student student) {
        this.student = student;
    }

    @Override
    public String getInfo() {
        return student.name;
    }

}

public class Window {

    public void showInfo(HasInfo info) {

    }
}

これは少し厄介になることもありますが、内部クラスを使用すると、Javaで少し整頓されます。

interface HasInfo {
    public String getInfo();
}

public class Student {

    public int id;
    public String name;
    public int age;
    public float score;

    public HasInfo getInfo() {
        return new HasInfo () {
            @Override
            public String getInfo() {
                return name;
            }

        };
    }
}

その後Window、偽のHasInfoオブジェクトを渡すだけでクラスをテストできます。

これは、デコレータパターンの例だと思います。

追加しました

コードの単純さに起因する混乱があるようです。以下に、この手法をよりよく示す別の例を示します。

interface Drawable {

    public void Draw(Pane pane);
}

/**
 * Student knows nothing about Window or Drawable.
 */
public class Student {

    public int id;
    public String name;
    public int age;
    public float score;
}

/**
 * DrawsStudents knows about both Students and Drawable (but not Window)
 */
public class DrawsStudents implements Drawable {

    private final Student subject;

    public DrawsStudents(Student subject) {
        this.subject = subject;
    }

    @Override
    public void Draw(Pane pane) {
        // Draw a Student on a Pane
    }

}

/**
 * Window only knows about Drawables.
 */
public class Window {

    public void showInfo(Drawable info) {

    }
}

showInfoが学生の名前のみを表示したい場合、名前を渡すだけではどうですか?保守性と理解可能性の両方の点で、文字列が何を表すかについての手がかりのない文字列を含む抽象インターフェイスで意味的に意味のある名前付きフィールドをラップすると、大きなダウングレードのように感じます。
サラ

@kai- 戻り値の型のStudentおよびStringここでの使用は、純粋にデモンストレーション用です。描画する場合に描画するgetInfoなど、追加のパラメータが存在する可能性がありますPane。ここの概念は機能的なコンポーネントを元のオブジェクトのデコレータとして渡すことです。
-OldCurmudgeon

その場合、学生エンティティをUIフレームワークにしっかりと結合することになりますが、それはさらに悪いことに聞こえます
...-sara

1
@kai-まったく逆です。私のUIはHasInfoオブジェクトについてのみ知っています。Student一つになる方法を知っています。
-OldCurmudgeon

あなたが与える場合getInfoには、ボイドのパスを返すPaneに描画し、その後、(での実装Studentクラス)が急にスイングするように結合されるか、使用しているものは何でも。文字列を返してパラメータを0にすると、UIは、魔法の仮定と暗黙的な結合なしに文字列をどう処理するかを知りません。getInfo実際に関連するプロパティを持つビューモデルを返すようにすると、Studentクラスは再びプレゼンテーションロジックに結合されます。これらの選択肢のいずれも望ましいとは思わない
サラ

1

あなたはすでに多くの良い答えを持っていますが、代替ソリューションを見ることができるかもしれないいくつかの提案があります:

  • この例では、Student(明らかにモデルオブジェクト)がWindow(明らかにビューレベルのオブジェクト)に渡されています。中間のControllerオブジェクトまたはPresenterオブジェクトは、まだ持っていない場合に役立ち、ユーザーインターフェイスをモデルから分離できます。コントローラー/プレゼンターは、UIテスト用に置き換えるために使用できるインターフェイスを提供する必要があり、インターフェイスを使用してモデルオブジェクトとビューオブジェクトを参照し、テスト用の両方から分離できるようにする必要があります。これらを作成またはロードする抽象的な方法(ファクトリオブジェクト、リポジトリオブジェクトなど)を提供する必要がある場合があります。

  • モデルオブジェクトの関連部分をデータ転送オブジェクトに転送することは、モデルが複雑になりすぎた場合にインターフェイスするための便利なアプローチです。

  • 生徒がインターフェイス分離の原則に違反している可能性があります。その場合は、作業しやすい複数のインターフェイスに分割すると有益です。

  • 遅延読み込みは、大きなオブジェクトグラフの構築を容易にします。


0

これは実際にはまともな質問です。ここでの本当の問題は、「オブジェクト」という一般用語の使用です。これは少しあいまいになる場合があります。

一般に、古典的なOOP言語では、「オブジェクト」という用語は「クラスインスタンス」を意味するようになりました。クラスインスタンスはかなり重い場合があります-パブリックプロパティとプライベートプロパティ(およびその間のプロパティ)、メソッド、継承、依存関係など。いくつかのプロパティを単純に渡すためにそのようなものを使用したくない場合があります。

この場合、オブジェクトを単純にいくつかのプリミティブを保持するコンテナーとして使用しています。C ++では、これらのようなオブジェクトはとして知られていましたstructs(C#のような言語にもまだ存在します)。実際、構造体は、あなたが話す用途に合わせて設計されています-論理的な関係があったときに、関連するオブジェクトとプリミティブをグループ化しました。

ただし、現代の言語では、コードを記述しているときに構造体とクラスの違いはまったくないため、オブジェクトを使用しても問題ありません。(ただし、シーンの背後には、注意すべき違いがいくつかあります。たとえば、構造体は参照型ではなく値型です。)基本的に、オブジェクトをシンプルに保つ限り、簡単になります。手動でテストします。ただし、最新の言語とツールを使用すると、これをかなり軽減できます(インターフェイス、モックフレームワーク、依存性注入など)。


1
参照はほとんどの言語でintのサイズに過ぎないため、オブジェクトのサイズが10億テラバイトであっても、参照を渡すことは高価ではありません。受信メソッドが大きすぎるAPIにさらされているかどうか、および望ましくない方法で物事を結合するかどうかについてさらに心配する必要があります。ビジネスオブジェクト(Student)をビューモデル(StudentInfoまたはStudentInfoViewModelその他)に変換するマッピングレイヤーを作成することを検討しますが、必ずしも必要ではありません。
サラ

クラスと構造は非常に異なります。1つは値で渡され(受信するメソッドがコピーを取得することを意味します)、もう1つは参照で渡されます(受信者は元のポインタを取得するだけです)。この違いを理解しないことは危険です。
ラバーダック

@kai参照を渡すことは高くないことを理解しています。私が言っているのは、完全なクラスインスタンスを必要とする関数を作成することは、そのクラスやそのメソッドなどの依存関係によってはテストがより困難になる可能性があるということです。
lunchmeat317

個人的には、外部システム(ファイル/ネットワークIO)にアクセスする境界クラスまたは非決定的(たとえば、ランダム、システム時間ベースなど)にアクセスする境界クラスを除き、ほとんど何でもモックに反対です。テスト中のクラスに、テスト中の現在の機能に関係のない依存関係がある場合、可能な場合はnullを渡すことを好みます。しかし、パラメータオブジェクトを受け取るメソッドをテストしている場合、そのオブジェクトに多数の依存関係がある場合、全体的な設計が心配になります。そのようなオブジェクトは軽量でなければなりません。
サラ
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.