web-dev-qa-db-ja.com

可能であれば、ローカル変数を削除する必要がありますか?

たとえば、AndroidでCPUをオンに保つには、次のようなコードを使用できます。

PowerManager powerManager = (PowerManager)getSystemService(POWER_SERVICE);
WakeLock wakeLock = powerManager.newWakeLock(PowerManager.PARTIAL_WAKE_LOCK, "abc");
wakeLock.acquire();

しかし、ローカル変数powerManagerwakeLockは削除できると思います。

((PowerManager)getSystemService(POWER_SERVICE))
    .newWakeLock(PowerManager.PARTIAL_WAKE_LOCK, "MyWakelockTag")
    .acquire();

同様のシーンがiOSアラートビューに表示されます。例:from

UIAlertView *alert = [[UIAlertView alloc]
    initWithTitle:@"my title"
    message:@"my message"
    delegate:nil
    cancelButtonTitle:@"ok"
    otherButtonTitles:nil];
[alert show];

-(void)alertView:(UIAlertView *)alertView clickedButtonAtIndex:(NSInteger)buttonIndex{
    [alertView release];
}

に:

[[[UIAlertView alloc]
    initWithTitle:@"my title"
    message:@"my message"
    delegate:nil
    cancelButtonTitle:@"ok"
    otherButtonTitles:nil] show];

-(void)alertView:(UIAlertView *)alertView clickedButtonAtIndex:(NSInteger)buttonIndex{
    [alertView release];
}

スコープで一度だけ使用されるローカル変数を削除することは良い習慣ですか?

101
ggrr

コードは書かれているよりもはるかに頻繁に読み取られるため、6か月後(それがあなたかもしれません)のコードを読み取らなければならない貧しい魂に同情し、最も明確で理解しやすいコードを求めて努力する必要があります。私の意見では、ローカル変数を使用した最初の形式は、はるかに理解しやすいと思います。 1行の3つのアクションではなく、3行の3つのアクションが表示されます。

そして、ローカル変数を取り除くことによって何かを最適化していると思うなら、そうではありません。最近のコンパイラーはpowerManagerをレジスターに入れます 1、ローカル変数が使用されているかどうかにかかわらず、newWakeLockメソッドを呼び出します。 wakeLockについても同様です。したがって、どちらの場合でも、同じコンパイル済みコードが作成されます。

1 宣言とローカル変数の使用の間に多くの介在コードがある場合は、スタックに入れられる可能性がありますが、これは細かいことです。

237
Tony BenBrahim

一部の非常に賛成されたコメントがこれを述べましたが、私が見た答えはどれもしなかったので、それを答えとして追加します。この問題を決定する主な要因は次のとおりです。

デバッグ可能性

多くの場合、開発者はコードを書くよりもデバッグにはるかに多くの時間と労力を費やしています。

ローカル変数を使用すると、次のことができます。

  • 割り当てられている行のブレークポイント(条件付きブレークポイントなど、他のすべてのブレークポイント装飾を含む)

  • ローカル変数の値の検査/監視/印刷/値の変更

  • キャストによって発生する問題をキャッチします。

  • 読みやすいスタックトレースがある(ラインXYZには10ではなく1つの操作があります)

ローカル変数がないと、デバッガーによっては、上記のすべてのタスクがはるかに困難、非常に困難、またはまったく不可能になります。

したがって、悪名高い格言に従ってください(自分の後に次の開発者がそれを維持するように、あなたが住んでいる場所を知っている狂った狂人であるかのようにコードを記述してください)、そしての側で誤りデバッグが容易、つまり、ローカル変数を使用

98
DVK

コードを理解しやすくする場合のみ。あなたの例では、読みにくくなっていると思います。

コンパイルされたコードの変数を削除することは、立派なコンパイラにとっては簡単な操作です。自分で出力を調べて確認できます。

47
whatsisname

あなたの質問は、「スコープ内で一度だけ使用されるローカル変数を削除することは良い習慣ですか?」間違った基準をテストしています。ローカル変数のユーティリティは、それが使用される回数には依存しませんが、コードをより明確にするかどうかには依存しません。意味のある名前で中間値にラベルを付けると、コードをより小さく、より消化しやすいチャンクに分割するため、提示したような状況での明瞭さが向上します。

私の見解では、あなたが提示した未変更のコードは、変更後のコードよりも明確であるため、変更しないでください。実際、わかりやすくするために、変更したコードからローカル変数を引き出すことを検討します。

ローカル変数によるパフォーマンスへの影響は期待できません。たとえローカル変数があったとしても、コードがプログラムの速度が重要な部分で非常にタイトなループにない限り、検討するには小さすぎる可能性があります。

29
Jack Aidley

多分。

個人的には、型キャストが含まれている場合は、ローカル変数を削除するのをためらいます。括弧の数が私が持っている精神的な限界に近づき始めているので、あなたのコンパクトバージョンは読みにくくなっています。さらに、ローカル変数を使用した、やや詳細なバージョンでは必要なかった新しいブラケットのセットも導入されています。

コードエディターによって提供される構文の強調表示は、このような懸念をある程度軽減する可能性があります。

私の目には、これはより良い妥協です。

PowerManager powerManager = (PowerManager)getSystemService(POWER_SERVICE);
powerManager.newWakeLock(PowerManager.PARTIAL_WAKE_LOCK, "abc").acquire();

したがって、1つのローカル変数を保持し、他のローカル変数を破棄します。タイプキャストはすでにタイプ情報を提供しているので、C#では最初の行でvarキーワードを使用します。

15
9Rune5

私はローカル変数を支持する回答の有効性を受け入れますが、悪魔の支持者を演じ、逆説を提示します。

私は個人的に、ドキュメントの目的に相当する純粋にローカル変数を使用することには反対ですが、その理由自体は、実践に価値があることを示しています。ローカル変数はコードを効果的に疑似ドキュメント化します。

あなたの場合、主な問題はインデントであり、変数がないことです。次のようにコードをフォーマットすることができます(すべきです)。

((PowerManager)getSystemService(POWER_SERVICE))
        .newWakeLock(PowerManager.PARTIAL_WAKE_LOCK,"MyWakelockTag")
        .acquire();

ローカル変数を使用したバージョンと比較してください。

PowerManager powerManager=(PowerManager)getSystemService(POWER_SERVICE);
WakeLock wakeLock = powerManager.newWakeLock(PowerManager.PARTIAL_WAKE_LOCK,"abc");
wakeLock.acquire();

ローカル変数の場合:名前はリーダーにいくつかの情報を追加し、タイプは明確に指定されますが、実際のメソッド呼び出しはあまり見えず、(私の意見では)明確に読み取られません。

ローカル変数を使用しない場合:より簡潔になり、メソッド呼び出しがより見やすくなりますが、何が返されるかについてより多くの情報を要求する場合があります。ただし、一部のIDEではこれを表示できます。

さらに3つのコメント:

  1. 私の意見では、機能を使用していないコードを追加すると、読者が実際に機能を使用しておらず、単に「ドキュメント化」のために存在することを理解する必要があるため、混乱を招く場合があります。たとえば、このメソッドが100行の長さである場合、メソッド全体を読まない限り、ローカル変数(したがって、メソッド呼び出しの結果)が後で必要にならないことは明らかではありません。

  2. ローカル変数を追加することは、それらのタイプを指定する必要があることを意味します。これにより、コードに他の方法では存在しない依存関係が導入されます。メソッドの戻り値の型が簡単に変更された場合(たとえば、名前が変更された場合)、コードを更新する必要がありますが、ローカル変数がない場合はそうしません。

  3. デバッガーがメソッドから返された値を表示しない場合、ローカル変数を使用するとデバッグが簡単になります。しかし、そのための修正は、デバッガーの欠陥を修正することであり、コードを変更することではありません。

13
rghome

ここには動機の緊張があります。一時変数の導入により、コードが読みやすくなります。しかし、それらは Extract MethodReplace Temp with Query などの他の可能なリファクタリングを防ぎ、見にくくすることもできます。これらの後者のタイプのリファクタリングは、適切な場合、多くの場合、一時変数よりもはるかに大きな利点を提供します。

これらの後者のリファクタリングの動機について、 Fowlerは書き込みます

「tempsの問題は、それらが一時的でローカルであることです。それらは、それらが使用されるメソッドのコンテキストでのみ表示されるため、tempsは、それがtempに到達できる唯一の方法であるため、長いメソッドを推奨する傾向があります。 tempをクエリメソッドに置き換えると、クラス内のどのメソッドでも情報を取得できます。これにより、クラスのコードをより明確にすることができます。」

したがって、はい。コードが読みやすくなる場合はtempsを使用してください。特に、それがあなたとあなたのチームにとってローカルな規範である場合は特にそうです。ただし、これを行うと、別の大きな改善点を見付けることが難しくなる場合があることに注意してください。そのような臨時雇用者なしで行く価値があるときを感じる能力を発達させ、そうすることでより快適になることができれば、それはおそらく良いことです。

FWIW私は個人的に、ファウラーの本「リファクタリング」を10年間読むのを避けました。私は完全に間違っていました。私が最終的にそれを読んだとき、それは私の日々のコーディング慣行を大きく変えました。

6

まあ、それはローカル変数を削除することをお勧めします可能であればコードを読みやすくします。あなたのその長いワンライナーは読むことができませんが、それはかなり冗長なOOスタイルに従います。

acquireWakeLock(POWER_SERVICE, PARTIAL, "abc");

それから私はそれがおそらく良い考えだと思います。おそらく、ヘルパーメソッドを導入して、類似したものに要約することができます。このようなコードが複数回表示される場合は、価値があるかもしれません。

3
leftaroundabout

これは、独自の名前を持つアンチパターンです Train Wreck 。置き換えを回避するいくつかの理由はすでに述べられています。

  • 読みにくい
  • デバッグが難しい(変数を監視し、例外の場所を検出するため)
  • デメテルの法則(LoD)の違反

このメソッドが他のオブジェクトからの知識が多すぎるかどうかを検討してください。 Method chaining は、カップリングを減らすのに役立つ代替手段です。

また、オブジェクトへの参照は非常に安価であることも覚えておいてください。

2
Borjab

注意すべきことの1つは、コードがさまざまな「深さ」で頻繁に読み取られることです。このコード:

_PowerManager powerManager = (PowerManager)getSystemService(POWER_SERVICE);
WakeLock wakeLock = powerManager.newWakeLock(PowerManager.PARTIAL_WAKE_LOCK, "abc");
wakeLock.acquire();
_

「スキミング」は簡単です。それは3つのステートメントです。まず、PowerManagerを考えます。次に、WakeLockを考え出します。次に、acquire the wakeLock。これは、各行の先頭を見るだけで簡単にわかります。単純な変数の割り当ては、部分的に "Type varName = ..."として簡単に認識でき、 "..."を精神的に読み飛ばすだけです。同様に、最後のステートメントは明らかに割り当ての形式ではありませんが、2つの名前しか含まれていないため、「主な要点」がすぐにわかります。多くの場合、「このコードは何をするのですか?」高いレベルで。

ここにあると思われる微妙なバグを追跡している場合は、明らかにこれをさらに詳しく調べる必要があり、実際には「...」を覚えています。ただし、個別のステートメント構造は、一度に1つのステートメントを実行するのに役立ちます(特に、各ステートメントで呼び出されるものの実装にさらに深くジャンプする必要がある場合に役立ちます。戻ったとき、「1つのユニット」を完全に理解しました。その後、次のステートメントに進むことができます)。

_((PowerManager)getSystemService(POWER_SERVICE))
    .newWakeLock(PowerManager.PARTIAL_WAKE_LOCK, "MyWakelockTag")
    .acquire();
_

今、それはすべて1つのステートメントです。トップレベルの構造は、読みやすくするのは簡単ではありません。 OPの元のバージョンでは、構造を視覚的に伝えるための改行とインデントがないと、括弧を数えて3つのステップのシーケンスにデコードする必要がありました。メソッド呼び出しのチェーンとして配置されるのではなく、マルチパート式の一部が相互にネストされている場合でも、これは同様に見える可能性があるため、括弧を数えずに信頼するように注意する必要があります。そして、私がインデントを信頼し、これのすべての推定ポイントとして最後のものにすくい取った場合、.acquire()はそれ自体で何を私に伝えますか?

ただし、それが必要な場合もあります。私があなたの変換を途中で適用して書いた場合:

_WakeLock wakeLock =
     ((PowerManeger)getSystemService(POWER_SERVICE))
    .newWakeLock(PowerManager.PARTIAL_WAKE_LOCK, "MyWakeLockTage");
wakeLock.acquire();
_

これは、「WakeLockを取得してからacquire itを取得する」という簡単な概要を伝えます。最初のバージョンよりもさらにシンプルです。取得したものがWakeLockであることはすぐにわかります。 PowerManagerを取得することは、このコードの点ではかなり重要ではないサブディテールであるが、wakeLockが重要である場合、PowerManagerを埋めることが実際に役立つ可能性があります。このコードが何をしているのかすぐに理解したいだけなら、それをざっと見るのは自然なことです。名前を付けないことは、それがisが1回だけ使用されることを伝え、時にはthats重要なことです(残りのスコープが非常に長い間、私はそれを再び使用するかどうかを知るためにそれらすべてを読む必要があります;明示的なサブスコープを使用することは、あなたの言語がそれらをサポートしている場合、それに対処する別の方法です).

これは、すべてがコンテキストと通信したい内容に依存することを示しています。自然言語で散文を書くのと同じように、情報内容に関して基本的に同等である特定のコードを書く方法は常に多くあります。自然言語で散文を書く場合と同様に、それらの間でどのように選択するかは、一般にnotである必要があります。むしろhowコードを書き留めることを選択すると、特定の事柄が強調され、他の事柄が強調されなくなります。実際に強調したいことに基づいて、それらの選択を意識的に行うように努力する必要があります(技術的な理由により、読みにくいコードを書く選択を含む場合があります)。特に、コードの「要点」を(さまざまなレベルで)取得する必要がある読者に役立つものについて考えてください。これは、式ごとの非常に密接な読み取りよりもはるかに頻繁に発生するためです。

2
Ben

ここで デメテルの法則 を考えてみましょう。 LoDに関するウィキペディアの記事では、次のように述べられています。

関数のデメテルの法則では、オブジェクトOのメソッドmは、次の種類のオブジェクトのメソッドのみを呼び出すことができる必要があります。[2]

  1. O自体
  2. mのパラメーター
  3. M内で作成/インスタンス化されたオブジェクト
  4. Oの直接コンポーネントオブジェクト
  5. Mのスコープ内で、Oからアクセス可能なグローバル変数

この法則に従うことの結果の1つは、上記の2番目の例に示すように、長い点線の文字列で他のメソッドによって返されたオブジェクトのメソッドを呼び出さないことです。

((PowerManager)getSystemService(POWER_SERVICE)).newWakeLock(PowerManager.PARTIAL_WAKE_LOCK,"MyWakelockTag").acquire();

これが何をしているかを理解することは本当に難しいです。それを理解するには、各プロシージャの機能を理解し、どの関数がどのオブジェクトで呼び出されているかを解読する必要があります。最初のコードブロック

PowerManager powerManager=(PowerManager)getSystemService(POWER_SERVICE);
WakeLock wakeLock=powerManager.newWakeLock(PowerManager.PARTIAL_WAKE_LOCK,"abc");
wakeLock.acquire();

powerManagerオブジェクトを取得し、PowerManagerからWakeLockオブジェクトを取得して、wakeLockを取得しようとしていることを明確に示しています。上記はLoDのルール#3に従います-これらのオブジェクトをコード内でインスタンス化しているので、それらを使用して必要なことを実行できます。

おそらくもう1つの考え方は、ソフトウェアを作成するときは、簡潔にするためではなく、明確にするために書く必要があることを覚えておくことです。すべてのソフトウェア開発の90%はメンテナンスです。維持したくないコードを記述しないでください。

幸運を祈ります。

1つの重要な側面は他の回答では言及されていません。変数を追加するときはいつでも、変更可能な状態を導入します。コードが複雑になり、理解やテストが難しくなるため、これは通常悪いことです。もちろん、変数のスコープが小さいほど、問題は小さくなります。

実際に必要なのは、値を変更できる変数ではなく、一時定数です。したがって、言語で許可されている場合は、コードでこれを表現することを検討してください。 Javaではfinalを使用でき、C++ではconstを使用できます。ほとんどの関数型言語では、これがデフォルトの動作です。

ローカル変数が最も害の少ないタイプの変更可能な状態であることは事実です。メンバー変数はさらに多くの問題を引き起こす可能性があり、静的変数はさらに悪化します。それでも、コードでできる限り正確に表現することが重要だと私は思います。そして、後で変更される可能性のある変数と、中間結果の単なる名前との間には大きな違いがあります。したがって、あなたの言語でこの違いを表現できる場合は、それを行ってください。

1
Frank Puffer

述べられた質問は、「できればローカル変数を排除すべきか」ということです。

いいえ、できるからといって排除するべきではありません。

ショップのコーディングガイドラインに従う必要があります。

ほとんどの場合、ローカル変数はコードの読み取りとデバッグを容易にします。

PowerManager powerManager。私にとって、クラスの小文字は、それが1回限りの使用であることを意味します。

すべきでないのは、変数が高価なリソースを保持する場合です。多くの言語には、クリーンアップ/リリースが必要なローカル変数の構文があります。 C#では使用しています。

using(SQLconnection conn = new SQLconnnection())
{
    using(SQLcommand cmd = SQLconnnection.CreateCommand())
    {
    }
}
1
paparazzo