web-dev-qa-db-ja.com

カスタムオブジェクトをパラメーターとして使用しないでください。

カスタムオブジェクトがあると仮定しますStudent

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

そして、クラスWindowStudentの情報を表示するために使用されます:

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

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

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

テストが簡単になるようにWindow個別に:

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

しかし、変更されたバージョンには別の問題があることがわかりました。

  1. 学生の変更(例:新しいプロパティの追加)では、showInfoのmethod-signatureを変更する必要があります

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

したがって、カスタムオブジェクトをパラメーターとして使用するか、オブジェクトの各プロパティをパラメーターとして受け入れると、どちらがより保守しやすくなりますか?

49
ggrr

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

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

131
Euphoric

あなたはそうだと言う

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

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

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

呼び出すのはそれほど複雑ではないようです。

26
Tom.Bowen89

簡単に言えば:

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

編集:

@ Tom.Bowen89のように、showInfoメソッドをテストすることはそれほど複雑ではありません。

showInfo(new Student(8812372,"Peter Parker",16,8.9));
22
  1. 学生の例では、Studentコンストラクターを呼び出して、showInfoに渡す学生を作成するのは簡単だと思います。なので問題ありません。
  2. 例のスチューデントがこの質問のために故意に取るに足らないものであり、構築するのがより難しいと仮定すると、テスト double を使用できます。マーティンファウラーの記事で取り上げられている、ダブルス、モック、スタブなどのテスト用のオプションがいくつかあります。
  3. ShowInfo関数をより汎用的なものにしたい場合は、パブリック変数を反復処理したり、オブジェクトのパブリックアクセサーを渡して、それらすべてに対してshowロジックを実行したりできます。次に、そのコントラクトに準拠したオブジェクトを渡すことができ、期待どおりに機能します。これは、インターフェースを使用するのに適した場所です。たとえば、ShowableまたはShowInfoableオブジェクトをshowInfo関数に渡して、学生情報だけでなく、インターフェースを実装するオブジェクトの情報も表示できるようにします(明らかに、これらのインターフェースには、オブジェクトを渡すことができるオブジェクトの具体的または汎用性に応じて、より適切な名前が必要です。であり、Studentはそのサブクラスです。
  4. 多くの場合、プリミティブを渡す方が簡単であり、ときどきパフォーマンスに必要ですが、同様の概念をグループ化できるほど、コードは一般的に理解しやすくなります。注意しなければならない唯一のことは、やりすぎないようにして、 enterprise fizzbuzz で終わることです。
3
Encaitar

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

私が本にアクセスしてから1年以上経過しているため、詳細の一部が間違っている場合は、私を許してください。

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

また、多くの異なる引数を受け入れるメソッドになってしまう場合は、そのメソッドが多すぎることを示しているため、より小さなメソッドに分解する必要があることを示しています。

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

3
user1666620

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

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が悪いことを示しています。

良い答えはたくさんありますが、別の解決策が見つかる可能性があるいくつかの提案を次に示します。

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

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

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

  • 遅延読み込みを使用すると、大きなオブジェクトのグラフを簡単に作成できます。

1
Jules

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

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

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

メインのオブジェクトを渡しておくのが一番です。コードの記述方法によって、この関数が何をしているのかを簡単に説明できるからです。

1
Lilly

これに関する一般的なルートの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クラスをテストできます。

これは Decorator Pattern の例だと思います。

追加

コードの単純さが原因で混乱が生じているようです。テクニックをよりよく示す別の例を次に示します。

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) {

    }
}
1
OldCurmudgeon

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

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

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

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

0
lunchmeat317