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


19

私は友人と、これらの2つの慣習が単に同じコインの両面であるのか、それとも本当に優れているのかについて、少し議論しています。

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

Item predictPrice(Item item)

渡されたのと同じオブジェクトで機能するため、アイテムを返す必要はないと考えています。実際、発信者の観点から見ると、もしそうでない場合は、新しいアイテムを返すと期待できるので、問題を混乱させます。

彼はそれが違いを生まないと主張し、それが新しいアイテムを作成してそれを返すかどうかさえ問題ではないだろうと主張する。次の理由から、私は強く反対します。

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

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

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

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

void predictPrice(Item item)

すべてのコードベース(私たちが取り組んでいる言語であるJavaではなく、明らかにCおよびC ++コードベース)で、上記のスタイルは本質的にかなり経験豊富なプログラマーによって守られ、守られています。彼は、私のコードベースと同僚のサンプルサイズは、考えられるすべてのコードベースと同僚のなかで小さいため、私の経験は優れているかどうかの真の指標ではないと主張しています。

それで、何か考えは?


strcatは、パラメーターを所定の位置に変更し、引き続き返します。strcatはvoidを返す必要がありますか?
ジェリージェレミア

JavaとC ++は、パラメーターの受け渡しに関して非常に異なる動作をします。そうである場合Itemとそうclass Item ...でないtypedef ...& Item場合、ローカルitemはすでにコピーです
カレス

google.github.io/styleguide/cppguide.html#Output_Parameters Googleは出力にRETURN値を使用することを好みます
-Firegun

回答:


26

これは本当に意見の問題ですが、それが価値があるために、アイテムがその場で修正された場合、修正されたアイテムを返すのは誤解を招くと思います。それpredictPriceとは別に、アイテムを変更する場合は、それを実行することを示す名前を付ける必要がありsetPredictPriceます。

私は(順番に)好むだろう

  1. それpredictPriceは(モジュロではないという正当な理由であり、あなたの全体的な設計にあるかもしれません)の方法でしたItem

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

    • setPredictedPrice代わりに次のような名前を持っていた

  2. それpredictPrice はなかった変更Itemが、予想価格を返さ

  3. それはpredictPriceだったvoidと呼ばれる方法setPredictedPrice

  4. predictPrice返されたthis(それが含まれるインスタンス上の他のメソッドへのメソッドチェーンのために)(そして呼び出されたsetPredictedPrice


1
返信いただきありがとうございます。1に関しては、Item内にpredictPriceを含めることはできません。2は良い提案です-私はそれがおそらく正しい解決策だと思います。
スキミー14年

2
@Squimmy:確かに、私はあなたが主張しているパターンを正確に使用している誰かによって引き起こされたバグに対処するために15分を費やしました:a = doSomethingWith(b) 修正 bして修正してそれを返しましbた。初期化。:-)
TJクラウダー

11

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

したがって、void predictPrice(Item item)他のクラスのメンバー関数は間違っています。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);

        }
    }
}

イデオン

そして、これは最大のコードではないことを認めます(フィールドに直接アクセスします)が、HashMapのキーとして使用される可変データ、またはこの場合は単にハッシュセット。

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

true
false

何が起こったのかは、挿入されたときに使用されたhashCodeがオブジェクトがハッシュ内にあるということです。hashCodeの計算に使用される値を変更しても、ハッシュ自体は再計算されません。これは、パッティングの危険性である任意のハッシュのキーとして変更可能なオブジェクトを。

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

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

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


3
それは正しく聞こえません。ほとんどの場合、のメンバーをpredictPrice直接いじってはなりませんItemが、Itemそのメソッドを呼び出すとどうなりますか?それが変更されている唯一のオブジェクトである場合、変更するオブジェクトのメソッドであるという引数を作成することができますが、複数のオブジェクト(同じクラスのものであってはなりません)は、特にむしろ高レベルのメソッド。

@delnanこれは、ハッシュの値が消えて再出現するのを一度追跡しなければならなかったバグに対する(過剰な)反応である可能性があることを認めます-誰かがハッシュのオブジェクトのフィールドを挿入後にいじっていましたその使用のためにオブジェクトのコピーを作成するのではなく。取ることができる防御的なプログラミング手段(不変オブジェクトなど)がありますが、オブジェクトの「所有者」ではない場合にオブジェクト自体の値を再計算したり、オブジェクト自体が簡単に戻れるようなものはありませんあなたは難しい。

カプセル化を強化するために、クラス関数の代わりに、より多くの自由な関数を使用することには良い議論があります。当然、定数オブジェクトを取得する関数(暗黙的thisまたはパラメーター)は、観察可能な方法でそれを変更すべきではありません(一部の定数オブジェクトは物事をキャッシュするかもしれません)。
デデュプリケーター14年

2

私は常に他人のオブジェクトを変異させないという慣習に従います。つまり、内部にあるクラス/構造体/ whathaveyouが所有するオブジェクトのみを変更します。

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

class Item {
    public double price = 0;
}

これはOKです:

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

コメントでダウン票に同行してください。今後より良い答えを書く方法を知っています。)
ベンレジェロ

1

構文は異なる言語間で異なり、言語固有ではないコードを表示しても意味がありません。異なる言語ではまったく異なることを意味するからです。

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

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

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


1

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

void predictPrice(Item * const item);

このスタイルの結果:

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

    predictPrice(&my_item);

スタイルを順守することで変更されることがわかっています。

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

これははるかに明確な構文ですが、Javaでは使用できないことに注意してください。
ベンレジェロ

0

強い好みはありませんが、オブジェクトを返すと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のセッターを定義すると、同じオブジェクトが返されます。Uい、setIdどこでも利用可能になるため。

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

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

  4. DBからオブジェクトを取得します(一部のフィールド値がDBで計算される場合、またはRealtionshipを設定する場合に必要になる可能性があります)。

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

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

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


0

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

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

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

メソッドは、上記の2番目のパターンに適合するように見えるので、基になるオブジェクトを変更し、参照を返すメソッドが好きではありません。このアプローチが役立つ場合がいくつかありますが、オブジェクトを変更するコードは、そうするように見えるはずです。以下のようなコードをmyThing = myThing.xyz(q);はるかに少ないルックス、それはで識別されるオブジェクトに変異するようにmyThing、単に希望よりをmyThing.xyz(q);

弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.