コード内の変数の使用を一般化する


11

変数を一般化することをお勧めします(単一の変数を使用してすべての値を保存すること)。
簡単な例を考えてみましょう

 Strings querycre,queryins,queryup,querydel; 
    querycre = 'Create table XYZ ...';
    execute querycre ;
    queryins = 'Insert into XYZ ...';
    execute queryins ;
    queryup  = 'Update  XYZ set ...';
    execute queryup;
    querydel = 'Delete from XYZ ...';
    execute querydel ;

そして

 Strings query; 
    query= 'Create table XYZ ... ';
    execute query ;
    query= 'Insert into XYZ ...';
    execute query ;
    query= 'Update  XYZ set ...';
    execute query ;
    query= 'Delete from XYZ ...';
    execute query ;

最初のケースでは、それぞれがデータを格納する4つの文字列を使用して、サフィックスに記載されているアクションを実行します。
2番目のケースでは、すべての種類のデータを格納するための1つの変数のみ。
異なる変数があると、他の人が読みやすく、理解しやすくなります。しかし、それらが多すぎると、管理が難しくなります。

また、変数が多すぎるとパフォーマンスが低下しますか?

PS:例のコードについては答えないでください。それは単に私が本当に意味することを伝えるためでした。


もちろん、同じ変数を再利用します...関数でそれを定義したからです。それが機能の目的です。
zzzzBov

回答:


26

この質問を自問しなければならないのは、DRY(Do n't Repeat Yourself)をフォローしていないというかなり強い匂いです。仮のカーリーブレース言語でこれを持っていると仮定します:

function doFoo() {
    query = "SELECT a, b, c FROM foobar WHERE baz = 23";
    result = runQuery(query);
    print(result);

    query = "SELECT foo, bar FROM quux WHERE x IS NULL";
    result = runQuery(query);
    print(result);

    query = "SELECT a.foo, b.bar FROM quux a INNER JOIN quuux b ON b.quux_id = a.id ORDER BY date_added LIMIT 10";
    result = runQuery(query);
    print(result);
}

リファクタリング:

function runAndPrint(query) {
    result = runQuery(query);
    print(result);
}

function doFoo() {
    runAndPrint("SELECT a, b, c FROM foobar WHERE baz = 23");
    runAndPrint("SELECT foo, bar FROM quux WHERE x IS NULL");
    runAndPrint("SELECT a.foo, b.bar FROM quux a INNER JOIN quuux b ON b.quux_id = a.id ORDER BY date_added LIMIT 10");
}

異なる変数を使用するかどうかを決定する必要がなくなり、同じ変更を3回適用するのではなく、クエリを実行して結果を1か所で出力するためのロジックを変更できるようになりました。(たとえば、クエリ結果をすぐに印刷するのではなく、テンプレートシステムを介してポンプすることを決定する場合があります)。


2
私はちょうど
乾燥の

1
@tdammersは、関数内に2行のコードのみを含めるのが良いですか?この関数がある場合は、doFoo(){print(runQuery( "Selct a、b、c from XYZ"));}
Shirish11

1
いいえ、呼び出しスタックは増加しません。呼び出しごとにrunAndPrint1つのスタックフレームをプッシュし、関数の終了時にそれをポップします。3回呼び出すと、プッシュ/ポップペアが3回実行されますが、スタックは一度に1フレーム以上は成長しません。あなたは本当に再帰関数での呼び出しスタックの深さを心配する必要があります。
tdammers

3
そして、たった2行のコードで機能するのはまったく問題ありません。2行で論理ユニットを作成すれば、2行で済みます。少しの情報を1か所に隔離しておくために、たくさんの1行関数を作成しました。
tdammers

1
@JamesAnderson:やや不自然な例ですが、ポイントを説明するのに役立ちます。コードの行数ではありません。何回同じ事実を述べるかです。など真実の原則、汝コピーではなく、貼り付けルール、単一のソースだけでなく、あるものについてDRYだと
tdammers

14

通常、これは悪い習慣です。

このように変数を再利用すると、理解するのがわかりにくいコードを作成できます。

コードを読む人は、変数がそのような方法で再利用されることを期待せず、開始時に設定された値が関数の終了時に異なる値を持つ理由を知りません。

投稿した例は非常に単純で、この問題の影響はあまりありませんが、変数を再利用するコードの代表ではありません(最初に設定され、途中で再利用される-見えないところ)。

あなたが与えた例は、クエリを渡して実行する関数へのカプセル化に役立ちます。


システムのパフォーマンスはどうなりますか?
-Shirish11

@ Shirish11-かもしれない。コンパイラ、言語、環境、その他の変数に依存します。
-Oded

通常、コンパイラはこれを最適化するのが得意です。ただし、常にコンパイラ/プレートフォーム/特定のケース/構成に依存します。
deadalnix

7

自己文書化されたコードは読みやすく、維持しやすい

最小の驚き原則とドキュメントとしてコード原則に従ってください:1つの目標に1つの変数を使用し、その使用を理解しやすくし、コードを説明なしで読みやすくします。

正しく構造化されたコードは(再)使用が簡単(したがって安価)です

また、ここでは、query実行前にステートメントを準備するために常に使用されるように見えます。これはおそらく、このコードの一部を1つ(または複数)のヘルパーメソッドリファクタリングして、クエリを準備および実行する(DRYの原則に準拠する)ことを示す兆候です。

このようにして、効果的に:

  • ヘルパーメソッドで変数を1つだけ使用して、現在のコンテキストのクエリを識別します。
  • クエリを再実行するたびに入力するコードを少なくする必要があります。
  • コードを他の人にとって読みやすくします。

例:

リファクタリングされたバージョンの方が明らかに優れている例から取って、これを考慮してください。もちろん、あなたのスニペットはこの質問の目的のための単なる例にすぎませんが、その概念は依然として真実であり、スケールがあります。

あなたの例1:

Strings querycre,queryins,queryup,querydel; 
    querycre = 'Create table XYZ ...';
    execute querycre ;
    queryins = 'Insert into XYZ ...';
    execute queryins ;
    queryup  = 'Update  XYZ set ...';
    execute queryup;
    querydel = 'Delete from XYZ ...';
    execute querydel ;

あなたの例2:

 Strings query; 
    query= 'Create table XYZ ...';
    execute query ;
    query= 'Insert into XYZ ...';
    execute query ;
    query= 'Update  XYZ set ...';
    execute query ;
    query= 'Delete from XYZ ...';
    execute query ;

例3(リファクタリングされた擬似コード):

def executeQuery(query, parameters...)
    statement = prepareStatement(query, parameters);
    execute statement;
end

// call point:
executeQuery('Create table XYZ ... ');
executeQuery('Insert into XYZ ...');
executeQuery('Update  XYZ set ...');
executeQuery('Delete from XYZ ...');

利点は、定期的な再利用にあります。

個人的な逸話

私はもともと、限られた画面の不動産を扱うCプログラマーとして始めたので、変数を再利用することは、コンパイルされたコード(当時)とより多くのコードを一度に読めるようにするために意味がありました。

しかし、その後、高レベルの言語に移行し、関数型プログラミングに磨きをかけ、副作用を制限するために可能な限り不変の変数と不変の参照を使用する習慣を取りました。

私にとっては何ですか?

関数の入力をすべて不変にし、新しい結果を返すという習慣をとる場合(真の数学関数がそうするように)、ストアを複製しないという習慣になります。

拡張により、これは以下につながります。

  • 短い関数を書いて、
  • 明確に定義された目的で、
  • 理解しやすい
  • 再利用するには、
  • 拡張する(OO継承または機能連鎖による)
  • および文書化(すでに自己文書化)。

ここで可変状態にメリットがないと言っているのではなく、習慣があなたにどのように成長し、コードの可読性にどのように影響するかを指摘しています。


2

コード設計の観点から

一般に、変数を再利用して異なる値を格納することは問題ありません -結局、変数が格納される値は異なるため、変数と呼ばれるのは、値が同じ型であるだけでなく同じことを意味する限りです。たとえば、currentQueryここで変数を再利用してもかまいません。

for currentQuery in queries:
    execute query;

当然そこにあなたがしてループだ持っている変数を再利用することではなく、ループがなかった場合でも、それは大丈夫だったでしょう。値が同じものを意味しない場合は、別の変数を使用します。

ただし、具体的には、記述しているコードはあまり見栄えがよくありません繰り返します。ループまたはヘルパーメソッド呼び出し(またはその両方)を使用することをお勧めします。個人的には、最初のバージョンまたは2番目のバージョンのように見えるプロダクションコードはめったに見ませんでしたが、私が持っているケースでは、2番目のバージョン(変数の再利用)がより一般的だったと思います。

パフォーマンスの観点から

使用する言語、コンパイラ、ランタイムシステムに依存しますが、一般に違いはないはずです -特にスタックベースのレジスタマシン用のコンパイラ(人気のあるx86 / x86-64など)はとにかく同じ変数が必要かどうかを完全に無視して、割り当て可能なターゲットとして空きスタックメモリまたはレジスタを使用します。

たとえばgcc -O2、まったく同じバイナリを生成し、私が知っている唯一のパフォーマンスの違いは、コンパイル中のシンボルテーブルのサイズです。60年代に戻らない限り、完全に無視できます。

Javaコンパイラーは、1番目のバージョンのためにより多くのストレージを必要とするバイトコードを生成しますが、JVMのジッターはそれをとにかく削除するので、高度に最適化されたコードが必要な場合でも、実質的に顕著なパフォーマンスへの影響はないと思われます。


0

ほとんどの場合、変数の再利用は問題ないと思います。

私にとっては、ほとんどの場合、クエリ変数を再利用するだけです。ほとんどの場合、クエリはすぐに実行されます。すぐにクエリを実行しない場合、通常は別の変数名を使用します。


-1

コンパイラが特に愚かである場合、スタックの使用量が増加する可能性があります。個人的には、各クエリに個別の変数があると読みやすさが増すとは思いませんが、クエリ文字列を実際に見て、その機能を確認する必要があります。


読者が私が探しているものを理解しやすいように、簡単な例を提供しました。私のコードはこれよりはるかに複雑です。
Shirish11

-2

この例では、2番目の例に進みます。読者とオプティマイザーの両方にとって、あなたが何をしているのかは明らかです。最初の例はもう少し適切で、やや複雑なコードでは使用しますが、次のようにします。

{
    String query = 'Create table XYZ ...';
    execute query;
}
{
    String query = 'Insert table XYZ ...';
    execute query;
}
And so on...

(この時点で、私はtdammersのソリューションを検討するかもしれません。)

最初の例の問題は、querycreブロック全体のスコープ内にあることです。これは広範囲にわたる可能性があります。これは、コードを読んでいる人を混乱させる可能性があります。また、オプティマイザを混乱させる可能性があります。オプティマイザは不必要なメモリ書き込みを残す可能性があるため、querycre必要に応じて後で利用できます(そうではありません)。すべての中括弧を使用するqueryと、レジスタにのみ格納されます(その場合)。

「テーブルの作成」や「実行」などのフレーズを使用すると、余分なメモリ書き込みがここで通知されるように見えないので、読者を混乱させるだけのコードに誤りがあります。ただし、速度重要なコードを記述している場合は、このことを知っておくと便利です。


同意しません。明確にするために2番目の例を好む場合、ヘルパーメソッドへの連続した呼び出しにリファクタリングする必要があります。より多くの意味を伝え、必要なコードが少なくなります。
ヘイレム

@haylem:このような実際の単純なケースでは、コードを読んでいる人が見つけ出さなければならないヘルパーメソッドを追加しています。(また、ヘルパーメソッドで問題が発生し、呼び出し元のすべての場所を把握する必要がある場合があります。)それほど明確ではないが、ほぼ同じ量のコード。より複雑なケースでは、解決策を採用し、次にtdammer採用します。私はこの質問に主に答えて、使用されていない変数が人間とオプティマイザの両方にもたらす(明らかに不明瞭ですが興味深い)問題を指摘しました。
ラルフシャピン

@haylem:あなたとtdammerは両方とも正しい解を与えます。場合によってはやりすぎになると思います。
ラルフシャピン
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.