「変数は可能な限り最小のスコープ内に存在する必要があります」には、「変数は可能であれば存在してはならない」というケースが含まれていますか?


32

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

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()ローカル変数はまったくありません。本当?


72
明示的な変数を作成すると、名前を付ける必要があるという利点があります。いくつかの変数を導入すると、不透明なメソッドがすぐに読み取り可能なメソッドに変わります。
Jared Goguen

11
正直なところ、変数名aおよびbに関する例は、ローカル変数の値を示すにはあまりにも考案されています。幸いなことに、あなたは良い答えを得ました。
ドックブラウン

3
2番目のスニペットでは、あなたの変数は、実際にはない変数。これらは変更しないため、事実上ローカル定数です。Javaコンパイラは、それらをローカルスコープ内にあり、コンパイラで何が起こるかを知っているため、それらを最終的に定義した場合、それらを同じように扱います。実際にfinalキーワードを使用するかどうかは、スタイルと意見の問題です。
ハイド

2
@JaredGoguen明示的な変数の作成には、名前を付ける必要があり、名前を付けることができるという利点があります。
ベルギ

2
絶対にゼロ「の変数が可能な最小範囲に住んでいる必要があります」のようなコードスタイルルールのは考えず普遍的に適用されています。そのため、この質問のフォームの推論に基づいてコードスタイルの決定を行うことは避けてください。単純なガイドラインを作成し、あまり明らかにされていない領域に拡張します(「可能であれば、変数のスコープを小さくする」-> 「可能であれば変数は存在すべきではない」)。結論を具体的に裏付ける正当な理由があるか、結論が不適切な延長であるかのいずれかです。いずれにせよ、元のガイドラインは必要ありません。
ベン

回答:


110

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

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

等々。


22
また、言及する価値があります:値はメモリに保存されるため、実際には同じスコープで終了します。名前を付けてください(ロバートが上記の理由で言及しているため)!
Maybe_Factor

5
@Maybe_Factorガイドラインはパフォーマンス上の理由から実際にはありません。コードを維持することがいかに簡単かということです。しかし、それは、意味のあるローカルは、名前がよく設計された関数を単に構成する場合を除き、1つの巨大な式よりも優れていることを意味します(たとえば、実行する理由はありませんvar taxIndex = getTaxIndex();)。
ルアーン

16
すべてが重要な要素です。私はそれを見る方法:簡潔であり目標。明快さは別です。堅牢性は別です。パフォーマンスは別です。等々。これらはいずれも絶対的な目標ではなく、競合することもあります。したがって、私たちの仕事は、それらの目標のバランスとる最善の方法を見つけることです。
gidds

8
コンパイラは、3&4の世話をされます
ストップハニングモニカ

9
番号4は正確さのために重要です。関数呼び出しの値が変更される可能性がある場合(たとえばnow())、変数を削除してメソッドを複数回呼び出すと、バグが発生する可能性があります。これにより、非常に微妙でデバッグが難しい状況が発生する可能性があります。当たり前のように思えるかもしれませんが、変数を削除するという盲目的なリファクタリングミッションを実行している場合、欠陥を導入することは簡単です。
ジミージェームズ

16

同意する必要はありませんし、コードの可読性を改善しない変数は避けるべきです。コード内の特定のポイントでスコープ内にある変数が多いほど、そのコードは複雑になります。

私は実際に変数の利点が表示されていないab、私は、変数なしのバージョンを記述しますので、あなたの例インチ 一方で、この機能はそもそもとても単純なので、それほど重要ではないと思います。

関数がより長く取得され、より多くの変数がスコープ内にあるほど、問題になります。

たとえば、あなたが持っている場合

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

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

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


2
もちろん、メソッドが長くなりすぎてローカルの数が多すぎて簡単に保守できない場合は、おそらくとにかく分割する必要があります。
ルアーン

4
「外来」変数の利点の1つはvar result = getResult(...); return result;、ブレークポイントを設定してreturn、正確に何であるかを学習できることresultです。
Joker_vD

5
@Joker_vD:その名前にふさわしいデバッガーは、とにかくすべてを実行できるはずです(ローカル変数に保存せずに関数呼び出しの結果を表示し、関数の終了時にブレークポイントを設定します)。
クリスチャンハックル

2
@ChristianHackl追加するのに時間がかかる可能性のある複雑なウォッチを設定せずに(および関数に副作用がある場合はすべてが壊れる可能性があります)、それを実行できるdebuggesrはほとんどありません。曜日をデバッグするためのwith variablesバージョンを使用します。
ゲイブセチャン

1
@ChristianHacklそして、デバッガーが恐ろしく設計されているためにデバッガーがデフォルトでそれを許可しない場合でも、それをさらに構築するには、マシン上のローカルコードを変更して結果を保存し、デバッガーでチェックアウトするだけです。その目的のためだけに変数に値を保存する理由はまだありません。
グレートダック

11

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

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

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

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

今、名前getAgetB示唆彼らはに割り当てることthis.athis.b、私たちが知ることができないことを確認するためにだけ見てからgetResult。したがって、メソッドに渡される値this.athis.b値が、mix代わりに呼び出さthisれた前のオブジェクトの状態に由来する可能性getResultがあります。これは、メソッドが呼び出される方法とタイミングをクライアントが制御するため、予測できません。

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

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

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

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


2

これはやや言語に依存しますが、関数型プログラミングのそれほど明白でない利点の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番目の2つについては、これは左から右です。Lisp / Clojureの例では、右から左に似ています。あなたの言語の「デフォルトの方向」にないコードを書くことに少し警戒するべきです、そして、2つを混ぜる「中間の」表現は絶対に避けられるべきです。

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


2
単純なLINQ式またはラムダで変数名としてアンダースコアを使用することにしました。myCollection.Select(_ => _.SomeProp).Where(_ => _.Size > 4);
グラハム

これがOPの質問に少しでも答えているかどうかわからない...
AC

1
なぜダウン投票するのですか?それは直接、それはまだ有用な情報の質問に答えていない場合でも、私には良い答えのように思える
reggaeguitar

1

「既存のスコープまたは追加するのが妥当なスコープの中で」「可能な限り最小のスコープ」を読む必要があるため、ノーと言います。それ以外の場合{}、変数のスコープが最後の使用範囲を超えないようにするために、人工スコープ(C言語のような無償ブロックなど)を作成する必要があることを意味します。通常、既に存在しない限り難読化/クラッターとして眉をひそめますスコープが独立して存在する正当な理由。


2
多くの言語でのスコーピングの問題の1つは、最後の使用が他の変数の初期値の計算にあるいくつかの変数を持つことが非常に一般的であることです。言語にスコープの目的で明示的に構造があり、内部スコープ変数を使用して計算された値を外部スコープ変数の初期化で使用できるようにする場合、このようなスコープは、多くの言語が必要とする厳密にネストされたスコープよりも便利です。
スーパーキャット

1

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

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

変数についても同様です。論理データ構造を理解可能な部分に指摘します。または、単にパラメーターに名前を付ける(統計する)こともできます。

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

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


1

NOの理由として多少欠けているのは、デバッグ/読みやすさです。コードはそのために最適化されるべきであり、明確で簡潔な名前は多くの場合に役立ちます。

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の例のように、単に中間結果を導入するのが最善です。これがどのくらいの頻度で要求されるかを想像するのは難しく、チェックインするものに注意すること-デバッグ変数が多すぎると混乱を招く-再び、判断の呼び出し。


0

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

しかし、「不要」とは、変数を使用せずに同等のプログラムを作成できるという意味ではありません。そうしないと、すべてをバイナリで作成する必要があると言われます。

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

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

コードの読みやすさは、正しく動作すること以外のすべてに勝ります(許容可能なパフォーマンス基準が正しく含まれていますが、これは「可能な限り高速」ではありません)。

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