web-dev-qa-db-ja.com

コーディングスタイルの問題:パラメーターを受け取り、それを変更してから、そのパラメーターを返す関数が必要ですか?

これらの2つの慣習が同じコインの両面にすぎないのか、それともどちらが真に優れているのかについて、友人と少し議論しています。

パラメータを取り、そのメンバーに入力してから返す関数があります。

Item predictPrice(Item item)

渡されたsameオブジェクトで機能するため、アイテムを返す意味がないと思います。実際、どちらかといえば、呼び出し元の観点からは、それがnew項目を返さないと期待できるので、問題を混乱させます。

彼はそれが違いをもたらさないと主張し、それが新しいアイテムを作成してそれを返したとしても問題にならないでしょう。次の理由により、私は強く同意しません。

  • アイテムへの複数の参照(またはポインターなど)が渡される場合、それらは新しいオブジェクトを割り当て、それを返すことは重要です。これらの参照は正しくないためです。

  • 非メモリ管理言語では、新しいインスタンスを割り当てる関数はメモリの所有権を主張するため、ある時点で呼び出されるクリーンアップメソッドを実装する必要があります。

  • ヒープへの割り当てはコストがかかる可能性があるため、呼び出された関数がそれを実行するかどうかが重要です。

したがって、オブジェクトを変更するのか、新しいオブジェクトを割り当てるのかを、メソッドシグネチャを介して確認できることが非常に重要であると思います。結果として、関数は渡されたオブジェクトを変更するだけなので、署名は次のようになるはずです。

void predictPrice(Item item)

私が使用してきたすべてのコードベース(確かにCおよびC++コードベースであり、Javaこれは私たちが作業している言語です)では、上記のスタイルは基本的に固執されており、かなり多くの私のコードベースと同僚のサンプルサイズは考えられるすべてのコードベースと同僚の中で小さいため、私の経験は優れているかどうかについての真の指標ではないと彼は主張しています。

それで、何か考えはありますか?

20
Squimmy

これは本当に意見の問題ですが、価値があるのは、アイテムが適切に変更されている場合、変更されたアイテムを返すことは誤解を招くと思います。これとは別に、predictPriceがアイテムを変更する場合は、そのことを示す名前(setPredictPriceなど)が必要です。

私は(順番に)好みます

  1. そのpredictPriceItemの方法でした(=ないことの正当な理由であり、これは全体的な設計に存在する可能性があります)。

    • 予測価格を返しました、または

    • 代わりにsetPredictedPriceのような名前でした

  2. そのpredictPricedid n'tItemを変更しましたが、予測価格を返しました

  3. そのpredictPricevoidと呼ばれるsetPredictedPriceメソッドでした

  4. そのpredictPricethisを返しました(それが含まれているインスタンスで他のメソッドにチェーンするメソッドの場合)(そしてsetPredictedPriceと呼ばれていました)

26
T.J. Crowder

オブジェクト指向の設計パラダイム内では、オブジェクト自体の外でオブジェクトを変更することはできません。オブジェクトの状態の変更は、オブジェクトのメソッドを介して行う必要があります

したがって、他のクラスのメンバー関数としてのvoid predictPrice(Item item)wrongです。 Cの時代には受け入れられたかもしれませんが、JavaおよびC++の場合、オブジェクトの変更はオブジェクトへのより深い結合を意味し、将来的に他の設計問題につながる可能性がありますクラスをリファクタリングしてフィールドを変更すると、「predictPrice」を他のファイルで変更する必要があります。

新しいオブジェクトを返しますが、関連する副作用はありません。渡されたパラメータは変更されません。あなた(predictPriceメソッド)は、そのパラメーターが他にどこで使用されているかを知りません。 Itemはどこかのハッシュの鍵ですか?これを行う際にハッシュコードを変更しましたか?他の誰かがそれが変化しないことを期待してそれを保持していますか?

これらの設計上の問題は、オブジェクトを変更するべきではないことを強く示唆している問題です(多くの場合、不変性を主張します)。そうした場合、状態への変更は、何かではなくオブジェクト自体によって抑制および制御されるべきです。他のクラスの外。


ハッシュの何かのフィールドをいじる場合に何が起こるかを見てみましょう。いくつかのコードを見てみましょう:

import Java.util.*;

public class Main {
    public static void main (String[] args) {
        Set set = new HashSet();
        Data d = new Data(1,"foo");
        set.add(d);
        set.add(new Data(2,"bar"));
        System.out.println(set.contains(d));
        d.field1 = 2;
        System.out.println(set.contains(d));
    }

    public static class Data {
        int field1;
        String field2;

        Data(int f1, String f2) {
            field1 = f1;
            field2 = f2;
        }

        public int hashCode() {
            return field2.hashCode() + field1;
        }

        public boolean equals(Object o) {
            if(!(o instanceof Data)) return false;
            Data od = (Data)o;
            return od.field1 == this.field1 && od.field2.equals(this.field2);

        }
    }
}

ideone

そして、これは(フィールドに直接アクセスする)最高のコードではないことを認めますが、これは、可変データがHashMapのキーとして使用されている問題、つまりこの場合は単にHashSet。

このコードの出力は次のとおりです。

true
false

何が起こったかというと、挿入時に使用されたhashCodeは、オブジェクトがハッシュ内にある場所です。 hashCodeの計算に使用される値を変更しても、ハッシュ自体は再計算されません。これは、any可変オブジェクトをハッシュのキーとして置くことの危険です。

したがって、質問の元の側面に戻ると、呼び出されるメソッドは、パラメーターとして取得するオブジェクトがどのように使用されているかを「認識」していません。これを行う唯一の方法として「オブジェクトを変化させる」を提供することは、忍び込むことができる多くの微妙なバグがあることを意味します。 ハッシュの値を失うような...あなたがさらに追加してハッシュが再ハッシュされない限り-私を信じて、それは厄介なバグを追い詰めるバグです(私は失いましたhashMapに20項目を追加するまでの値で、突然突然表示されます)。

  • veryオブジェクトを変更する十分な理由がない限り、新しいオブジェクトを返すのが最も安全です。
  • オブジェクトを変更する正当な理由があるis場合、その変更は、次のことができるいくつかの外部関数ではなく、オブジェクト自体(メソッドを呼び出す)によって行う必要があります。そのフィールドをいじる。
    • これにより、より少ないメンテナンスコストでオブジェクトをリファクタリングできます。
    • これにより、オブジェクトは、ハッシュコードを計算する値が変更されないことを確認できます(または、ハッシュコード計算の一部ではありません)。

関連: 同じifステートメント内でifステートメントの条件として使用される引数の値を上書きして返す

11
user40980

私は常にmutatingではない他の誰かのオブジェクトの習慣に従います。つまり、内部にあるclass/struct/whathaveyouが所有するオブジェクトのみを変更します。

次のデータクラスがあるとします。

class Item {
    public double price = 0;
}

これで結構です:

class Foo {
    private Item bar = new Item();

    public void predictPrice() {
        bar.price = bar.price + 5; // or something
    }
}

// Elsewhere...

Foo foo = Foo();
foo.predictPrice();
System.out.println(foo.bar.price);
// Since I called predictPrice on Foo, which takes and returns nothing, it's
// apparent something might've changed

そしてこれは非常に明確です:

class Foo {
    private Item bar = new Item();
}
class Baz {
    public double predictPrice(Item item) {
        return item.price + 5; // or something
    }
}

// Elsewhere...

Foo foo = Foo();
Baz baz = Baz();
foo.bar.price = baz.predictPrice(foo.bar);
System.out.println(foo.bar.price);
// Since I explicitly set foo.bar.price to the return value of predictPrice, it's
// obvious foo.bar.price might've changed

しかし、これはぼんやりしていて私の趣味には神秘的です。

class Foo {
    private Item bar = new Item();
}
class Baz {
    public void predictPrice(Item item) {
        item.price = item.price + 5; // or something
    }
}

// Elsewhere...

Foo foo = Foo();
Baz baz = Baz();
baz.predictPrice(foo.bar);
System.out.println(foo.bar.price);
// In my head, it doesn't appear that to foo, bar, or price changed. It seems to
// me that, if anything, I've placed a predicted price into baz that's based on
// the value of foo.bar.price
2
Ben Leggiero

構文は言語によって異なります。また、言語によってまったく異なることを意味するため、言語に固有ではないコードを示すことは意味がありません。

Javaでは、Itemは参照型です。これは、Itemのインスタンスであるオブジェクトへのポインターの型です。 C++では、この型は_Item *_として記述されます(同じものの構文は言語によって異なります)。したがって、JavaのItem predictPrice(Item item)は、C++のItem *predictPrice(Item *item)と同等です。どちらの場合も、ポインタ()を受け取るのは関数(またはメソッド)です。オブジェクト、オブジェクトへのポインタを返します。

C++では、Item(他に何もない)はオブジェクト型です(Itemがクラスの名前であると想定)。このタイプの値はオブジェクトであり、Item predictPrice(Item item)はオブジェクトを受け取ってオブジェクトを返す関数を宣言します。 _&_は使用されないため、値によって渡されて返されます。つまり、オブジェクトは渡されたときにコピーされ、返されたときにコピーされます。 Javaにはオブジェクトタイプがないため、Javaには同等のものはありません。

どっち?あなたが質問で尋ねていることの多く(例えば、「渡されたものと同じオブジェクトで動作するので...」と私は信じています)は、ここで渡され何が返されるかを正確に理解することに依存しています。

1
user102008

私がコードベースを遵守してきた慣習は、構文から明確なスタイルを好むことです。関数が値を変更する場合、ポインターによる受け渡しを優先します

void predictPrice(Item * const item);

このスタイルの結果:

  • それをあなたが見ると呼びます:

    predictPrice(&my_item);

そしてあなたはそれがあなたのスタイルへの忠実さによって修正されることを知っています。

  • それ以外の場合は、関数にインスタンスを変更させたくない場合は、const refまたは値で渡すことをお勧めします。
1
user27886

私は強い好みはありませんが、オブジェクトを返すとAPIの柔軟性が向上すると投票します。

メソッドに他のオブジェクトを返す自由がある間だけ意味があることに注意してください。 javadocが@returns the same value of parameter aそれでは役に立たない。しかし、javadocが@return an instance that holds the same data than parameter a、同じインスタンスまたは別のインスタンスを返すことは、実装の詳細です。

たとえば、現在のプロジェクト(Java EE)にはビジネスレイヤーがあります。 DBに保存する(および自動IDを割り当てる)従業員、たとえば、私は次のような従業員

 public Employee createEmployee(Employee employeeData) {
   this.entityManager.persist(employeeData);
   return this.employeeData;
 }

もちろん、JPAはIDを割り当てた後に同じオブジェクトを返すため、この実装との違いはありません。ここで、JDBCに切り替えた場合

 public Employee createEmployee(Employee employeeData) {
   PreparedStatement ps = this.connection.prepareStatement("INSERT INTO EMPLOYEE(name, surname, data) VALUES(?, ?, ?)");
   ... // set parameters
   ps.execute();
   int id = getIdFromGeneratedKeys(ps);
   ??????
 }

今、????? 3つのオプションがあります。

  1. Idのセッターを定義すると、同じオブジェクトが返されます。醜い、なぜならsetIdはどこでも利用できるからです。

  2. いくつかのリフレクション作業を行い、EmployeeオブジェクトにIDを設定します。汚れていますが、少なくともcreateEmployeeレコードに限定されています。

  3. 元のEmployeeをコピーし、設定するidも受け入れるコンストラクターを提供します。

  4. DBからオブジェクトを取得します(一部のフィールド値がDBで計算されている場合、または地域を入力する場合に必要になる場合があります)。

これで、1と2では同じインスタンスが返されますが、3と4では新しいインスタンスが返されます。プリンシペからAPIで「実際の」値がメソッドによって返される値になると安定した場合は、より多くの自由度があります。

それに対して私が考えることができる唯一の本当の議論は、implementatios 1.または2.を使用すると、APIを使用している人々が結果の割り当てを見落とす可能性があり、3。または4.の実装に変更するとコードが壊れるということです。

とにかく、ご覧のとおり、私の好みは他の個人的な好み(IDのセッターの禁止など)に基づいているため、すべての人に当てはまるとは限りません。

0
SJuan76

Javaでコーディングする場合、可変タイプの各オブジェクトの使用法を次の2つのパターンのいずれかに一致させる必要があります。

  1. 正確に1つのエンティティは、可変オブジェクトの「所有者」と見なされ、そのオブジェクトの状態をそれ自体の一部と見なします。他のエンティティは参照を保持できますが、参照をidentifying他の誰かが所有するオブジェクトと見なす必要があります。

  2. オブジェクトは変更可能ですが、それへの参照を保持している人は誰もそれを変更できないため、インスタンスを事実上不変にします(Javaのメモリモデルの癖のため、事実上不変のオブジェクトにスレッドを持たせる良い方法はありません。各アクセスに間接的なレベルを追加することなく、安全で不変のセマンティクス)。このようなオブジェクトへの参照を使用して状態をカプセル化し、カプセル化された状態を変更するエンティティは、適切な状態を含む新しいオブジェクトを作成する必要があります。

これらのメソッドが上記の2番目のパターンに適合するように見えるため、基になるオブジェクトを変更して参照を返すメソッドは好きではありません。このアプローチが役立つ場合がいくつかありますが、オブジェクトを変更するコードはlookのようにする必要があります。 myThing = myThing.xyz(q);のようなコードは、単にmyThing.xyz(q);を使用する場合よりも、myThingで識別されるオブジェクトを変更する場合のように見えます。

0
supercat