web-dev-qa-db-ja.com

「変数は可能な限り最小のスコープで生きるべきである」は「変数は可能な限り存在すべきではない」というケースを含みますか

インスタンス変数よりもローカル変数を優先する理由は?」で受け入れられた回答によると、変数は最小のスコープ内に存在する必要があります可能。
問題を私の解釈に単純化します。これは、この種のコードをリファクタリングする必要があることを意味します。

_public class Main {
    private A a;
    private B b;

    public ABResult getResult() {
        getA();
        getB();
        return ABFactory.mix(a, b);
    }

    private getA() {
      a = SomeFactory.getA();
    }

    private getB() {
      b = SomeFactory.getB();
    }
}
_

このようなものに:

_public class Main {
    public ABResult getResult() {
        A a = getA();
        B b = getB();
        return ABFactory.mix(a, b);
    }

    private getA() {
      return SomeFactory.getA();
    }

    private getB() {
      return SomeFactory.getB();
    }
}
_

しかし、「変数は可能な限り最小のスコープで生きるべき」の「精神」によれば、「変数を持たない」のスコープは「変数を持っている」よりも小さいのではないですか?したがって、上記のバージョンはリファクタリングする必要があると思います。

_public class Main {
    public ABResult getResult() {
        return ABFactory.mix(getA(), getB());
    }

    private getA() {
      return SomeFactory.getA();
    }

    private getB() {
      return SomeFactory.getB();
    }
}
_

そのため、getResult()にはローカル変数がまったくありません。本当?

32
ocomfd

いいえ。理由はいくつかあります。

  1. 意味のある名前の変数を使用すると、コードを理解しやすくなります。
  2. 複雑な数式を小さなステップに分割すると、コードが読みやすくなります。
  3. キャッシング。
  4. オブジェクトへの参照を保持して、オブジェクトを複数回使用できるようにする。

等々。

110
Robert Harvey

同意する必要はありませんが、コードの可読性を向上させない変数は避けてください。コードの特定の時点でスコープ内にある変数が多いほど、コードを理解するのが複雑になります。

あなたの例では、変数abの利点を実際には見ないので、変数なしのバージョンを記述します。一方で、そもそも機能がシンプルなのであまり重要ではないと思います。

関数の取得時間が長くなり、スコープ内の変数が増えるほど、問題が大きくなります。

たとえば、

    a=getA();
    b=getB();
    m = ABFactory.mix(a,b);

より大きな関数の上に、1つではなく3つの変数を導入することで、残りのコードを理解する精神的な負担を増やします。 aまたはbが再び使用されるかどうかを確認するには、残りのコードを読む必要があります。必要範囲よりも長い範囲にあるローカルは、全体的な可読性に悪影響を及ぼします。

もちろん、変数が必要な場合(一時的な結果を格納する場合など)または変数doesがコードの可読性を向上させる場合は、保持する必要があります。

16
JacquesB

他の答えに加えて、私は何か他のことを指摘したいと思います。変数のスコープを小さく保つことの利点は、構文的に変数にアクセスできるコードの量を減らすだけでなく、変数を変更する可能性のある制御フローパスの数を減らすことです(新しい値を割り当てるか、呼び出します)。変数に保持されている既存のオブジェクトの変更メソッド)。

クラススコープの変数(インスタンスまたは静的)は、ローカルスコープの変数よりもはるかに多くの可能性がある制御フローパスを持っています。これは、メソッドによって変更でき、任意の順序で、何度でも、多くの場合、クラス外のコードによって呼び出すことができるためです。 。

最初のgetResultメソッドを見てみましょう:

_public ABResult getResult() {
    getA();
    getB();
    return ABFactory.mix(this.a, this.b);
}
_

これで、getAおよびgetBという名前は、それらが_this.a_および_this.b_に割り当てることができるsuggestかもしれません。 getResultを見ただけではわからないしたがって、mixメソッドに渡された_this.a_および_this.b_の値が、thisより前のgetResultオブジェクトの状態に由来する可能性があります。メソッドが呼び出される方法とタイミングをクライアントが制御するため、予測することは不可能です。

ローカルのaおよびb変数を含む改訂されたコードでは、各変数の割り当てからその使用への(例外のない)制御フローが正確に1つあることは明らかです。変数は、使用される直前に宣言されます。

したがって、(変更可能な)変数をクラススコープからローカルスコープに移動すること(および(変更可能な)変数をループの外側から内側に移動すること)には、制御フローの推論を簡略化するという大きな利点があります。

一方、最後の例のように変数を削除しても、制御フローの推論には実際には影響しないため、あまりメリットがありません。また、値に付けられた名前も失われます。これは、変数を内部スコープに移動するだけでは起こりません。これは考慮しなければならないトレードオフです。そのため、変数を削除する方が良い場合もあれば、悪い場合もあります。

変数名を失いたくないが、変数のスコープを減らしたい場合(より大きな関数内で使用されている場合)、変数とその使用法を ブロックステートメント (または それらを独自の関数に移動する )。

11
YawarRaza7349

これは多少言語依存ですが、関数型プログラミングのあまり明白でない利点の1つは、プログラマーやコードの読者がこれらを必要としないように促すことです。考慮してください:

(reduce (fn [map string] (assoc map string (inc (map string 0))) 

または、いくつかのLINQ:

var query2 = mydb.MyEntity.Select(x => x.SomeProp).AsEnumerable().Where(x => x == "Prop");

または Node.js:

 return visionFetchLabels(storageUri)
    .then(any(isCat))
    .then(notifySlack(secrets.slackWebhook, `A cat was posted: ${storageUri}`))
    .then(logSuccess, logError)
    .then(callback)

最後は、中間変数なしで、前の関数の結果に対して関数を呼び出すチェーンです。それらを導入すると、それははるかに不明確になります。

ただし、最初の例と他の2つの例の違いは、暗黙の操作順序です。これは、実際に計算された順序と同じではない場合がありますが、読者が考えなければならない順序です。次の2つは左から右です。 LISP/Clojureの例では、右から左のようになります。言語の「デフォルトの方向」ではないコードの記述には少し注意する必要があり、2つを混合する「ミドルアウト」式は絶対に避けてください。

F#のパイプ演算子|>は、通常は右から左でなければならないような左から右への記述を可能にするため、便利です。

2
pjc50

「可能な最小のスコープ」を「既存のスコープまたは追加するのに妥当なスコープの中から」と読む必要があるので、私はノーと言います。それ以外の場合は、人工スコープを作成する必要があることを意味します(例:無償の{} Cのような言語のブロック)変数のスコープが最後の意図された使用を超えないことを保証するためだけであり、スコープが独立して存在する正当な理由がない限り、それは一般に難読化/乱雑として嫌われます。

functionsmethods)を検討してください。そこでは、可能な限り最小のサブタスクでコードを分割することも、最大のシングルトンコードを分割することもありません。

これは、論理的なタスクを消費可能な部分に区切ることによるシフト制限です。

同じことが変数にも当てはまります。論理データ構造をわかりやすい部分に示す。または、単にパラメータに名前を付ける(ステータスを付ける)だけです。

boolean automatic = true;
importFile(file, automatic);

しかし、もちろん最初に宣言があり、さらに200行目で最初の使用法が悪いスタイルとして受け入れられています。これは明らかに"変数は可能な限り最小のスコープ内に存在する必要がある"が言うつもりです。非常に近い"変数を再利用しないでください。"のように

1
Joop Eggen

NOの理由としていくらか欠けているのは、デバッグ/読み取り可能性です。コードはそのために最適化する必要があります。明確で簡潔な名前は、たとえば、 3つの方法を想像してみてください

if (frobnicate(x) && (get_age(x) > 2000 || calculate_duration(x) < 100 )

この行は短いですが、すでに読みにくいです。さらにいくつかのパラメーターを追加します。これは、複数行にわたる場合です。

can_be_frobnicated = frobnicate(x)
is_short_lived_or_ancient = get_age(x) > 2000 || calculate_duration(x) < 100
if (can_be_frobnicated || is_short_lived_or_ancient )

この方法の方が読みやすく、意味を伝えやすいので、中間変数に問題はありません。

別の例は、Rのような言語で、最後の行は自動的に戻り値になります。

some_func <- function(x) {
    compute(x)
}

これは危険です、返品は延長されますか、それとも必要ですか?これはより明確です:

some_func <- function(x) {
   rv <- compute(x)
   return(rv)
}

いつものように、これは判断の呼びかけです。読み取りを改善しない場合は中間変数を排除し、そうでない場合は保持または導入します。

もう1つのポイントはデバッグ可能性です。中間結果に関心がある場合は、上記のRの例のように、単に中間を導入するのが最善です。これがどのくらいの頻度で要求されるかは想像するのが難しく、チェックインするものに注意する-デバッグ変数が多すぎて混乱します-繰り返しになりますが、判断の呼び出しです。

1
Christian Sauer

タイトルだけを参照:絶対に、変数が不要な場合は削除する必要があります。

ただし、「不要」とは、変数を使用せずに同等のプログラムを作成できることを意味するものではありません。それ以外の場合は、すべてをバイナリで書き込む必要があると言われます。

最も一般的な種類の不要な変数は未使用の変数であり、変数のスコープが小さいほど、不要であると判断しやすくなります。中間の変数が不要であるかどうかを判断することは困難です。これは、バイナリの状況ではないため、状況に応じたものです。実際、2つの異なる方法で同じソースコードを使用すると、周囲のコードで問題を修正した過去の経験に応じて、同じユーザーが異なる回答を生成する可能性があります。

サンプルコードが正確に表現されている場合は、2つのプライベートメソッドを削除することをお勧めしますが、ファクトリーコールの結果をローカル変数に保存したのか、単にそれらをミックスの引数として使用したのかについては、ほとんど心配する必要はありません。方法。

コードの可読性は、正常に動作することを除いてすべてに優先します(「可能な限り高速」であることはめったにない、許容可能なパフォーマンス基準が正しく含まれています)。

0
jmoreno