Javaの優れたスタイル(インスタンス変数と戻り値)


32

クラスのいくつかのメソッドで共通のデータを使用する必要がある場合、これらの2つの方法のどちらを使用するかを決めるのに苦労することがよくあります。より良い選択は何でしょうか?

このオプションでは、インスタンス変数を作成して、追加の変数を宣言する必要を回避し、メソッドパラメーターの定義を回避することもできますが、これらの変数がインスタンス化/変更される場所はそれほど明確ではありません。

public class MyClass {
    private int var1;

    MyClass(){
        doSomething();
        doSomethingElse();
        doMoreStuff();
    }

    private void doSomething(){
        var1 = 2;
    }

    private void doSomethingElse(){
        int var2 = var1 + 1;
    }

    private void doMoreStuff(){
        int var3 = var1 - 1;
    }
}

または、ローカル変数をインスタンス化して引数として渡すだけですか?

public class MyClass {  
    MyClass(){
        int var1 = doSomething();
        doSomethingElse(var1);
        doMoreStuff(var1);
    }

    private int doSomething(){
        int var = 2;
        return var;
    }

    private void doSomethingElse(int var){
        int var2 = var + 1;
    }

    private void doMoreStuff(int var){
        int var3 = var - 1;
    }
}

答えが両方とも正しいという場合、どちらがより頻繁に表示/使用されますか?また、各オプションに長所/短所を追加できる場合は非常に価値があります。



1
インスタンス変数に中間結果を入れると、これらの変数のスレッド間で競合が発生する可能性があるため、同時実行がより難しくなることを誰もまだ指摘していないと思います。
-sdenham

回答:


107

私はこれがまだ言及されていないことに驚いています...

var1実際にオブジェクトの状態の一部であるかどうかによって異なります

あなたは、これらのアプローチの両方が正しく、それが単なるスタイルの問題であると仮定します。あなたは間違っている。

これは完全に適切にモデル化する方法に関するものです。

同様に、オブジェクトの状態private変更するインスタンスメソッドが存在します。それがあなたのメソッドがしていることではない場合、そうでなければなりませんprivate static


7
@mucaho:オブジェクトのシリアル化などの操作を行ったときに保存されるオブジェクトの一部のように、永続状態に関するものtransientであるため、これとは何の関係もありません。たとえば、ArrayListをシリアル化するときは、空き領域ではなく、実際のArrayList要素を保持するバッキングストアの部分のみを保存する必要があるため、ArrayListのバッキングストアはArrayListの状態にとって非常に重要ですが、最後にさらに要素を追加するために予約されています。transienttransient
user2357112は、Monicaを

4
答えに追加する- var1いくつかのメソッドに必要であるが、状態の一部ではMyClassない場合、var1使用する別のクラスにそれらのメソッドを配置する時間になるかもしれませんMyClass
マイクパートリッジ

5
@CandiedOrangeここで正しいモデリングについて話しています。「オブジェクトの状態の一部」と言うとき、コード内の文字通りの部分については話していません。状態の概念的な概念、つまり概念を適切にモデル化するためにオブジェクトの状態はどうあるべきかについて議論しています。「有用な寿命」は、おそらくその最大の決定要因です。
jpmc26

4
@CandiedOrange NextおよびPreviousは、明らかにパブリックメソッドです。var1の値がプライベートメソッドのシーケンス間でのみ関連する場合、それは明らかにオブジェクトの永続状態の一部ではないため、引数として渡す必要があります。
Taemyr

2
@CandiedOrange 2つのコメントの後、あなたの意図を明確にしました。私はあなたが最初の返事で簡単に持つことができると言っています。また、はい、オブジェクトだけではないことを忘れていました。プライベートインスタンスメソッドには目的がある場合があることに同意します。私は何も思い付くことができませんでした。編集:私はあなたが実際に私に返信する最初の人ではないことに気付いた。私の悪い。次の回答が実際に物事を説明している間、私はなぜある回答が簡潔で、一文で、無回答だったのかについて混乱していた。
ファンドモニカの訴訟

17

どちらがより一般的かはわかりませんが、常に後者を使用します。データフローとライフタイムをより明確に伝え、初期化中にのみ関連するライフタイムを持つフィールドでクラスのすべてのインスタンスを肥大化することはありません。前者は紛らわしいだけで、コードのレビューが非常に困難になると思います。なぜなら、どのメソッドも変更される可能性を考慮する必要があるからvar1です。


7

範囲を縮小する必要があります変数をあります(そして合理的です)。メソッドだけでなく、一般的に。

つまり、変数がオブジェクトの状態の一部であるかどうかによって異なります。はいの場合、そのスコープ、つまりオブジェクト全体で使用しても問題ありません。この場合、最初のオプションを使用します。いいえの場合、2番目のオプションを選択すると、変数の可視性が低下し、全体的な複雑さが軽減されます。


5

Javaの優れたスタイル(インスタンス変数と戻り値)

別のスタイルがあります-コンテキスト/状態を使用します。

public static class MyClass {
    // Hold my state from one call to the next.
    public static final class State {
        int var1;
    }

    MyClass() {
        State state = new State();
        doSomething(state);
        doSomethingElse(state);
        doMoreStuff(state);
    }

    private void doSomething(State state) {
        state.var1 = 2;
    }

    private void doSomethingElse(State state) {
        int var2 = state.var1 + 1;
    }

    private void doMoreStuff(State state) {
        int var3 = state.var1 - 1;
    }
}

このアプローチには多くの利点があります。状態オブジェクトは、たとえば、オブジェクトとは独立して変更でき、将来のために多くのゆらぎを与えます。

これは、コール間で一部の詳細を保持する必要がある分散/サーバーシステムでもうまく機能するパターンです。ユーザーの詳細、データベース接続などをstateオブジェクトに保存できます。


3

それは副作用についてです。

var1状態の一部であるかどうかを尋ねることは、この質問のポイントを見逃します。var1永続化する必要がある場合は、インスタンスでなければなりません。永続性が必要かどうかにかかわらず、どちらのアプローチも機能するようにできます。

副作用アプローチ

一部のインスタンス変数は、呼び出しから呼び出しへのプライベートメソッド間の通信にのみ使用されます。この種のインスタンス変数は、存在しないようにリファクタリングできますが、そうする必要はありません。時には物事がより明確になります。しかし、これにはリスクがないわけではありません。

変数は2つの異なるプライベートスコープで使用されるため、そのスコープから変数を解放しています。配置するスコープで必要なわけではありません。これはわかりにくいかもしれません。「グローバルは悪です!」混乱のレベル。これは機能しますが、うまくスケールしません。それは小さなでのみ動作します。大きなオブジェクトはありません。長い継承チェーンはありません。ヨーヨー効果を引き起こさないでください。

機能的アプローチ

現在、var1何も持続する必要がない場合でも、パブリックコール間で保持する状態に到達する前にすべての一時的な値を取得する必要がある場合は、使用する必要があります。つまり、まだ設定できますvar1、より機能的なメソッドを使用するだけでインスタンスをます。

状態の一部であるかどうかにかかわらず、どちらのアプローチも使用できます。

これらの例では、 'var1'がカプセル化されているため、デバッガーはそれが存在することを認識しています。あなたは私たちを偏らせたくないので、あなたはそれを意図的にやったと思います。幸いなことに、私はどちらを気にしません。

副作用のリスク

とはいえ、あなたの質問はどこから来たのか知っています。私は悲惨なヨーヨーの下で働いてきました、複数のメソッドの複数のレベルでインスタンス変数を突然変異させる継承のており、それを追いかけようとしてきました。これがリスクです。

これが私をより機能的なアプローチへと駆り立てる痛みです。メソッドは、その依存関係と出力をその署名に記録できます。これは強力で明確なアプローチです。また、プライベートメソッドに渡すものを変更して、クラス内でより再利用できるようにすることもできます。

副作用の利点

制限もあります。純粋な関数には副作用はありません。それは良いことですが、オブジェクト指向ではありません。オブジェクト指向の大部分は、メソッド外のコンテキストを参照する機能です。ここでグローバルを漏らさずにそれを行うことは、OOPの強みです。グローバルの柔軟性は得られますが、クラスにうまく含まれています。必要に応じて、1つのメソッドを呼び出して、すべてのインスタンス変数を一度に変更できます。その場合、少なくともメソッドに名前を付けて、それが何であるかを明確にする名前を付ける必要があります。そうすれば、人々は驚かないでしょう。コメントも役立ちます。これらのコメントは「事後条件」として形式化される場合があります。

機能的なプライベートメソッドの欠点

機能的アプローチにより依存関係が明確になります。純粋な関数型言語でない限り、隠された依存関係を排除することはできません。メソッドのシグネチャだけを見ても、それがコードの残りの部分で副作用を隠していないことはわかりません。あなただけではありません。

ポスト条件

あなた、そしてチームの他の全員が、副作用(事前/事後条件)をコメントで確実に文書化すれば、機能的アプローチから得られる利益ははるかに少なくなります。ええ、私は夢を見ます。

結論

個人的には、可能であればどちらの場合でも機能的なプライベートメソッドを使用する傾向がありますが、正直なところ、それらの前後の条件付き副作用コメントは、古くなった場合やメソッドが間違った方法で呼び出された場合にコンパイラエラーを引き起こさないためです。副作用の柔軟性を本当に必要としない限り、物事が機能することを知りたいだけです。


1
「いくつかのインスタンス変数は、呼び出しから呼び出しへのプライベートメソッド間の通信にのみ使用されます。」それはコードの匂いです。インスタンス変数がこのように使用される場合、それはクラスが大きすぎることを示しています。その変数とメソッドを新しいクラスに抽出します。
ケビンクライン

2
これは本当に意味がありません。OP は機能的なパラダイムでコードを記述できますが(これは一般に良いことです)、それは明らかに質問のコンテキストではありません。パラダイムを変更することで、彼らは、オブジェクトの状態を保存しないようにできることをOPに伝えるためにしようとすると...本当に関係ありません
jpmc26

@kevincline OPの例には、1つのインスタンス変数と3つのメソッドしかありません。基本的に、すでに新しいクラスに抽出されています。この例が有用なことを行うわけではありません。クラスのサイズは、プライベートヘルパーメソッドが互いに通信する方法とは関係ありません。
MagicWindow

@ jpmc26 OPはvar1、パラダイムを変更することで、状態変数としての保存を回避できることをすでに示しています。クラスのスコープは、状態が保存される場所だけではありません。また、囲みスコープでもあります。これは、クラスレベルで変数を配置する2つの可能な動機があることを意味します。包囲する範囲に対してのみそれを行うと主張することができ、状態は悪ではありませんが、私はそれがまた悪であるアリティとのトレードオフだと言います。これを知っているのは、これを行うコードを維持しなければならなかったからです。いくつかはうまくやった。いくつかは悪夢でした。2つの間の線は状態ではありません。読みやすさです。
MagicWindow

状態ルールは読みやすさを向上させます:1)操作の中間結果が状態のように見えないようにする2)メソッドの依存関係を隠すことを避けます。メソッドのアリティが高い場合、それはそのメソッドの複雑さを還元不可能かつ真に表すか、現在の設計に不必要な複雑さがあります。
-sdenham

1

最初のバリアントは直感的ではなく、私にとって潜在的に危険に見えます(何らかの理由で誰かがプライベートメソッドをパブリックにすることを想像してください)。

クラス構築時に変数をインスタンス化するか、引数として渡します。後者は、機能的なイディオムを使用するオプションを提供し、包含オブジェクトの状態に依存しません。


0

オブジェクトの状態、および2番目の方法が優先される場合についての回答が既にあります。最初のパターンの一般的なユースケースを1つだけ追加します。

最初のパターンは、クラスが行うことのすべてがアルゴリズムをカプセル化することである場合に完全に有効です。この使用例の1つは、アルゴリズムを単一のメソッドに書き込んだ場合、大きすぎることです。したがって、それをより小さなメソッドに分解してクラスにし、サブメソッドをプライベートにします。

パラメータを介してアルゴリズムのすべての状態を渡すのは面倒になる可能性があるため、プライベートフィールドを使用します。また、基本的にインスタンスの状態であるため、最初の段落の規則とも一致しています。プライベートフィールドを使用する場合、アルゴリズムがリエントラントではないことを念頭に置いて適切に文書化するだけです。これはほとんどの場合問題ではありませんが、噛みつく可能性があります。


0

何かをする例を試してみましょう。これはjavaではなくjavascriptなので、許してください。ポイントは同じでなければなりません。

訪問https://blockly-games.appspot.com/pond-duck?lang=enを、JavaScriptのタブをクリックし、これを貼り付けます。

hunt = lock(-90,1), 
heading = -135;

while(true) {
  hunt()  
  heading += 2
  swim(heading,30)
}

function lock(direction, width) {
  var dir = direction
  var wid = width
  var dis = 10000

  //hunt
  return function() {
    //randomize() //Calling this here makes the state of dir meaningless
    scanLock()
    adjustWid()
    if (isSpotted()) {
      if (inRange()) {
        if (wid <= 4) {
          cannon(dir, dis)
        }
      }
    } else {
      if (!left()) {
        right()
      }
    }
  }

  function scanLock() {
    dis = scan(dir, wid);
  }

  function adjustWid() {
    if (inRange()) {
      if (wid > 1)
        wid /= 2;
    } else {
      if (wid < 16) {
        wid *= 2; 
      }
    }
  }

  function isSpotted() {
    return dis < 1000;
  }

  function left() {
    dir += wid;
    scanLock();
    return isSpotted();
  }

  function right() {
    dir -= wid*2;
    scanLock();
    return isSpotted()
  }

  function inRange() {
    return dis < 70;
  }

  function randomize() {
    dir = Math.random() * 360
  }
}

あなたはそれに気づくはずですdirwiddisたくさんの周りに渡され取得されていません。また、lock()返す関数内のコードは擬似コードによく似ていることに注意してください。それは実際のコードです。しかし、それは非常に読みやすいです。パスと割り当てを追加することもできますが、擬似コードでは決して見られない混乱が追加されます。

これらの3つの変数は永続状態であるため、割り当てと受け渡しを行わなくてもよいと主張したい場合はdir、ループごとにランダムな値を割り当てる再設計を検討してください。今は永続的ではありませんか?

確かに、今ではdirスコープ内にドロップダウンできますが、受け渡しと設定で擬似コードを乱雑にすることなく強制することはできません。

そのため、状態は、パスやリターンではなく、副作用を使用するかどうかを決定する理由ではありません。副作用だけで、コードが読めなくなるわけでもありません。純粋に機能的なコードの利点は得られません。しかし、よくできて、良い名前で、彼らは実際に読むのに素晴らしいことができます。

それは彼らが悪夢のようなスパゲッティになれないということではありません。しかし、それでは何ができないのでしょうか?

幸せなカモ狩り。


1
内部/外部関数は、OPが記述するものとは異なるパラダイムです。-内部関数に引数を渡した外部関数のローカル変数間のトレードオフは、引数をプライベート関数に渡したクラスのプライベート変数に似ていることに同意します。ただし、この比較を行うと、オブジェクトの有効期間は関数の1回の実行に対応するため、外部関数の変数は永続状態の一部になります。
Taemyr

@Taemyr OPは、パブリックコンストラクター内で呼び出されるプライベートメソッドを記述します。関数を入力するたびに状態を踏んだ場合、状態は実際には「永続的」ではありません。状態は、共有場所のメソッドが読み書きできるように使用されることがあります。永続化する必要があるわけではありません。とにかく永続化されているという事実は、依存するものではありません。
candied_orange

1
オブジェクトを破棄するたびに踏んでも永続的です。
Taemyr

1
良い点ですが、JavaScriptでそれらを表現することは議論を本当に難読化します。また、JS構文を削除すると、コンテキストオブジェクトが渡されることになります(外部関数のクロージャー)
fdreger

1
@sdenhamクラスの属性と操作の一時的な中間結果には違いがあると主張しています。それらは同等ではありません。ただし、一方を他方に保存することもできます。必ずしもそうである必要はありません。問題は、それが必要な場合です。はい、セマンティックおよびライフタイムの問題があります。アリティの問題もあります。あなたはそれらが十分に重要だとは思わない。それはいいです。しかし、オブジェクト状態以外にクラスレベルを使用することは正しくないということは、モデリングの1つの方法を主張しています。私はそれを他の方法で行うプロフェッショナルなコードを維持しました。
candied_orange
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.