私は高校で最初のプログラミングクラスにいます。私たちは、最初の学期のプロジェクトの終わりをやっています。このプロジェクトには1つのクラスのみが含まれますが、多くのメソッドが含まれます。私の質問は、インスタンス変数とローカル変数を使用したベストプラクティスです。ほとんどインスタンス変数のみを使用してコーディングする方がはるかに簡単なようです。しかし、これが私がそれを行うべき方法であるかどうか、またはローカル変数をもっと使用する必要があるかどうかはわかりません(メソッドにローカル変数の値をもっと多く取り込む必要があります)。
これについての私の理由は、メソッドに2つまたは3つの値を返させたい場合が多いためですが、もちろんこれは不可能です。したがって、インスタンス変数を使用する方が簡単で、クラス内で普遍的であるため、心配する必要はありません。
誰もこれについて話し合っているのを見たことがないので、もっと考えてみようと思います。簡単な答え/アドバイスは、値を返す方が簡単だと思うという理由だけで、ローカル変数よりもインスタンス変数を使用しないことです。ローカル変数とインスタンス変数を適切に使用しないと、コードの操作が非常に困難になります。あなたは本当に追跡するのが難しいいくつかの深刻なバグを生み出すでしょう。深刻なバグが何を意味するのか、そしてそれがどのように見えるのかを理解したい場合は、読んでください。
関数への書き込みを提案するときに、インスタンス変数のみを使用してみましょう。非常に単純なクラスを作成します。
public class BadIdea {
public Enum Color { GREEN, RED, BLUE, PURPLE };
public Color[] map = new Colors[] {
Color.GREEN,
Color.GREEN,
Color.RED,
Color.BLUE,
Color.PURPLE,
Color.RED,
Color.PURPLE };
List<Integer> indexes = new ArrayList<Integer>();
public int counter = 0;
public int index = 0;
public void findColor( Color value ) {
indexes.clear();
for( index = 0; index < map.length; index++ ) {
if( map[index] == value ) {
indexes.add( index );
counter++;
}
}
}
public void findOppositeColors( Color value ) {
indexes.clear();
for( index = 0; i < index < map.length; index++ ) {
if( map[index] != value ) {
indexes.add( index );
counter++;
}
}
}
}
これは私が知っているばかげたプログラムですが、このようなことにインスタンス変数を使用することは非常に悪い考えであるという概念を説明するために使用できます。あなたが見つける最大のことは、それらのメソッドが私たちが持っているすべてのインスタンス変数を使用するということです。また、インデックス、カウンター、およびインデックスが呼び出されるたびに変更されます。最初に気付く問題は、これらのメソッドを次々に呼び出すと、以前の実行からの回答が変更される可能性があることです。したがって、たとえば、次のコードを記述した場合:
BadIdea idea = new BadIdea();
idea.findColor( Color.RED );
idea.findColor( Color.GREEN ); // whoops we just lost the results from finding all Color.RED
FindColorはインスタンス変数を使用して戻り値を追跡するため、一度に返すことができる結果は1つだけです。もう一度呼び出す前に、これらの結果への参照を保存してみましょう。
BadIdea idea = new BadIdea();
idea.findColor( Color.RED );
List<Integer> redPositions = idea.indexes;
int redCount = idea.counter;
idea.findColor( Color.GREEN ); // this causes red positions to be lost! (i.e. idea.indexes.clear()
List<Integer> greenPositions = idea.indexes;
int greenCount = idea.counter;
この2番目の例では、3行目の赤い位置を保存しましたが、同じことが起こりました!?なぜそれらを失ったのですか?!割り当てられた代わりにidea.indexesがクリアされたため、一度に使用できる回答は1つだけです。再度呼び出す前に、その結果の使用を完全に終了する必要があります。メソッドを再度呼び出すと、結果がクリアされ、すべてが失われます。これを修正するには、赤と緑の回答が分かれるように、毎回新しい結果を割り当てる必要があります。それでは、答えを複製して、新しいコピーを作成しましょう。
BadIdea idea = new BadIdea();
idea.findColor( Color.RED );
List<Integer> redPositions = idea.indexes.clone();
int redCount = idea.counter;
idea.findColor( Color.GREEN );
List<Integer> greenPositions = idea.indexes.clone();
int greenCount = idea.counter;
最後に、2つの別々の結果があります。赤と緑の結果が分離されました。しかし、プログラムが機能する前に、BadIdeaが内部でどのように動作するかについて多くのことを知る必要がありましたね。結果が乱雑にならないように安全に確認するために、呼び出すたびに返品のクローンを作成することを忘れないでください。発信者がこれらの詳細を覚えなければならないのはなぜですか?そうする必要がなかったらもっと簡単ではないでしょうか?
また、呼び出し元は結果を記憶するためにローカル変数を使用する必要があるため、BadIdeaのメソッドでローカル変数を使用しなかった場合、呼び出し元は結果を記憶するためにローカル変数を使用する必要があることに注意してください。それで、あなたは本当に何を成し遂げましたか?あなたは本当に問題を発信者に移し、彼らにもっと多くのことをさせました。また、ルールには多くの例外があるため、発信者にプッシュした作業は簡単なルールではありません。
それでは、2つの異なる方法でそれを試してみましょう。私がいかに「賢く」なっているかに注目してください。同じインスタンス変数を再利用して「メモリを節約」し、コードをコンパクトに保ちました。 ;-)
BadIdea idea = new BadIdea();
idea.findColor( Color.RED );
List<Integer> redPositions = idea.indexes;
int redCount = idea.counter;
idea.findOppositeColors( Color.RED ); // this causes red positions to be lost again!!
List<Integer> greenPositions = idea.indexes;
int greenCount = idea.counter;
同じことが起こりました!くそー、でも私はとても「賢く」てメモリを節約していて、コードはより少ないリソースを使用していました!!!これは、メソッドの呼び出しが順序に依存するように、インスタンス変数を使用することの本当の危険です。メソッド呼び出しの順序を変更すると、BadIdeaの基本的な状態を実際に変更していなくても、結果は異なります。地図の内容は変更していません。メソッドを異なる順序で呼び出すと、プログラムが異なる結果を生成するのはなぜですか?
idea.findColor( Color.RED )
idea.findOppositeColors( Color.RED )
これらの2つの方法を交換した場合とは異なる結果が生成されます。
idea.findOppositeColors( Color.RED )
idea.findColor( Color.RED )
これらのタイプのエラーは、特にそれらの線が互いに隣接していない場合、追跡するのが非常に困難です。これらの2行の間のどこかに新しい呼び出しを追加するだけで、プログラムを完全に中断して、大きく異なる結果を得ることができます。確かに、少数の行を処理している場合は、エラーを簡単に見つけることができます。ただし、大規模なプログラムでは、プログラム内のデータが変更されていなくても、それらを再現しようとして数日を無駄にする可能性があります。
そして、これはシングルスレッドの問題のみを調べます。 BadIdeaがマルチスレッドの状況で使用されていた場合、エラーは非常に奇妙になる可能性があります。 findColors()とfindOppositeColors()が同時に呼び出された場合はどうなりますか?クラッシュ、髪の毛がすべて抜け落ち、死、空間、時間が特異点に崩壊し、宇宙が飲み込まれますか?おそらくそれらのうちの少なくとも2つ。スレッドはおそらく今あなたの頭上にありますが、うまくいけば、私たちはあなたが今悪いことをするのを避けることができるので、あなたがスレッドに到達したとき、それらの悪い習慣はあなたに本当の心痛を引き起こしません。
メソッドを呼び出すときにどれほど注意しなければならないかに気づきましたか?彼らはお互いを上書きし、おそらくランダムにメモリを共有しました。外部で機能させるために内部でどのように機能したかを詳細に覚えておく必要がありました。呼び出される順序を変更すると、次の行で非常に大きな変更が発生します。そしてそれはシングルスレッドの状況でしか機能しませんでした。このようなことを行うと、触れるたびにバラバラに見える非常に壊れやすいコードが生成されます。私が示したこれらのプラクティスは、コードが脆弱であることに直接貢献しました。
これはカプセル化のように見えるかもしれませんが、どのように記述したかの技術的な詳細は呼び出し元に知られている必要がありますであるため、正反対です。呼び出し元は、コードを機能させるために非常に特殊な方法でコードを作成する必要があり、コードの技術的な詳細を知らなければそれを行うことはできません。これはしばしばLeaky Abstractionと呼ばれます。これは、クラスが技術的な詳細を抽象化/インターフェースの背後に隠すことを想定しているためですが、技術的な詳細が漏れ出し、呼び出し元に動作の変更を強制します。すべてのソリューションにはある程度のリークがありますが、これらのような上記の手法のいずれかを使用すると、どのような問題を解決しようとしても、それらを適用するとひどくリークします。それでは、GoodIdeaを見てみましょう。
ローカル変数を使用して書き直してみましょう。
public class GoodIdea {
...
public List<Integer> findColor( Color value ) {
List<Integer> results = new ArrayList<Integer>();
for( int i = 0; i < map.length; i++ ) {
if( map[index] == value ) {
results.add( i );
}
}
return results;
}
public List<Integer> findOppositeColors( Color value ) {
List<Integer> results = new ArrayList<Integer>();
for( int i = 0; i < map.length; i++ ) {
if( map[index] != value ) {
results.add( i );
}
}
return results;
}
}
これにより、上記で説明したすべての問題が修正されます。カウンターを追跡したり返したりしていないことはわかっていますが、追跡している場合は、新しいクラスを作成して、Listの代わりにそれを返すことができます。次のオブジェクトを使用して、複数の結果をすばやく返すことがあります。
public class Pair<K,T> {
public K first;
public T second;
public Pair( K first, T second ) {
this.first = first;
this.second = second;
}
}
長い答えですが、非常に重要なトピックです。
クラスのコアコンセプトである場合は、インスタンス変数を使用します。反復、再帰、または何らかの処理を行う場合は、ローカル変数を使用します。
同じ場所で2つ(またはそれ以上)の変数を使用する必要がある場合は、それらの属性(およびそれらを設定する適切な手段)を使用して新しいクラスを作成するときが来ました。これにより、コードがよりクリーンになり、問題について考えるのに役立ちます(各クラスは語彙の新しい用語です)。
コアコンセプトである場合、1つの変数をクラスにすることができます。たとえば、実際の識別子:これらは文字列として表すことができますが、多くの場合、それらを独自のオブジェクトにカプセル化すると、突然「引き付け」機能(検証、他のオブジェクトへの関連付けなど)が開始されます。
また、(完全に関連しているわけではありませんが)オブジェクトの一貫性もあります。オブジェクトは、その状態が理にかなっていることを確認できます。あるプロパティを設定すると、別のプロパティが変更される場合があります。また、後でスレッドセーフになるようにプログラムを変更するのもはるかに簡単になります(必要な場合)。
短編小説:変数に複数のメソッド(またはクラスの外部)からアクセスする必要がある場合にのみ、インスタンス変数として作成します。ローカルでのみ必要な場合は、単一のメソッドで、ローカル変数である必要があります。インスタンス変数は、ローカル変数よりもコストがかかります。
注意:インスタンス変数はデフォルト値に初期化されますが、ローカル変数は初期化されません。
各変数のスコープを可能な限り小さくしたいので、メソッドの内部のローカル変数が常に優先されます。ただし、複数のメソッドが変数にアクセスする必要がある場合は、インスタンス変数である必要があります。
ローカル変数は、結果に到達したり、その場で何かを計算したりするために使用される中間値に似ています。インスタンス変数は、年齢や名前など、クラスの属性に似ています。
スコープをできるだけ狭くする変数を宣言します。最初にローカル変数を宣言します。これだけでは不十分な場合は、インスタンス変数を使用してください。これが十分でない場合は、クラス(静的)変数を使用してください。
配列やオブジェクトなどの複合構造を返す複数の値を返す必要があります。
簡単な方法:変数を複数のメソッドで共有する必要がある場合は、インスタンス変数を使用します。それ以外の場合は、ローカル変数を使用します。
ただし、できるだけ多くのローカル変数を使用することをお勧めします。どうして?クラスが1つしかない単純なプロジェクトの場合、違いはありません。多くのクラスを含むプロジェクトの場合、大きな違いがあります。インスタンス変数は、クラスの状態を示します。クラス内のインスタンス変数が多いほど、このクラスが持つことができる状態が多くなり、このクラスが複雑になるほど、クラスの維持が困難になるか、プロジェクトでエラーが発生しやすくなります。したがって、クラスの状態をできるだけ単純に保つために、できるだけ多くのローカル変数を使用することをお勧めします。
オブジェクトの観点から問題について考えてみてください。各クラスは、異なるタイプのオブジェクトを表します。インスタンス変数は、クラスがそれ自体または他のオブジェクトと連携するために覚えておく必要のあるデータです。ローカル変数は、中間計算として使用する必要があります。データは、メソッドを終了した後で保存する必要はありません。
そもそも、メソッドから複数の値を返さないようにしてください。できない場合、場合によっては本当にできない場合は、それをクラスにカプセル化することをお勧めします。最後のケースでは、クラス内の別の変数(インスタンス変数)を変更することをお勧めします。インスタンス変数アプローチの問題は、副作用が増えることです。たとえば、プログラムでメソッドAを呼び出し、いくつかのインスタンス変数を変更します。時間の経過とともに、コードの複雑さが増し、メンテナンスがますます難しくなります。
インスタンス変数を使用する必要がある場合は、クラスコンストラクターでfinalを作成してから初期化するようにするため、副作用が最小限に抑えられます。このプログラミングスタイル(アプリケーションの状態変化を最小限に抑える)は、保守が容易なより優れたコードにつながるはずです。
通常、変数のスコープは最小限にする必要があります。
残念ながら、変数スコープが最小化されたクラスを構築するには、多くの場合、多くのメソッドパラメーターの受け渡しを行う必要があります。
しかし、常にそのアドバイスに従い、可変スコープを完全に最小化すると、メソッドに出入りする必要なすべてのオブジェクトで、多くの冗長性とメソッドの柔軟性が失われる可能性があります。
次のような何千ものメソッドを使用してコードベースを想像してください。
private ClassThatHoldsReturnInfo foo(OneReallyBigClassThatHoldsCertainThings big,
AnotherClassThatDoesLittle little) {
LocalClassObjectJustUsedHere here;
...
}
private ClassThatHoldsReturnInfo bar(OneMediumSizedClassThatHoldsCertainThings medium,
AnotherClassThatDoesLittle little) {
...
}
一方、次のようなインスタンス変数が多数あるコードベースを想像してみてください。
private OneReallyBigClassThatHoldsCertainThings big;
private OneMediumSizedClassThatHoldsCertainThings medium;
private AnotherClassThatDoesLittle little;
private ClassThatHoldsReturnInfo ret;
private void foo() {
LocalClassObjectJustUsedHere here;
....
}
private void bar() {
....
}
コードが増えると、最初の方法で変数スコープを最小限に抑えることができますが、多くのメソッドパラメーターが簡単に渡される可能性があります。通常、コードはより冗長になり、これらすべてのメソッドをリファクタリングするため、複雑になる可能性があります。
より多くのインスタンス変数を使用すると、渡される多くのメソッドパラメータの複雑さを軽減でき、明確にするためにメソッドを頻繁に再編成するときにメソッドに柔軟性を与えることができます。ただし、維持する必要のあるオブジェクトの状態が増えます。一般的に、前者を行い、後者を控えることをお勧めします。
ただし、非常に多くの場合、人によって異なる場合がありますが、最初のケースの何千もの余分なオブジェクト参照と比較して、状態の複雑さをより簡単に管理できます。メソッド内のビジネスロジックが増加し、順序と明確さを維持するために組織を変更する必要がある場合、これに気付くかもしれません。
それだけでなく。明確さを保ち、プロセスでメソッドパラメータを大幅に変更するようにメソッドを再編成すると、バージョン管理の差分が多くなり、安定した本番品質のコードにはあまり適していません。バランスがあります。 1つの方法は、ある種の複雑さを引き起こします。他の方法は、別の種類の複雑さを引き起こします。
自分に最適な方法を使用してください。時間の経過とともにそのバランスがわかります。
この若いプログラマーは、メンテナンスコードが少ないことに対して洞察に満ちた第一印象を持っていると思います。
ときにインスタンス変数を使用する
同様に、これらの条件のいずれも一致しない場合はローカル変数を使用します。具体的には、スタックがポップオフされた後に変数の役割が終了します。例:Comparator.compare(o1、o2);