web-dev-qa-db-ja.com

入力パラメーターの変更はアンチパターンですか?

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

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

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

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

入力パラメーターを変更しないことに慣れているので、私にとっては少し臭いです。この入力パラメータの変更はアンチパターンですか、それとも問題ありませんか?深刻な欠点はありますか?

64
CsBalazsHungary

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

アンチパターンと単なる悪い習慣の違いはここにあります: アンチパターン定義

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

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

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

73

ロバート・C・マーティンの有名な本「クリーン・コード」を引用:

出力引数は避けてください

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

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

17
claasz

2番目のイディオムの方が高速な場合があります。これは、呼び出し側が、反復ごとに新しいインスタンスを作成する代わりに、長いループで1つの変数を再利用できるためです。

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

15
user470365

これらは2つの同等のコードではないと思います。最初のケースでは、otherObjectを作成する必要があります。 2番目に既存のインスタンスを変更できます。どちらにも用途があります。コードのにおいは、他のものよりも優先されます。

10
Euphoric

それは本当に言語に依存します。

Javaの2番目の形式はアンチパターンの可能性がありますが、一部の言語ではパラメータの受け渡しが異なります。たとえば、AdaとVHDLでは、値渡しまたは参照渡しではなく、パラメータにinモードを指定できます、out、または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番目のパラメーターの目的はプロシージャー宣言で明確に述べられているため、このフォームに異議はありません。私は読みやすさのためにこの機能を好む傾向があります。しかし、手順が優れている場合があります。呼び出し元がすでにオブジェクトを作成している場所。

7
Brian Drummond

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

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

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

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

3
Rotem

それは確かに臭いように見えます、そして、より多くの文脈を見なければ、確かに言うことは不可能です。これを行うには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 pattern が1つのオプションです。

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

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

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

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

緩やかなセマンティクス-myObjectToOtherObjectを考えると、最初のオブジェクトから2番目のオブジェクトにsomeデータを移動すること、または完全に新しく変換することを同じように意味する可能性があるため、2番目の方法の方が適切と思われます。

ただし、メソッド名がConvertである場合(「変換を行う」の部分を見るとそうなります)、2番目のメソッドは意味がありません。値を既存の別の値であるIMOに変換しません。

2
guillaume31