受信パラメータの変更はアンチパターンですか?[閉まっている]


60

私はJavaでプログラミングしており、常にコンバーターを次のように作成しています。

public OtherObject MyObject2OtherObject(MyObject mo){
    ... Do the conversion
    return otherObject;
}

新しい職場でのパターンは次のとおりです。

public void MyObject2OtherObject(MyObject mo, OtherObject oo){
    ... Do the conversion
}

私にとっては、着信パラメータを変更しないことに慣れていたので、少し臭いです。この着信パラメータの変更はアンチパターンですか、それとも大丈夫ですか?重大な欠点がありますか?


4
値渡しパラメータがそれらをローカル変数に効果的に変換するため、これに対する正しい答えは言語固有になります。
Blrfl

1
2番目のパターンは、オブジェクトを再利用するための効率測定として使用される場合があります。それooが新しいオブジェクトに設定されたポインターではなく、メソッドに渡されるオブジェクトであると仮定します。それはここですか?これがJavaであれば、おそらくC ++ではないかもしれません
リチャードティングル14年

3
これは早すぎる最適化のようです。一般に最初のフォームを使用することをお勧めしますが、パフォーマンスのボトルネックが発生した場合は、コメント付きの2番目のフォーム使用して正当化できます。コメントは、自分や他の人がそのコードを見て、同じ質問を繰り返し表示するのを防ぎます。
キーン14年

2
2番目のパターンは、関数が値を返す場合(例:成功/失敗)に役立ちます。しかし、実際に「間違った」ものは何もありません。オブジェクトを作成する場所によって異なります。
GrandmasterB

12
これらの2つの異なるパターンを同じ方法で実装する関数には名前を付けません。
ケーシー14年

回答:


70

これはアンチパターンではなく、悪い習慣です。

アンチパターンと単なる悪い習慣の違いは、アンチパターン定義です。

ボブおじさんのクリーンコードによると、あなたが示す新しい職場スタイルは悪い習慣であり、痕跡的またはOOP前の時代です。

引数は、関数への入力として最も自然に解釈されます。

関数のシグネチャを確認することを強制するものはすべて、ダブルテイクと同等です。それは認知の中断であり、避けるべきです。オブジェクト指向プログラミングの数日前には、出力引数が必要な場合がありました。ただし、出力引数の必要性の多くはオブジェクト指向言語ではなくなります


7
あなたがリンクした定義が、これがアンチパターンと見なされることを排除する理由がわかりません。
サミュエルエドウィン区14年

7
私はそうである根拠は、「我々は代わりに、我々は我々が持っているものをリサイクルし、引数として渡す必要があり、新しいオブジェクトを作成しないことにより、CPU / MEMを保存している」ということからだと仮定し、明らかに間違った程度誰も深刻なOOPの背景これはそれでできましたない、これまでのパターンを構成する-私たちは、定義によってそれアンチパターン考えることができる方法はありません...
vaxquis

1
入力パラメーターが、変更が必要なオブジェクトの大きなコレクションである場合はどうですか?
スティーブチェンバース

私もオプション1を好みますOtherObjectが、インターフェースはどうなりますか?(できれば)呼び出し元のみが、どの具象型が必要かを知っています。
user949300

@SteveChambersこの場合、クラスまたはメソッドの設計が悪いと思うので、それを避けるためにリファクタリングする必要があります。
piotr.wittchen

16

ロバートC.マーティンの有名な本「Clean Code」を引用:

出力引数は避けるべきです

関数には少数の引数が必要です

2番目のパターンは両方のルール、特に「出力引数」ルールに違反しています。この意味では、最初のパターンよりも悪いです。


1
私はそれが最初のものに違反すると言うでしょう、多くの議論は厄介なコードを意味しますが、私は2つの議論はまったく問題ないと思います。きれいなコードのルールは、5つまたは6つの着信データが必要な場合、1つのメソッドで多くのことを行いたいので、コードの可読性とOOPスコープを高めるためにリファクタリングする必要があると思います。
CsBalazsHungary 14年

2
とにかく、これは厳密な法律ではなく、強い提案だと思います。プリミティブ型では機能しないことはわかっています。プロジェクトで広く使用されているパターンである場合、ジュニアはintを渡すアイデアを取得し、Objectsのように変更されることを期待します。
CsBalazsHungary 14年

27
Clean Codeが正しいかどうかに関係なく、これらを避けるべき理由を説明せずにこれが価値ある答えだとは思いません。それらは石版に書かれた戒めではなく、理解が重要です。この答えは、本の中で推論の要約と章の参照を提供することにより、改善することができる
Daenyth

3
まあ、地獄、誰かがそれを本に書いたなら、どんな考えられる状況に対しても良いアドバイスに違いない。考える必要はありません。
ケーシー14年

2
@emodendroketしかし、男、それ男です!
ピエールアラード14年

15

呼び出し元は、繰り返しごとに新しいインスタンスを作成する代わりに、1つの変数を長いループで再利用できるため、2番目のイディオムは高速になります。

私は一般的にそれを使用しませんが、例えば。ゲームプログラミングでは、その場所があります。たとえば、JavaMonkeyのVector3fの多くの操作では、変更して結果として返す必要があるインスタンスを渡すことができます。


12
まあ、これがボトルネックであるかどうかは同意できますが、どこで作業しても、ボトルネックは通常、オブジェクトの複製や作成ではなく、効率の低いアルゴリズムです。ゲーム開発についてあまり知らない。
CsBalazsHungary

4
@CsBalazsHungaryオブジェクトの作成が懸念される場合の問題は、メモリの割り当てとガベージコレクションに関連する傾向があると思います。これはおそらく、アルゴリズムの複雑性に続く次のレベルのボトルネックになります(特にスマートフォンなどのメモリが限られている環境) 。
JAB

JMonkeyは、多くの場合パフォーマンスが重要なので、これをよく見ています。私はそれが「JavaMonkey」と呼ばれる聞いたことがない
リチャード・チンクル

3
@JAB:JVMのGCは、一時オブジェクトの場合に合わせて特別に調整されています。大量の非常に短命のオブジェクトを収集するのは簡単であり、多くの場合、一時的な世代全体を1回のポインターの移動で収集できます。
Phoshi

2
1がしなければならない場合、実際に、オブジェクトを作成し、それができなくなり、性能対効果。コードブロックの速度を大幅に最適化する必要がある場合、代わりにプリミティブとネイティブ配列を使用する必要があります。そのため、この答えの記述は成り立たないと思います。
vaxquis 14年

10

これらは2つの同等のコードだとは思いません。最初のケースでは、を作成する必要がありますotherObject。2番目の既存のインスタンスを変更できます。両方とも用途があります。コードの匂いは、他のものよりも好むでしょう。


私はそれらが同等ではないことを知っています。あなたは正しいです、私たちはこれらのことを永続させているので、違いを生むでしょう。あなたはそれらのどれもアンチパターンではないと言っています、それはユースケースの質問ですか?
CsBalazsHungary 14年

@CsBalazsHungaryはい、と言うでしょう。あなたが提供した小さなコードから判断します。
陶酔14年

@claaszが書いたように、もちろん更新されない着信プリミティブパラメーターがあると、より深刻な問題になると思います。必要でない限り、避けるべきです。
CsBalazsHungary 14年

1
@CsBalazsHungaryプリミティブな引数の場合、関数は機能しません。そのため、引数を変更するよりも問題が大きくなります。
陶酔14年

ジュニアは、プリミティブ型を出力パラメーターにしようとするofに陥ると思います。したがって、可能であれば最初のコンバーターを優先する必要があります。
CsBalazsHungary 14年

7

本当に言語に依存します。

Javaでは、2番目の形式はアンチパターンである場合がありますが、一部の言語ではパラメーターの受け渡しが異なります。値または参照によって、例えば、代わりにパスのエイダとVHDLで、パラメータは、モードを有することができinoutまたはin out

変更すると、エラーが発生しinたパラメータを読み込むか、outパラメータを、しかしへの変更in outパラメータは、呼び出し元に渡されます。

したがって、Adaの2つの形式(これらは合法的なVHDLでもあります)は

function MyObject2OtherObject(mo : in MyObject) return OtherObject is
begin
    ... Do the conversion
    return otherObject;
end MyObject2OtherObject;

そして

procedure MyObject2OtherObject(mo : in MyObject; oo : out OtherObject) is
begin
    ... Do the conversion
    oo := ... the conversion result;
end MyObject2OtherObject;

どちらにも用途があります。プロシージャは複数のOutパラメータで複数の値を返すことができますが、関数は単一の結果のみを返すことができます。2番目のパラメーターの目的はプロシージャ宣言で明確に記述されているため、このフォームに異議はありません。私は読みやすさのために機能を好む傾向があります。ただし、呼び出し元がオブジェクトをすでに作成している場合など、プロシージャの方が優れている場合があります。


3

それは確かに臭いのように見えます、そして、より多くの文脈を見ないで、それは確かに言うことは不可能です。これを行うには2つの理由がありますが、両方の選択肢があります。

まず、それは部分的な変換を実装するか、変換が失敗した場合に結果をデフォルト値のままにする簡潔な方法です。つまり、これがあるかもしれません:

public void ConvertFoo(Foo from, Foo to) {
    if (can't convert) {
        return;
    }
    ...
}

Foo a;
Foo b = DefaultFoo();
ConvertFoo(a, b);
// If conversion fails, b is unchanged

もちろん、通常、これは例外を使用して処理されます。ただし、何らかの理由で例外を回避する必要がある場合でも、これを行うためのより良い方法があります-TryParseパターンは1つのオプションです。

別の理由は、純粋な一貫性の理由である可能性があることです。たとえば、何らかの理由でこのメソッドがすべての変換関数(複数の出力を持つ他の変換関数など)に使用されるパブリックAPIの一部です。

Javaは複数の出力を処理するのが得意ではありません。一部の言語のような出力専用のパラメーターや、他の言語のような複数の戻り値を持つことはできませんが、それでも戻りオブジェクトを使用できます。

一貫性の理由はかなり不十分ですが、悲しいことに最も一般的かもしれません。

  • おそらく、職場(またはコードベース)のスタイル警官はJava以外のバックグラウンドから来たものであり、変更に消極的でした。
  • あなたのコードは、このスタイルがより慣用的な言語からの移植であったかもしれません。
  • 組織は異なる言語間でAPIの一貫性を維持する必要がある場合があり、これは最も一般的な分母のスタイルでした(それは簡単ですが、Googleでも起こります)。
  • または、遠い過去にスタイルがより意味を持ち、現在の形式に変形した可能性があります(たとえば、TryParseパターンである可能性がありますが、一部の善意の前身は、誰もそれをまったくチェックしなかったことを発見した後、戻り値を削除しました)。

2

2番目のパターンの利点は、呼び出し元に作成されたオブジェクトの所有権と責任を強制させることです。メソッドがオブジェクトを作成したか、再利用可能なプールから取得したかは疑いありません。呼び出し側は、新しいオブジェクトの寿命と廃棄に責任があることを知っています。

この方法の欠点は次のとおりです。

  1. ファクトリメソッドとして使用することはできません。呼び出し元はOtherObject、必要なサブタイプを正確に把握し、事前に構築する必要があります。
  2. これらのパラメーターが由来する場合、コンストラクター内のパラメーターを必要とするオブジェクトでは使用できませんMyObjectOtherObjectについて知らなくても構築可能でなければなりませんMyObject

答えは、c#での私の経験に基づいています。ロジックがJavaに変換されることを望みます。


あなたが述べた責任で、それはオブジェクトの「見づらい」ライフサイクルも作り出すと思います。複雑なメソッドシステムでは、渡すすべてのメソッドを調べ始めるよりも、オブジェクトを直接変更する方が好ましいでしょう。
CsBalazsHungary 14年

これは私が考えていたものでした。原始的な種類の依存性注入
リンドンホワイト14年

もう1つの利点は、OtherObjectが抽象またはインターフェースである場合です。関数はどのタイプを作成するのか分かりませんが、呼び出し側はそうするかもしれません。
user949300

2

緩い意味論を考える- myObjectToOtherObject均等にあなたが移動することを意味するかもしれないいくつかの秒に最初のオブジェクトからデータをかあなたは完全に新たにそれを変換することを、第二の方法は、より適切なようです。

ただし、メソッド名がConvert(「...変換を行う」部分を見ればわかるはずです)、2番目のメソッドは意味をなさないと思います。値を既存の別の値、IMOに変換しません。

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